PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.7
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.7
1.3.7 1.3.6 1.3.5 1.3.4 1.3.3 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 All 33 releases
xspeed / includes / modules / AIPrivacy / AIPrivacyModule.php

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

128 lines 4.8 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' => __( 'Choose whether AI features may use visitor data, with or without Pro.', 'xspeed' ),
52 'group' => 'ai-agents',
53 );
54 }
55
56 /**
57 * @inheritDoc
58 *
59 * OFF here would mean asking for less consent than the user configured, which
60 * is the one direction this profile must never move.
61 */
62 public function conflict_safe_exempt(): array {
63 return array( 'gdpr_consent_required' );
64 }
65
66 public function settings_schema(): array {
67 return array(
68 'gdpr_consent_required' => array(
69 'type' => 'bool',
70 'default' => true,
71 'label' => __( 'Ask visitors for consent', 'xspeed' ),
72 'description' => __( 'When on, AI features only record a visitor\'s data after they accept the consent banner. Turn it off only where the law does not require consent, as GDPR does.', 'xspeed' ),
73 ),
74 'consent_banner_text' => array(
75 'type' => 'string',
76 'default' => 'We collect anonymized navigation and performance data to make this site faster. Accept to help us optimize your experience.',
77 'label' => __( 'Consent banner text', 'xspeed' ),
78 'description' => __( 'The text of the consent banner. Say plainly what you collect, such as page visits and speed data, and why.', 'xspeed' ),
79 ),
80 );
81 }
82
83 /**
84 * The canonical "may we collect data right now?" decision. Pro AI
85 * features should ALWAYS go through the `xspeed_ai_can_collect_data`
86 * filter (which this method backs by default) rather than calling
87 * this directly — so a site owner can override the decision with
88 * their own filter callback (e.g., a custom CMP integration).
89 */
90 public static function can_collect( bool $default = true ): bool {
91 $opts = Settings_Manager::get( self::SLUG );
92
93 // Privacy mode off → fall back to whatever the caller proposed.
94 // Most callers proposed true; an upstream filter that already
95 // returned false gets preserved.
96 if ( empty( $opts['gdpr_consent_required'] ) ) {
97 return $default;
98 }
99
100 // Privacy mode on → require explicit consent cookie. Absence of
101 // the cookie means the visitor hasn't accepted the banner yet
102 // (or rejected it) and we record nothing.
103 return ! empty( $_COOKIE[ self::CONSENT_COOKIE ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
104 }
105
106 public function boot(): void {
107 add_filter( 'xspeed_ai_can_collect_data', array( self::class, 'can_collect' ), 10, 1 );
108 }
109
110 public function cli_commands(): array {
111 return array(
112 array(
113 'name' => 'xspeed ai-privacy',
114 'callback' => array( $this, 'cli_handler' ),
115 'shortdesc' => 'Show whether AI data collection is allowed for the current request context (CLI = no cookie).',
116 '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.',
117 'synopsis' => array(),
118 ),
119 );
120 }
121
122 public function cli_handler( array $args, array $assoc ): void {
123 $opts = $this->get_settings();
124 \WP_CLI::log( sprintf( '%-26s %s', 'gdpr_consent_required', ! empty( $opts['gdpr_consent_required'] ) ? 'on' : 'off' ) );
125 \WP_CLI::log( sprintf( '%-26s %s', 'can_collect (no cookie)', self::can_collect() ? 'yes' : 'no' ) );
126 }
127 }
128