File: /wordpress/plugins/wp-cloud-client/beta/src/Settings/NetworkSettingsPage.php
<?php
declare(strict_types=1);
namespace VPlugins\WPCloudClient\Settings;
use VPlugins\WPCloudClient\Analytics\GoogleAnalytics4;
use VPlugins\WPCloudClient\BusinessProfile\ProfileHelperPage;
/**
* Network Admin-only settings page for multisite installs.
*
* These settings (GA4 tracking ID, mail interceptor toggle, business profile)
* are single network-wide values shared by every site (see
* {@see SettingsRepository::getGa4TrackingId()} for why), so on multisite
* only a network super-admin may view or change them, and only from Network
* Admin — {@see \VPlugins\WPCloudClient\Plugin} registers this instead of
* SettingsPage when SettingsRepository::isMultisite() is true, so no
* per-site admin_menu entry exists at all on multisite.
*/
final class NetworkSettingsPage {
public const SLUG = 'wpcc-network-settings';
public const ACTION = 'wpcc_network_settings_save';
private const TAB_ANALYTICS = 'analytics';
private const TAB_MAIL = 'email-history';
private const TAB_PROFILE = 'business-profile';
/**
* Class constructor.
*
* @param SettingsRepository $settings Settings repository instance.
* @param GoogleAnalytics4 $ga4 Google Analytics 4 instance.
* @param ProfileHelperPage $profileHelper Business profile helper page.
*/
public function __construct(
private readonly SettingsRepository $settings,
private readonly GoogleAnalytics4 $ga4,
private readonly ?ProfileHelperPage $profileHelper,
) {}
/**
* Register the settings page under Network Admin > Settings.
*
* @return void
*/
public function addMenuPage(): void {
add_submenu_page(
'settings.php',
__( 'WP Cloud Client', 'wp-cloud-client' ),
__( 'WP Cloud Client', 'wp-cloud-client' ),
'manage_network_options',
self::SLUG,
[ $this, 'render' ],
);
}
/**
* Get the tab definitions (slug => label).
*
* @return array<string, string>
*/
private function getTabs(): array {
$tabs = [
self::TAB_ANALYTICS => __( 'Google Analytics 4', 'wp-cloud-client' ),
self::TAB_MAIL => __( 'Email History', 'wp-cloud-client' ),
self::TAB_PROFILE => __( 'Business Profile', 'wp-cloud-client' ),
];
if ( $this->settings->isStaging() ) {
unset( $tabs[ self::TAB_ANALYTICS ] );
}
return $tabs;
}
/**
* Get the currently active tab slug.
*
* @return string
*/
private function activeTab(): string {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab selection is not a state-changing action.
$tab = isset( $_GET['tab'] ) ? sanitize_key( (string) $_GET['tab'] ) : '';
$tabs = $this->getTabs();
return isset( $tabs[ $tab ] ) ? $tab : (string) ( array_key_first( $tabs ) ?? '' );
}
/**
* Build the URL for a given tab.
*
* Uses network_admin_url() instead of menu_page_url() because the
* latter always resolves against admin_url(), not the Network Admin
* context this page is registered in.
*
* @param string $tab Tab slug.
* @return string Tab URL.
*/
private function tabUrl( string $tab ): string {
return add_query_arg(
[
'page' => self::SLUG,
'tab' => $tab,
],
network_admin_url( 'settings.php' ),
);
}
/**
* Render the network settings page HTML.
*
* @return void
*/
public function render(): void {
if ( ! current_user_can( 'manage_network_options' ) ) {
return;
}
$configured = $this->settings->isConfigured();
$tabs = $this->getTabs();
$activeTab = $this->activeTab();
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<?php
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display flag set by our own redirect after save(), not a state-changing action.
if ( isset( $_GET['updated'] ) ) :
?>
<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Settings saved.', 'wp-cloud-client' ); ?></p></div>
<?php endif; ?>
<div class="wpcc-status-indicator" style="margin: 15px 0; padding: 10px 15px; border-left: 4px solid <?php echo $configured ? '#00a32a' : '#d63638'; ?>; background: #fff;">
<strong><?php esc_html_e( 'Connection Status:', 'wp-cloud-client' ); ?></strong>
<?php if ( $configured ) : ?>
<span style="color: #00a32a;"><?php esc_html_e( 'Configured', 'wp-cloud-client' ); ?></span>
<?php else : ?>
<span style="color: #d63638;"><?php esc_html_e( 'Not Configured', 'wp-cloud-client' ); ?></span>
<?php endif; ?>
<p class="description"><?php esc_html_e( 'These settings apply to every site in this network. Authentication credentials are provisioned automatically by the server.', 'wp-cloud-client' ); ?></p>
</div>
<nav class="nav-tab-wrapper">
<?php foreach ( $tabs as $slug => $label ) : ?>
<a href="<?php echo esc_url( $this->tabUrl( $slug ) ); ?>"
class="nav-tab <?php echo $activeTab === $slug ? 'nav-tab-active' : ''; ?>">
<?php echo esc_html( $label ); ?>
</a>
<?php endforeach; ?>
</nav>
<div class="wpcc-tab-content" style="margin-top: 20px;">
<?php $this->renderTabContent( $activeTab ); ?>
</div>
</div>
<?php
}
/**
* Render the content for a specific tab.
*
* @param string $tab The active tab slug.
* @return void
*/
private function renderTabContent( string $tab ): void {
if ( self::TAB_PROFILE === $tab ) {
if ( null !== $this->profileHelper ) {
$this->profileHelper->render();
} else {
printf( '<p>%s</p>', esc_html__( 'Business Profile module is not available.', 'wp-cloud-client' ) );
}
return;
}
?>
<form action="<?php echo esc_url( network_admin_url( 'edit.php?action=' . self::ACTION ) ); ?>" method="post">
<?php wp_nonce_field( self::ACTION ); ?>
<input type="hidden" name="tab" value="<?php echo esc_attr( $tab ); ?>" />
<?php if ( self::TAB_ANALYTICS === $tab ) : ?>
<input type="hidden" name="_ga4_tab" value="1" />
<h2><?php esc_html_e( 'Google Analytics 4', 'wp-cloud-client' ); ?></h2>
<p><?php esc_html_e( 'Optionally connect a custom Google Analytics 4 property to track every site in this network.', 'wp-cloud-client' ); ?></p>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="ga4_tracking_id"><?php esc_html_e( 'Custom Google Analytics 4 Tracking ID', 'wp-cloud-client' ); ?></label></th>
<td>
<input type="text" id="ga4_tracking_id" name="ga4_tracking_id" value="<?php echo esc_attr( $this->settings->getGa4TrackingId() ?? '' ); ?>" class="regular-text" placeholder="G-XXXXXXXXXX" />
<p class="description"><?php esc_html_e( 'Enter your own Google Analytics 4 Tracking ID (format: G-XXXXXXXXXX). Tracking will run concurrently with the built-in tracking.', 'wp-cloud-client' ); ?></p>
</td>
</tr>
</table>
<?php elseif ( self::TAB_MAIL === $tab ) : ?>
<input type="hidden" name="_mail_tab" value="1" />
<h2><?php esc_html_e( 'Email History', 'wp-cloud-client' ); ?></h2>
<p><?php esc_html_e( 'WP Cloud Client intercepts outgoing emails to provide an Email History log in the dashboard. Disable this if you are using a third-party SMTP plugin that should handle all mail delivery.', 'wp-cloud-client' ); ?></p>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Disable Website Pro Mail System', 'wp-cloud-client' ); ?></th>
<td>
<label>
<input type="checkbox" id="disable_mail_interceptor" name="disable_mail_interceptor" value="1" <?php checked( $this->settings->isMailInterceptorDisabled() ); ?> />
<?php esc_html_e( 'Disable email interception (Email History will not be available)', 'wp-cloud-client' ); ?>
</label>
</td>
</tr>
</table>
<?php endif; ?>
<?php submit_button( __( 'Save Network Settings', 'wp-cloud-client' ) ); ?>
</form>
<?php
}
/**
* Handle the network settings form submission.
*
* Hooked to network_admin_edit_{self::ACTION}. SettingsPage's Settings
* API/options.php form processing doesn't work in Network Admin context,
* so this saves directly through SettingsRepository and redirects back.
*
* Each tab submits its own form with a hidden sentinel (_ga4_tab /
* _mail_tab) so this only touches the setting for the tab that was
* actually submitted — otherwise saving the GA4 tab would read a missing
* disable_mail_interceptor checkbox as unchecked and silently re-enable
* mail interception (and vice versa).
*
* @return void
*/
public function save(): void {
check_admin_referer( self::ACTION );
if ( ! current_user_can( 'manage_network_options' ) ) {
wp_die( esc_html__( 'You do not have permission to do that.', 'wp-cloud-client' ) );
}
if ( ! $this->settings->isStaging() && ! empty( $_POST['_ga4_tab'] ) ) {
$this->settings->setGa4TrackingId( $this->ga4->sanitizeTrackingId( wp_unslash( (string) ( $_POST['ga4_tracking_id'] ?? '' ) ) ) );
}
if ( ! empty( $_POST['_mail_tab'] ) ) {
$this->settings->setMailInterceptorDisabled( ! empty( $_POST['disable_mail_interceptor'] ) );
}
$tab = isset( $_POST['tab'] ) ? sanitize_key( (string) $_POST['tab'] ) : '';
wp_safe_redirect(
add_query_arg(
[
'page' => self::SLUG,
'tab' => $tab,
'updated' => 'true',
],
network_admin_url( 'settings.php' ),
)
);
exit;
}
}