PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / _inc / lib / class-jetpack-ai-settings.php
jetpack / _inc / lib Last commit date
admin-pages 3 days ago core-api 3 days ago debugger 1 month ago markdown 8 months ago class-jetpack-ai-helper.php 3 days ago class-jetpack-ai-settings.php 3 days ago class-jetpack-application-password-extras.php 5 months ago class-jetpack-blog-stats-helper.php 8 months ago class-jetpack-currencies.php 2 years ago class-jetpack-instagram-gallery-helper.php 2 months ago class-jetpack-mapbox-helper.php 3 years ago class-jetpack-newsletter-category-helper.php 8 months ago class-jetpack-podcast-feed-locator.php 3 days ago class-jetpack-podcast-helper.php 3 days ago class-jetpack-recommendations.php 2 months ago class-jetpack-spinner.php 3 months ago class-jetpack-top-posts-helper.php 3 days ago class.color.php 2 years ago class.core-rest-api-endpoints.php 3 days ago class.jetpack-automatic-install-skin.php 2 years ago class.jetpack-iframe-embed.php 3 years ago class.jetpack-password-checker.php 8 months ago class.jetpack-search-performance-logger.php 4 years ago class.media-extractor.php 3 months ago class.media-summary.php 1 month ago class.media.php 8 months ago components.php 1 month ago debugger.php 2 months ago icalendar-reader.php 2 months ago markdown.php 8 months ago plans.php 4 years ago plugins.php 4 years ago tonesque.php 8 months ago widgets.php 4 months ago
class-jetpack-ai-settings.php
328 lines
1 <?php
2 /**
3 * Jetpack AI feature settings.
4 *
5 * Central registry for the Jetpack AI master switch and per-feature toggles,
6 * implementing the layered AI gate contract:
7 *
8 * 1. the host allows AI — WP_AI_SUPPORT, via wp_supports_ai()
9 * 2. the plan includes AI — connection + plan checks (owned by each feature)
10 * 3. AI is on for the whole site — the jetpack_ai_enabled option (master switch)
11 * 4. the feature's own switch — per-feature options surfaced on the AI settings page
12 *
13 * Gates 1 and 3 are enforced by is_ai_enabled(), which plugin load points call
14 * instead of applying `jetpack_ai_enabled` directly: the gates AND in after the
15 * filter chain, so no later-priority callback can override them. The gates also
16 * ride the filter itself for package consumers that cannot reference this class.
17 * Gate 4 options are registered here and consulted at each feature's registration
18 * or enqueue point — a disabled feature must stop loading, not just hide.
19 *
20 * @package automattic/jetpack
21 */
22
23 use Automattic\Jetpack\Modules;
24 use Automattic\Jetpack\Status\Host;
25
26 if ( ! defined( 'ABSPATH' ) ) {
27 exit( 0 );
28 }
29
30 // All consumers require this canonical file once. A class_exists() guard here
31 // would be true on the first load because PHP registers unconditional classes
32 // before executing the file, returning before the self-initialization below.
33
34 /**
35 * Registers the Jetpack AI master switch and per-feature toggle options, and
36 * enforces the host (WP_AI_SUPPORT) and master gates on the AI filters.
37 */
38 class Jetpack_AI_Settings {
39
40 /**
41 * Master switch option. Named after the pre-existing `jetpack_ai_enabled`
42 * filter it backs, following the `reader_chat` option/filter precedent.
43 *
44 * @var string
45 */
46 const MASTER_OPTION = 'jetpack_ai_enabled';
47
48 /**
49 * Slug of the `ai` module that acts as the site-wide master switch off
50 * WordPress.com Simple (self-hosted and Atomic), where modules run.
51 *
52 * @var string
53 */
54 const AI_MODULE = 'ai';
55
56 /**
57 * Feature key => option name for every toggle on the AI settings page.
58 *
59 * `seo_enhancer` and `ai_search` reuse options owned by the SEO/Search
60 * surfaces; the rest are registered by this class.
61 *
62 * @var array
63 */
64 const FEATURE_OPTIONS = array(
65 'writing_assistant' => 'jetpack_ai_writing_assistant_enabled',
66 'image_editor' => 'jetpack_ai_image_editor_enabled',
67 'feature_clip' => 'jetpack_ai_feature_clip_enabled',
68 'seo_enhancer' => 'ai_seo_enhancer_enabled',
69 'ai_search' => 'jetpack_search_ai_answers_enabled',
70 );
71
72 /**
73 * Option defaults. The reused SEO/Search options keep their established
74 * opt-in defaults; the new per-feature toggles default to on.
75 *
76 * @var array
77 */
78 const FEATURE_DEFAULTS = array(
79 'writing_assistant' => true,
80 'image_editor' => true,
81 'feature_clip' => true,
82 'seo_enhancer' => false,
83 'ai_search' => false,
84 );
85
86 /**
87 * Feature keys whose options this class registers and syncs (the reused
88 * SEO/Search options are registered by their owning surfaces).
89 *
90 * @var array
91 */
92 const OWNED_FEATURES = array( 'writing_assistant', 'image_editor', 'feature_clip' );
93
94 /**
95 * Whether init() has already run.
96 *
97 * @var bool
98 */
99 private static $initialized = false;
100
101 /**
102 * Hook everything up. Must run on every request (front-end, editor, REST):
103 * the filters attached here gate feature loading.
104 *
105 * @return void
106 */
107 public static function init() {
108 if ( self::$initialized ) {
109 return;
110 }
111 self::$initialized = true;
112
113 add_action( 'init', array( __CLASS__, 'register_settings' ) );
114 add_filter( 'jetpack_sync_options_whitelist', array( __CLASS__, 'add_sync_options_whitelist' ) );
115
116 // Plugin call sites use is_ai_enabled(), which applies gates 1 (host) and
117 // 3 (master) after the filter chain. This in-chain registration stays for
118 // the package consumers that cannot reference this plugin class
119 // (external-media, my-jetpack): there the gates keep their pre-helper,
120 // priority-10 behavior.
121 add_filter( 'jetpack_ai_enabled', array( __CLASS__, 'apply_master_gates' ) );
122
123 // AI surfaces that do not flow through jetpack_ai_enabled.
124 add_filter( 'jetpack_search_ai_answers_enabled', array( __CLASS__, 'apply_master_gates' ) );
125 add_filter( 'jetpack_ai_sidebar_enabled', array( __CLASS__, 'apply_master_gates' ) );
126 }
127
128 /**
129 * Register the master switch and the per-feature options this class owns.
130 *
131 * @return void
132 */
133 public static function register_settings() {
134 $show_in_rest = ! ( new Host() )->is_wpcom_simple();
135
136 $options = array(
137 self::MASTER_OPTION => __( 'Whether Jetpack AI is enabled on this site.', 'jetpack' ),
138 self::FEATURE_OPTIONS['writing_assistant'] => __( 'Whether the Jetpack AI writing assistant is enabled.', 'jetpack' ),
139 self::FEATURE_OPTIONS['image_editor'] => __( 'Whether the Jetpack AI image editor is enabled.', 'jetpack' ),
140 self::FEATURE_OPTIONS['feature_clip'] => __( 'Whether Jetpack AI video clip generation is enabled.', 'jetpack' ),
141 );
142
143 foreach ( $options as $option => $description ) {
144 register_setting(
145 'general',
146 $option,
147 array(
148 'type' => 'boolean',
149 'description' => $description,
150 'sanitize_callback' => 'rest_sanitize_boolean',
151 // The master option is never exposed over core settings REST:
152 // off-Simple the `ai` module is the master and the option only
153 // holds the legacy pre-module opt-out (a core-REST write would
154 // clobber it without touching the real master); on Simple the
155 // dedicated feature-settings endpoint is the writable surface.
156 'show_in_rest' => self::MASTER_OPTION === $option ? false : $show_in_rest,
157 'default' => true,
158 )
159 );
160 }
161 }
162
163 /**
164 * Add the per-feature AI options to Jetpack Sync's option whitelist.
165 *
166 * Atomic and self-hosted sites write these locally; syncing them lets
167 * WordPress.com (Calypso, the multi-site dashboard) read toggle state and
168 * is the prerequisite for mirroring the dashboard AI toggle later.
169 *
170 * The master switch is deliberately absent: off-Simple the `ai` module is
171 * the master, and module state already reaches WordPress.com through the
172 * synced `active_modules` callable — syncing the option as well would add
173 * a second, driftable source of truth for the same bit.
174 *
175 * @param array $options Option names allowed to sync.
176 * @return array Updated option names.
177 */
178 public static function add_sync_options_whitelist( $options ) {
179 $options = (array) $options;
180 foreach ( self::OWNED_FEATURES as $feature ) {
181 $options[] = self::FEATURE_OPTIONS[ $feature ];
182 }
183 return array_values( array_unique( $options ) );
184 }
185
186 /**
187 * Fold the host (gate 1) and master switch (gate 3) into an AI enabled filter.
188 *
189 * Restrictive-only on purpose: `jetpack_ai_enabled` is applied with different
190 * defaults at different call sites (Jetpack_AI_Helper passes false on plain
191 * self-hosted sites; the editor extension hub passes true), so this callback
192 * may only ever turn a yes into a no — returning the option value directly
193 * would flip self-hosted defaults to enabled.
194 *
195 * @param bool $enabled The value the call site computed so far.
196 * @return bool
197 */
198 public static function apply_master_gates( $enabled ) {
199 return (bool) $enabled && self::host_allows_ai() && self::is_master_enabled();
200 }
201
202 /**
203 * Whether Jetpack AI is enabled on this site, with the host (gate 1) and
204 * master switch (gate 3) as final, non-overridable checks.
205 *
206 * Runs the `jetpack_ai_enabled` filter with the call site's default — the
207 * chain may still enable or disable as before — then ANDs the host and
208 * master gates after it, so no late-priority callback can turn AI back on
209 * once either gate says no. Plugin call sites use this helper; the filter
210 * registration in init() stays for the package consumers that cannot
211 * reference this class.
212 *
213 * @since 16.2
214 *
215 * @param bool $default The call site's computed default. Defaults differ
216 * between call sites — see apply_master_gates().
217 * @return bool
218 */
219 public static function is_ai_enabled( $default = true ) {
220 /**
221 * Filter whether the AI features are enabled in the Jetpack plugin.
222 *
223 * @since 11.8
224 *
225 * @param bool $default Are AI features enabled? The default varies by call site.
226 */
227 $enabled = (bool) apply_filters( 'jetpack_ai_enabled', $default );
228
229 return $enabled && self::host_allows_ai() && self::is_master_enabled();
230 }
231
232 /**
233 * Gate 1: whether the host allows AI at all.
234 *
235 * Defers to core's wp_supports_ai(), which is backed by the WP_AI_SUPPORT
236 * constant and its own filter. This is a server-owner decision: when it is
237 * off, no AI settings should be shown and no upgrade should ever be offered.
238 *
239 * @return bool
240 */
241 public static function host_allows_ai() {
242 return wp_supports_ai();
243 }
244
245 /**
246 * Gate 3: whether the site-wide AI master switch is on.
247 *
248 * The master lives in a different place depending on the platform. On
249 * WordPress.com Simple no Jetpack modules run, so the `jetpack_ai_enabled`
250 * option is the master. Everywhere else (self-hosted and Atomic) the `ai`
251 * module is the real master switch, toggled through the standard Jetpack
252 * module machinery; there the option only carries the legacy pre-module
253 * value the one-time opt-out migration reads, and is never written again.
254 *
255 * @return bool
256 */
257 public static function is_master_enabled() {
258 if ( ( new Host() )->is_wpcom_simple() ) {
259 return (bool) get_option( self::MASTER_OPTION, true );
260 }
261
262 return ( new Modules() )->is_active( self::AI_MODULE );
263 }
264
265 /**
266 * Set the site-wide AI master switch, writing to whichever store backs it on
267 * this platform (see {@see self::is_master_enabled()}).
268 *
269 * On WordPress.com Simple the `jetpack_ai_enabled` option is the master, so
270 * we update it. Off-Simple the `ai` module is the master, so we activate or
271 * deactivate it. The no-exit / no-redirect arguments are passed to
272 * `Modules::update_status()` so this is safe to call outside a request that
273 * expects to terminate (REST handlers, migrations, CLI).
274 *
275 * @param bool $enabled Whether AI should be enabled site-wide.
276 * @return void
277 */
278 public static function set_master_enabled( bool $enabled ) {
279 if ( ( new Host() )->is_wpcom_simple() ) {
280 update_option( self::MASTER_OPTION, $enabled );
281 return;
282 }
283
284 // The module alone is the master off-Simple. The option is deliberately NOT
285 // written here: WordPress.com derives the master state from the synced
286 // `active_modules` callable, and the stored option must keep its legacy
287 // pre-module value so Jetpack::reconcile_ai_master_optout() can read an
288 // explicit opt-out on sites that upgrade later.
289 ( new Modules() )->update_status( self::AI_MODULE, $enabled, false, false );
290 }
291
292 /**
293 * Gate 4: whether an individual feature's switch is on.
294 *
295 * Checks only the feature's own toggle — callers remain responsible for the
296 * outer gates (most already consult the jetpack_ai_enabled filter, which
297 * carries host + master). Only the matching option is read: a code-level
298 * override belongs on the option itself, through core's own option filters.
299 *
300 * @param string $feature Feature key (see FEATURE_OPTIONS).
301 * @return bool False for unknown features.
302 */
303 public static function is_feature_enabled( $feature ) {
304 if ( ! isset( self::FEATURE_OPTIONS[ $feature ] ) ) {
305 return false;
306 }
307
308 // WordPress.com Simple has no per-feature toggles. It keeps the existing
309 // wp.com settings contract, so the features Jetpack owns stay on there and
310 // the host and master gates remain the only controls. The reused SEO and
311 // Search options are deliberately excluded: they have their own settings
312 // surfaces on Simple and must keep honoring their stored values.
313 if ( in_array( $feature, self::OWNED_FEATURES, true ) && ( new Host() )->is_wpcom_simple() ) {
314 return true;
315 }
316
317 $option = self::FEATURE_OPTIONS[ $feature ];
318
319 return (bool) get_option( $option, self::FEATURE_DEFAULTS[ $feature ] );
320 }
321 }
322
323 // Self-initialize on load. The consuming AI extension files require this file
324 // directly (__DIR__-relative) because on WordPress.com Simple the plugin's
325 // extension files load through wpcom's own loader and load-jetpack.php never
326 // runs. This keeps filter registration identical in both bootstrap paths.
327 Jetpack_AI_Settings::init();
328