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
4 changes: 4 additions & 0 deletions src/admin/class-settings-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public function handle_form_submission(): void {
'website_urn' => isset( $_POST['website_urn'] ) ? sanitize_text_field( wp_unslash( $_POST['website_urn'] ) ) : '',
'merchant_api_key' => isset( $_POST['merchant_api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['merchant_api_key'] ) ) : null,
'bot_protection_enabled' => isset( $_POST['bot_protection_enabled'] ),
'analytics_enabled' => isset( $_POST['analytics_enabled'] ),
'active_paths' => isset( $_POST['active_paths'] ) && is_array( $_POST['active_paths'] )
? array_map( 'sanitize_text_field', wp_unslash( $_POST['active_paths'] ) )
: array(),
Expand Down Expand Up @@ -202,11 +203,13 @@ private function process_save_settings( array $form_data ): void {
} elseif ( '' === $merchant_api_key ) {
// API key field was shown but submitted empty — disable bot protection.
$this->settings->set_bot_protection_enabled( false );
$this->settings->set_analytics_enabled( false );
}

// Bot protection settings are only present when API key is already saved.
if ( null === $merchant_api_key ) {
$this->settings->set_bot_protection_enabled( $form_data['bot_protection_enabled'] );
$this->settings->set_analytics_enabled( $form_data['analytics_enabled'] );

$raw_paths = $form_data['active_paths'];

Expand Down Expand Up @@ -296,6 +299,7 @@ public function render_page(): void {
'website_urn' => $this->settings->get_website_urn(),
'license_url' => home_url( '/license.xml' ),
'bot_protection_enabled' => $this->settings->is_bot_protection_enabled(),
'analytics_enabled' => $this->settings->is_analytics_enabled(),
'active_paths' => $this->settings->get_active_paths(),
'site_url' => home_url( '/' ),
'resource_links' => array(
Expand Down
20 changes: 11 additions & 9 deletions src/class-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ public function init(): void {
$status_handler = new Status_Handler( $settings, SUPERTAB_CONNECT_API_BASE_URL, $http_client );
$status_handler->register();

$analytics_enabled = $settings->has_merchant_api_key() && $settings->is_bot_protection_enabled();
$bot_protection_active = $settings->has_merchant_api_key() && $settings->is_bot_protection_enabled();
$analytics_enabled = $bot_protection_active && $settings->is_analytics_enabled();

$dispatcher = null;
if ( $analytics_enabled && self::should_use_wp_queue() ) {
Expand All @@ -95,8 +96,8 @@ public function init(): void {
return;
}

if ( $analytics_enabled && ! defined( 'REST_REQUEST' ) && ! wp_doing_cron() ) {
$this->init_bot_protection( $settings, $http_client, $dispatcher );
if ( $bot_protection_active && ! defined( 'REST_REQUEST' ) && ! wp_doing_cron() ) {
$this->init_bot_protection( $settings, $http_client, $dispatcher, $analytics_enabled );
}
}

Expand Down Expand Up @@ -148,13 +149,14 @@ public function add_privacy_policy_content(): void {
/**
* Initialize bot protection for front-end requests.
*
* @param Settings $settings Settings manager.
* @param HttpClientInterface $http_client HTTP client for SDK requests.
* @param ?Analytics_Dispatcher $dispatcher When set, analytics events are queued via this
* dispatcher; when null, the SDK's default transport is used.
* @param Settings $settings Settings manager.
* @param HttpClientInterface $http_client HTTP client for SDK requests.
* @param ?Analytics_Dispatcher $dispatcher When set, analytics events are queued via this
* dispatcher; when null, the SDK's default transport is used.
* @param bool $analytics_enabled Whether the merchant opted in to analytics.
* @return void
*/
private function init_bot_protection( Settings $settings, HttpClientInterface $http_client, ?Analytics_Dispatcher $dispatcher ): void {
private function init_bot_protection( Settings $settings, HttpClientInterface $http_client, ?Analytics_Dispatcher $dispatcher, bool $analytics_enabled ): void {
$enforcement = self::get_enforcement_mode();

if ( null !== $dispatcher ) {
Expand All @@ -174,7 +176,7 @@ private function init_bot_protection( Settings $settings, HttpClientInterface $h
httpClient: $http_client,
baseUrl: SUPERTAB_CONNECT_API_BASE_URL,
cache: new WP_Transient_Cache(),
analyticsEnabled: true,
analyticsEnabled: $analytics_enabled,
analyticsTransport: $analytics_transport,
);
$bot_protection = new Bot_Protection( $supertab_connect, $settings );
Expand Down
29 changes: 29 additions & 0 deletions src/class-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ class Settings {
*/
private const OPTION_BOT_PROTECTION_ENABLED = 'supertab_connect_bot_protection_enabled';

/**
* Option name for the analytics enabled flag.
*
* @var string
*/
private const OPTION_ANALYTICS_ENABLED = 'supertab_connect_analytics_enabled';

/**
* Option name for active paths.
*
Expand Down Expand Up @@ -106,6 +113,27 @@ public function set_bot_protection_enabled( bool $enabled ): void {
update_option( self::OPTION_BOT_PROTECTION_ENABLED, $enabled );
}

/**
* Check if analytics is enabled.
*
* Opt-in: absent or falsy means disabled.
*
* @return bool True if analytics is enabled.
*/
public function is_analytics_enabled(): bool {
return (bool) get_option( self::OPTION_ANALYTICS_ENABLED, false );
}

/**
* Set the analytics enabled flag.
*
* @param bool $enabled Whether analytics should be enabled.
* @return void
*/
public function set_analytics_enabled( bool $enabled ): void {
update_option( self::OPTION_ANALYTICS_ENABLED, $enabled );
}

/**
* Get the active paths for bot protection.
*
Expand Down Expand Up @@ -173,6 +201,7 @@ public function delete(): void {
delete_option( self::OPTION_MERCHANT_API_KEY );
delete_option( self::OPTION_WEBSITE_URN );
delete_option( self::OPTION_BOT_PROTECTION_ENABLED );
delete_option( self::OPTION_ANALYTICS_ENABLED );
delete_option( self::OPTION_ACTIVE_PATHS );

// Invalidate cached license XML.
Expand Down
8 changes: 5 additions & 3 deletions src/class-status-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,13 @@ public function build_response( string $authorization_header, string $expected_a
);
}

// Mirrors the gate in Plugin::init(): bot protection (and with it,
// event reporting) only runs when both conditions hold.
// Bot protection gate: requires both merchant API key and enabled flag.
// Event reporting additionally requires opt-in analytics.
$protection_active = $this->settings->has_merchant_api_key()
&& $this->settings->is_bot_protection_enabled();

$event_reporting = $protection_active && $this->settings->is_analytics_enabled();

return array(
'status' => 200,
'body' => (string) wp_json_encode(
Expand All @@ -169,7 +171,7 @@ public function build_response( string $authorization_header, string $expected_a
'version' => SUPERTAB_CONNECT_VERSION,
),
'enforcement' => $protection_active ? Plugin::get_enforcement_mode()->value : 'disabled',
'eventReporting' => $protection_active,
'eventReporting' => $event_reporting,
)
),
);
Expand Down
19 changes: 19 additions & 0 deletions templates/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
$supertab_connect_website_urn = $template_data['website_urn'];
$supertab_connect_license_url = $template_data['license_url'];
$supertab_connect_bot_protection_enabled = $template_data['bot_protection_enabled'];
$supertab_connect_analytics_enabled = $template_data['analytics_enabled'];
$supertab_connect_active_paths = $template_data['active_paths'];
$supertab_connect_site_url = $template_data['site_url'];
$supertab_connect_resource_links = $template_data['resource_links'];
Expand Down Expand Up @@ -184,6 +185,24 @@ class="wp-hide-pw hide-if-no-js"
<div id="supertab-active-paths-section"
<?php echo ! $supertab_connect_bot_protection_enabled ? 'style="display:none;"' : ''; // Only hardcoded strings output. ?>
>
<!-- Analytics -->
<fieldset>
<label for="supertab-analytics-enabled">
<input
type="checkbox"
id="supertab-analytics-enabled"
name="analytics_enabled"
value="1"
<?php checked( $supertab_connect_analytics_enabled ); ?>
/>
<?php esc_html_e( 'Enable analytics', 'supertab-connect' ); ?>
</label>
</fieldset>

<p class="description">
<?php esc_html_e( 'Share bot-traffic analytics with Supertab Connect. Events are recorded only for requests classified by CAP. Disabled by default.', 'supertab-connect' ); ?>
</p>

<h4><?php esc_html_e( 'Active Paths', 'supertab-connect' ); ?></h4>

<p class="description">
Expand Down
28 changes: 28 additions & 0 deletions tests/SettingsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,34 @@ public function test_set_bot_protection_enabled_can_disable(): void {
$this->assertFalse( $settings->is_bot_protection_enabled() );
}

public function test_is_analytics_enabled_returns_false_by_default(): void {
$settings = new Settings();
$this->assertFalse( $settings->is_analytics_enabled() );
}

public function test_set_analytics_enabled_stores_value(): void {
$settings = new Settings();
$settings->set_analytics_enabled( true );

$this->assertTrue( $settings->is_analytics_enabled() );
}

public function test_set_analytics_enabled_can_disable(): void {
$settings = new Settings();
$settings->set_analytics_enabled( true );
$settings->set_analytics_enabled( false );

$this->assertFalse( $settings->is_analytics_enabled() );
}

public function test_delete_removes_analytics_setting(): void {
$settings = new Settings();
$settings->set_analytics_enabled( true );
$settings->delete();

$this->assertFalse( $settings->is_analytics_enabled() );
}

public function test_delete_removes_bot_protection_setting(): void {
$settings = new Settings();
$settings->set_bot_protection_enabled( true );
Expand Down
15 changes: 15 additions & 0 deletions tests/StatusHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ static function ( string $token, string $audience ) use ( &$seen ): bool {

public function test_valid_challenge_returns_status_payload(): void {
$this->enable_bot_protection();
$this->settings->set_analytics_enabled( true );

$handler = $this->create_handler( static fn (): bool => true );
$response = $handler->build_response( 'Bearer good-token', 'https://example.com' );
Expand Down Expand Up @@ -178,6 +179,20 @@ public function test_disabled_bot_protection_reports_disabled(): void {
$this->assertFalse( $payload['eventReporting'] );
}

public function test_analytics_opt_in_required_for_event_reporting(): void {
$this->enable_bot_protection();
// Analytics enabled flag left off (opt-in default: disabled).

$handler = $this->create_handler( static fn (): bool => true );
$response = $handler->build_response( 'Bearer good-token', 'https://example.com' );

$payload = json_decode( $response['body'], true );

$this->assertSame( 200, $response['status'] );
$this->assertSame( 'observe', $payload['enforcement'] );
$this->assertFalse( $payload['eventReporting'], 'eventReporting must be false when analytics is not opt-in enabled.' );
}

public function test_missing_credentials_reports_disabled(): void {
$this->settings->set_bot_protection_enabled( true );
// No merchant API key saved.
Expand Down
1 change: 1 addition & 0 deletions uninstall.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
delete_option( 'supertab_connect_merchant_api_key' );
delete_option( 'supertab_connect_website_urn' );
delete_option( 'supertab_connect_bot_protection_enabled' );
delete_option( 'supertab_connect_analytics_enabled' );
delete_option( 'supertab_connect_active_paths' );

// Remove transients.
Expand Down
Loading