PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.2
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.2
1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.2.0 All 28 releases
xspeed / includes / modules / AIPrivacy / AIPrivacyModule.php

AIPrivacyModule.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.3.2, at includes/modules/AIPrivacy/AIPrivacyModule.php

127 lines 5.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * AI Privacy module — the GDPR off-switch FEATURES.md §AI row 6
4 * mandates ship in Free, regardless of whether the user has Pro
5 * installed.
6 *
7 * Why this lives in Free even though every AI feature is Pro: privacy
8 * is a fundamental user right, not a paid feature. If a site activates
9 * xSpeed Pro and starts collecting navigation patterns / Web Vitals
10 * for AI analysis, the GDPR off-switch must already be present and
11 * configured — not gated behind a license. So this module ships with
12 * Free and exposes a public filter that every Pro AI feature checks
13 * before recording any data point.
14 *
15 * Pro consumes this via:
16 * if ( ! apply_filters( 'xspeed_ai_can_collect_data', true ) ) return;
17 *
18 * When Pro is not installed, the module still works (the toggle just
19 * has no consumer) — same pattern as our other Free/Pro contracts.
20 *
21 * @package XSpeed
22 */
23
24 declare(strict_types=1);
25
26 namespace XSpeed\Modules\AIPrivacy;
27
28 defined( 'ABSPATH' ) || exit;
29
30 use XSpeed\Module;
31 use XSpeed\Settings_Manager;
32
33 final class AIPrivacyModule extends Module {
34
35 public const SLUG = 'ai-privacy';
36 public const TIER = self::TIER_FREE;
37 public const VERSION = '1.0.0';
38
39 /**
40 * Cookie name a consent banner sets when the visitor accepts AI
41 * data collection. Banner ships separately (UI work) — defining the
42 * key here means Pro modules and any third-party banner agree on a
43 * single source of truth.
44 */
45 public const CONSENT_COOKIE = 'xspeed_ai_consent';
46
47 public function ui_metadata(): array {
48 return array(
49 'label' => __( 'AI Privacy', 'xspeed' ),
50 'icon' => 'Shield',
51 'description' => __( 'Govern whether AI-powered features (Pro) may collect data from your visitors. The off-switch is here in Free because privacy is a fundamental right, not a paid feature.', 'xspeed' ),
52 );
53 }
54
55 /**
56 * @inheritDoc
57 *
58 * OFF here would mean asking for less consent than the user configured, which
59 * is the one direction this profile must never move.
60 */
61 public function conflict_safe_exempt(): array {
62 return array( 'gdpr_consent_required' );
63 }
64
65 public function settings_schema(): array {
66 return array(
67 'gdpr_consent_required' => array(
68 'type' => 'bool',
69 'default' => true,
70 'label' => __( 'Require consent before AI data collection', 'xspeed' ),
71 'description' => __( 'When ON, AI-powered features only record data after a visitor accepts the consent banner. When OFF, they collect from every visitor — only legal in regions without GDPR-style consent rules. The setting applies even if Pro is not installed (so a later Pro upgrade respects whichever choice you made).', 'xspeed' ),
72 ),
73 'consent_banner_text' => array(
74 'type' => 'string',
75 'default' => 'We collect anonymized navigation and performance data to make this site faster. Accept to help us optimize your experience.',
76 'label' => __( 'Consent banner text', 'xspeed' ),
77 'description' => __( 'Shown in the cookie consent banner. Keep it factual — what you collect (page navigation, performance metrics) and why.', 'xspeed' ),
78 ),
79 );
80 }
81
82 /**
83 * The canonical "may we collect data right now?" decision. Pro AI
84 * features should ALWAYS go through the `xspeed_ai_can_collect_data`
85 * filter (which this method backs by default) rather than calling
86 * this directly — so a site owner can override the decision with
87 * their own filter callback (e.g., a custom CMP integration).
88 */
89 public static function can_collect( bool $default = true ): bool {
90 $opts = Settings_Manager::get( self::SLUG );
91
92 // Privacy mode off → fall back to whatever the caller proposed.
93 // Most callers proposed true; an upstream filter that already
94 // returned false gets preserved.
95 if ( empty( $opts['gdpr_consent_required'] ) ) {
96 return $default;
97 }
98
99 // Privacy mode on → require explicit consent cookie. Absence of
100 // the cookie means the visitor hasn't accepted the banner yet
101 // (or rejected it) and we record nothing.
102 return ! empty( $_COOKIE[ self::CONSENT_COOKIE ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
103 }
104
105 public function boot(): void {
106 add_filter( 'xspeed_ai_can_collect_data', array( self::class, 'can_collect' ), 10, 1 );
107 }
108
109 public function cli_commands(): array {
110 return array(
111 array(
112 'name' => 'xspeed ai-privacy',
113 'callback' => array( $this, 'cli_handler' ),
114 'shortdesc' => 'Show whether AI data collection is allowed for the current request context (CLI = no cookie).',
115 'ai_hint' => 'May xSpeed send this site\'s data to an AI provider? Check before invoking any AI-backed feature — it reports the user\'s opt-in state, not a setting to change casually.',
116 'synopsis' => array(),
117 ),
118 );
119 }
120
121 public function cli_handler( array $args, array $assoc ): void {
122 $opts = $this->get_settings();
123 \WP_CLI::log( sprintf( '%-26s %s', 'gdpr_consent_required', ! empty( $opts['gdpr_consent_required'] ) ? 'on' : 'off' ) );
124 \WP_CLI::log( sprintf( '%-26s %s', 'can_collect (no cookie)', self::can_collect() ? 'yes' : 'no' ) );
125 }
126 }
127