| @@ -19,11 +19,9 @@ | ||
| 19 | 19 | * |
| 20 | 20 | * @package automattic/jetpack |
| 21 | 21 | */ |
| 22 | 22 | |
| 23 | -use Automattic\Jetpack\Connection\Manager; | |
| 24 | 23 | use Automattic\Jetpack\Modules; |
| 25 | -use Automattic\Jetpack\Status; | |
| 26 | 24 | use Automattic\Jetpack\Status\Host; |
| 27 | 25 | |
| 28 | 26 | if ( ! defined( 'ABSPATH' ) ) { |
| 29 | 27 | exit( 0 ); |
| @@ -55,32 +53,8 @@ | ||
| 55 | 53 | */ |
| 56 | 54 | const AI_MODULE = 'ai'; |
| 57 | 55 | |
| 58 | 56 | /** |
| 59 | - * The `jetpack_ai_enabled` route custom code can take to hold AI off, as | |
| 60 | - * reported by {@see self::get_master_forced_off_route()}. Each route names a | |
| 61 | - * different hook, so each needs its own documentation link. | |
| 62 | - * | |
| 63 | - * @var string | |
| 64 | - */ | |
| 65 | - const FORCED_OFF_ROUTE_FILTER = 'filter'; | |
| 66 | - | |
| 67 | - /** | |
| 68 | - * The module-filter route; see {@see self::FORCED_OFF_ROUTE_FILTER}. | |
| 69 | - * | |
| 70 | - * @var string | |
| 71 | - */ | |
| 72 | - const FORCED_OFF_ROUTE_MODULES = 'modules'; | |
| 73 | - | |
| 74 | - /** | |
| 75 | - * The filter route on VIP, which documents this filter as its own supported | |
| 76 | - * off switch and so owns the page to send the reader to. | |
| 77 | - * | |
| 78 | - * @var string | |
| 79 | - */ | |
| 80 | - const FORCED_OFF_ROUTE_FILTER_VIP = 'filter-vip'; | |
| 81 | - | |
| 82 | - /** | |
| 83 | 57 | * Feature key => option name for every toggle on the AI settings page. |
| 84 | 58 | * |
| 85 | 59 | * `ai_search` reuses an option owned by the Search surface; the rest are |
| 86 | 60 | * registered by this class. The automatic-generation option is deliberately |
| @@ -125,16 +99,8 @@ | ||
| 125 | 99 | */ |
| 126 | 100 | private static $initialized = false; |
| 127 | 101 | |
| 128 | 102 | /** |
| 129 | - * Whether apply_master_gates() should step aside; see | |
| 130 | - * {@see self::get_master_forced_off_route()}. | |
| 131 | - * | |
| 132 | - * @var bool | |
| 133 | - */ | |
| 134 | - private static $probing_third_party = false; | |
| 135 | - | |
| 136 | - /** | |
| 137 | 103 | * Hook everything up. Must run on every request (front-end, editor, REST): |
| 138 | 104 | * the filters attached here gate feature loading. |
| 139 | 105 | * |
| 140 | 106 | * @return void |
| @@ -235,14 +201,8 @@ | ||
| 235 | 201 | * @param bool $enabled The value the call site computed so far. |
| 236 | 202 | * @return bool |
| 237 | 203 | */ |
| 238 | 204 | public static function apply_master_gates( $enabled ) { |
| 239 | - // Stand aside while get_master_forced_off_route() asks the chain what | |
| 240 | - // everyone else says; our own verdict would drown theirs out. | |
| 241 | - if ( self::$probing_third_party ) { | |
| 242 | - return (bool) $enabled; | |
| 243 | - } | |
| 244 | - | |
| 245 | 205 | return (bool) $enabled |
| 246 | 206 | && self::host_allows_ai() |
| 247 | 207 | && ( ! self::should_enforce_ai_controls() || self::is_master_enabled() ); |
| 248 | 208 | } |
| @@ -327,79 +287,8 @@ | ||
| 327 | 287 | return ( new Modules() )->is_active( self::AI_MODULE ); |
| 328 | 288 | } |
| 329 | 289 | |
| 330 | 290 | /** |
| 331 | - * Whether the site's WordPress.com connection can carry AI. Offline mode | |
| 332 | - * counts as disconnected even while the site holds its tokens, and Simple | |
| 333 | - * sites are always connected. | |
| 334 | - * | |
| 335 | - * @return bool | |
| 336 | - */ | |
| 337 | - public static function site_is_connected() { | |
| 338 | - return ( new Host() )->is_wpcom_simple() | |
| 339 | - || ( ( new Manager( 'jetpack' ) )->has_connected_owner() | |
| 340 | - && ! ( new Status() )->is_offline_mode() ); | |
| 341 | - } | |
| 342 | - | |
| 343 | - /** | |
| 344 | - * Whether the current user's own account is connected. Surfaces that proxy | |
| 345 | - * as the requesting user need this on top of {@see self::site_is_connected()}. | |
| 346 | - * | |
| 347 | - * @return bool | |
| 348 | - */ | |
| 349 | - public static function user_is_connected() { | |
| 350 | - return ( new Host() )->is_wpcom_simple() | |
| 351 | - || ( new Manager( 'jetpack' ) )->is_user_connected(); | |
| 352 | - } | |
| 353 | - | |
| 354 | - /** | |
| 355 | - * Which hook custom code used to hold AI off, so the notice can link to the | |
| 356 | - * matching documentation. Always empty on WordPress.com Simple, which runs | |
| 357 | - * no modules. | |
| 358 | - * | |
| 359 | - * @return string One of the FORCED_OFF_ROUTE_* constants, or '' when nothing | |
| 360 | - * holds AI off. | |
| 361 | - */ | |
| 362 | - public static function get_master_forced_off_route() { | |
| 363 | - $host = new Host(); | |
| 364 | - | |
| 365 | - if ( $host->is_wpcom_simple() ) { | |
| 366 | - return ''; | |
| 367 | - } | |
| 368 | - | |
| 369 | - // Ask the chain with our own gates stood down, so a deactivated module | |
| 370 | - // cannot mask a filter that would keep AI off however the module is set. | |
| 371 | - $third_party_off = false; | |
| 372 | - self::$probing_third_party = true; | |
| 373 | - try { | |
| 374 | - $third_party_off = ! apply_filters( 'jetpack_ai_enabled', true ); | |
| 375 | - } finally { | |
| 376 | - self::$probing_third_party = false; | |
| 377 | - } | |
| 378 | - | |
| 379 | - if ( $third_party_off ) { | |
| 380 | - return $host->is_vip_site() | |
| 381 | - ? self::FORCED_OFF_ROUTE_FILTER_VIP | |
| 382 | - : self::FORCED_OFF_ROUTE_FILTER; | |
| 383 | - } | |
| 384 | - | |
| 385 | - if ( self::is_master_enabled() ) { | |
| 386 | - return ''; | |
| 387 | - } | |
| 388 | - | |
| 389 | - // Removed from the available list by `jetpack_get_available_modules`. | |
| 390 | - if ( ! in_array( self::AI_MODULE, ( new Modules() )->get_available(), true ) ) { | |
| 391 | - return self::FORCED_OFF_ROUTE_MODULES; | |
| 392 | - } | |
| 393 | - | |
| 394 | - // Forced off through `option_jetpack_active_modules` or `jetpack_active_modules`. | |
| 395 | - $overridden = class_exists( 'Jetpack_Modules_Overrides' ) | |
| 396 | - && 'inactive' === Jetpack_Modules_Overrides::instance()->get_module_override( self::AI_MODULE ); | |
| 397 | - | |
| 398 | - return $overridden ? self::FORCED_OFF_ROUTE_MODULES : ''; | |
| 399 | - } | |
| 400 | - | |
| 401 | - /** | |
| 402 | 291 | * Set the site-wide AI master switch, writing to whichever store backs it on |
| 403 | 292 | * this platform (see {@see self::is_master_enabled()}). |
| 404 | 293 | * |
| 405 | 294 | * On WordPress.com Simple the `jetpack_ai_enabled` option is the master, so |
| @@ -458,10 +347,16 @@ | ||
| 458 | 347 | return (bool) get_option( $option, self::FEATURE_DEFAULTS[ $feature ] ); |
| 459 | 348 | } |
| 460 | 349 | |
| 461 | 350 | /** |
| 462 | - * Whether AI SEO is enabled after its feature filter and the site-wide AI checks. | |
| 351 | + * Whether the AI SEO feature (metadata generation, manual and automatic) | |
| 352 | + * is effectively enabled: its own toggle (gate 4) through the filter, with | |
| 353 | + * the host and master gates ANDed after the chain so no late-priority | |
| 354 | + * callback can turn the feature back on — same finality as is_ai_enabled(). | |
| 463 | 355 | * |
| 356 | + * Not {@see self::is_feature_enabled()} with `ai_seo`, which is the stored | |
| 357 | + * toggle alone. This is the one load points and payloads should read. | |
| 358 | + * | |
| 464 | 359 | * @since 16.2 |
| 465 | 360 | * |
| 466 | 361 | * @return bool |
| 467 | 362 | */ |
| @@ -474,9 +369,9 @@ | ||
| 474 | 369 | * @param bool $enabled Whether the SEO feature toggle is on. |
| 475 | 370 | */ |
| 476 | 371 | $enabled = (bool) apply_filters( 'jetpack_ai_seo_enabled', self::is_feature_enabled( 'ai_seo' ) ); |
| 477 | 372 | |
| 478 | - return $enabled && self::is_ai_enabled(); | |
| 373 | + return self::apply_master_gates( $enabled ); | |
| 479 | 374 | } |
| 480 | 375 | } |
| 481 | 376 | |
| 482 | 377 | // Self-initialize on load. The consuming AI extension files require this file |