From 2d0c8778b1f26ec926c396a1ddf8db838cc471f3 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Tue, 12 May 2026 12:47:44 -0700 Subject: [PATCH 1/4] Connection abilities: register connection status reads via Registrar --- projects/packages/connection/actions.php | 16 + .../changelog/add-connection-abilities | 4 + projects/packages/connection/composer.json | 3 +- .../abilities/class-connection-abilities.php | 313 ++++++++++++ .../abilities/Connection_Abilities_Test.php | 478 ++++++++++++++++++ 5 files changed, 813 insertions(+), 1 deletion(-) create mode 100644 projects/packages/connection/changelog/add-connection-abilities create mode 100644 projects/packages/connection/src/abilities/class-connection-abilities.php create mode 100644 projects/packages/connection/tests/php/abilities/Connection_Abilities_Test.php diff --git a/projects/packages/connection/actions.php b/projects/packages/connection/actions.php index 486d29dad0ef..6d676f1546a7 100644 --- a/projects/packages/connection/actions.php +++ b/projects/packages/connection/actions.php @@ -13,6 +13,22 @@ array( Automattic\Jetpack\Connection\Connection_Assets::class, 'configure' ), 1 ); + + // Register Connection abilities with the WordPress Abilities API once every + // connection-consuming plugin (jetpack, boost, social, search, protect, + // backup, etc.) has had a chance to load. The Registrar gates registration + // on the `jetpack_wp_abilities_enabled` filter (default false), so this is + // a no-op until a site explicitly opts in to abilities. + add_action( + 'plugins_loaded', + static function () { + if ( ! apply_filters( 'jetpack_wp_abilities_enabled', false ) ) { + return; + } + \Automattic\Jetpack\Connection\Abilities\Connection_Abilities::init(); + }, + 20 + ); } else { global $wp_filter; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited diff --git a/projects/packages/connection/changelog/add-connection-abilities b/projects/packages/connection/changelog/add-connection-abilities new file mode 100644 index 000000000000..176f1f111183 --- /dev/null +++ b/projects/packages/connection/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Abilities API: register connection status reads (get-connection-status, get-current-user-connection). diff --git a/projects/packages/connection/composer.json b/projects/packages/connection/composer.json index 6e6967807bf8..27b9aaddbc07 100644 --- a/projects/packages/connection/composer.json +++ b/projects/packages/connection/composer.json @@ -11,7 +11,8 @@ "automattic/jetpack-constants": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", - "automattic/jetpack-redirect": "@dev" + "automattic/jetpack-redirect": "@dev", + "automattic/jetpack-wp-abilities": "@dev" }, "require-dev": { "automattic/jetpack-test-environment": "@dev", diff --git a/projects/packages/connection/src/abilities/class-connection-abilities.php b/projects/packages/connection/src/abilities/class-connection-abilities.php new file mode 100644 index 000000000000..0fd4da098c8a --- /dev/null +++ b/projects/packages/connection/src/abilities/class-connection-abilities.php @@ -0,0 +1,313 @@ + 'Jetpack Connection', + 'description' => __( 'Abilities for inspecting the site\'s and the current user\'s Jetpack connection state.', 'jetpack-connection' ), + ); + } + + /** + * {@inheritDoc} + */ + public static function get_abilities(): array { + return array( + 'jetpack-connection/get-connection-status' => self::spec_get_connection_status(), + 'jetpack-connection/get-current-user-connection' => self::spec_get_current_user_connection(), + ); + } + + /* + --------------------------------------------------------------------- + * Ability specs + * --------------------------------------------------------------------- + */ + + /** + * Spec: jetpack-connection/get-connection-status. + */ + private static function spec_get_connection_status(): array { + return array( + 'label' => __( 'Get Jetpack connection status', 'jetpack-connection' ), + 'description' => __( + 'Return the site-level Jetpack connection state in one zero-argument call. Shape: { site_connected, user_connected, master_user, plan_class, blog_id, registration_url, jetpack_version }. `site_connected` is true when the site has a blog id and a blog token. `user_connected` is true when at least one user has linked their WordPress.com account. `master_user` is the local user id of the connection owner (the user who registered the site), or null if there is no owner. `plan_class` is the slug of the currently active Jetpack/WordPress.com plan (e.g. "free", "personal", "premium", "business"), or null when no plan is known. `blog_id` is the WordPress.com site id, or null when the site has not been registered. `registration_url` is the wp-admin URL the site owner should visit to register the site when `site_connected` is false; null once the site is connected. `jetpack_version` is the running Jetpack plugin version, or null when the constant is not defined (which means Jetpack itself is not loaded — the abilities are still callable through any other connection-consuming plugin). Read-only and idempotent — safe to poll. Call jetpack-connection/get-current-user-connection for the calling user\'s individual link state.', + 'jetpack-connection' + ), + 'input_schema' => array( + 'type' => 'object', + 'properties' => new \stdClass(), + 'additionalProperties' => false, + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'site_connected' => array( 'type' => 'boolean' ), + 'user_connected' => array( 'type' => 'boolean' ), + 'master_user' => array( 'type' => array( 'integer', 'null' ) ), + 'plan_class' => array( 'type' => array( 'string', 'null' ) ), + 'blog_id' => array( 'type' => array( 'integer', 'null' ) ), + 'registration_url' => array( 'type' => array( 'string', 'null' ) ), + 'jetpack_version' => array( 'type' => array( 'string', 'null' ) ), + ), + ), + 'execute_callback' => array( __CLASS__, 'get_connection_status' ), + 'permission_callback' => array( __CLASS__, 'can_view_connection' ), + 'meta' => array( + 'annotations' => array( + 'readonly' => true, + 'destructive' => false, + 'idempotent' => true, + ), + 'show_in_rest' => true, + ), + ); + } + + /** + * Spec: jetpack-connection/get-current-user-connection. + */ + private static function spec_get_current_user_connection(): array { + return array( + 'label' => __( 'Get current user Jetpack connection', 'jetpack-connection' ), + 'description' => __( + 'Return the calling user\'s Jetpack connection state in one zero-argument call. Shape: { id, login, connected, is_master_user, connect_url }. `id` is the local WordPress user id and `login` is their user_login. `connected` is true when the user has linked their WordPress.com account (a user token exists for this id). `is_master_user` is true when this user is the connection owner — the user who originally registered the site to WordPress.com. `connect_url` is the wp-admin URL the user should visit to link their account when `connected` is false; null when the user is already connected. Read-only and idempotent. Fails with jetpack_connection_not_logged_in when called by an anonymous request — the caller must be authenticated. Call jetpack-connection/get-connection-status for the site-wide view.', + 'jetpack-connection' + ), + 'input_schema' => array( + 'type' => 'object', + 'properties' => new \stdClass(), + 'additionalProperties' => false, + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'login' => array( 'type' => 'string' ), + 'connected' => array( 'type' => 'boolean' ), + 'is_master_user' => array( 'type' => 'boolean' ), + 'connect_url' => array( 'type' => array( 'string', 'null' ) ), + ), + ), + 'execute_callback' => array( __CLASS__, 'get_current_user_connection' ), + 'permission_callback' => array( __CLASS__, 'can_view_connection' ), + 'meta' => array( + 'annotations' => array( + 'readonly' => true, + 'destructive' => false, + 'idempotent' => true, + ), + 'show_in_rest' => true, + ), + ); + } + + /* + --------------------------------------------------------------------- + * Permission callbacks + * --------------------------------------------------------------------- + */ + + /** + * Permission check: any authenticated user can read connection state. + * + * Connection state is not sensitive in itself (the same data is exposed + * on the Jetpack admin page and through several existing REST endpoints), + * but anonymous callers have no legitimate need to inspect it, so we + * still require an authenticated request. + * + * @return bool + */ + public static function can_view_connection(): bool { + return is_user_logged_in(); + } + + /* + --------------------------------------------------------------------- + * Execute callbacks + * --------------------------------------------------------------------- + */ + + /** + * Execute: get-connection-status. + * + * @param array|null $input Ignored — zero-arg ability. + * @return array + */ + public static function get_connection_status( $input = null ) { + unset( $input ); + + $manager = self::get_manager(); + $site_connected = (bool) $manager->is_connected(); + $user_connected = (bool) $manager->has_connected_user(); + + $master_user_raw = Jetpack_Options::get_option( 'master_user' ); + $master_user = is_numeric( $master_user_raw ) && (int) $master_user_raw > 0 ? (int) $master_user_raw : null; + + $blog_id_raw = Jetpack_Options::get_option( 'id' ); + $blog_id = is_numeric( $blog_id_raw ) && (int) $blog_id_raw > 0 ? (int) $blog_id_raw : null; + + // The active plan is stored by the Jetpack plugin under `jetpack_active_plan`, + // not as one of the connection package's own option names — read it directly + // from the options table. Absent on connection-only consumers (Boost, Search, + // etc.), in which case `plan_class` is null. + $plan = get_option( 'jetpack_active_plan' ); + $plan_slug = is_array( $plan ) && isset( $plan['product_slug'] ) && is_string( $plan['product_slug'] ) && '' !== $plan['product_slug'] + ? (string) $plan['product_slug'] + : null; + + $jetpack_version_raw = Constants::get_constant( 'JETPACK__VERSION' ); + $jetpack_version = is_string( $jetpack_version_raw ) && '' !== $jetpack_version_raw ? $jetpack_version_raw : null; + + return array( + 'site_connected' => $site_connected, + 'user_connected' => $user_connected, + 'master_user' => $master_user, + 'plan_class' => $plan_slug, + 'blog_id' => $blog_id, + 'registration_url' => $site_connected ? null : self::registration_url(), + 'jetpack_version' => $jetpack_version, + ); + } + + /** + * Execute: get-current-user-connection. + * + * @param array|null $input Ignored — zero-arg ability. + * @return array|WP_Error + */ + public static function get_current_user_connection( $input = null ) { + unset( $input ); + + $user_id = get_current_user_id(); + if ( ! $user_id ) { + return new WP_Error( + self::ERROR_PREFIX . 'not_logged_in', + __( 'You must be logged in to inspect the current user\'s Jetpack connection. Authenticate the request and try again.', 'jetpack-connection' ) + ); + } + + $user = get_userdata( $user_id ); + if ( ! $user ) { + return new WP_Error( + self::ERROR_PREFIX . 'not_logged_in', + __( 'The current user could not be loaded. Authenticate the request and try again.', 'jetpack-connection' ) + ); + } + + $manager = self::get_manager(); + $connected = (bool) $manager->is_user_connected( $user_id ); + $is_master_user = false; + $master_user = Jetpack_Options::get_option( 'master_user' ); + if ( is_numeric( $master_user ) && (int) $master_user === $user_id ) { + $is_master_user = true; + } + + return array( + 'id' => $user_id, + 'login' => (string) $user->user_login, + 'connected' => $connected, + 'is_master_user' => $is_master_user, + 'connect_url' => $connected ? null : self::connect_url(), + ); + } + + /* + --------------------------------------------------------------------- + * Helpers + * --------------------------------------------------------------------- + */ + + /** + * Return a Connection_Manager instance. Filterable for tests so they can + * inject a partial mock without having to seed Jetpack_Options + tokens. + * + * @return Connection_Manager + */ + protected static function get_manager(): Connection_Manager { + /** + * Filters the Connection_Manager instance used by the Connection abilities. + * + * Tests inject a partial mock here; production callers should leave + * the default. The filter callback receives the package-default + * instance and must return a Connection_Manager — non-Manager + * returns are discarded. + * + * @since 8.4.0 + * + * @param Connection_Manager $manager The default instance. + */ + $instance = apply_filters( 'jetpack_connection_abilities_manager', new Connection_Manager() ); + return $instance instanceof Connection_Manager ? $instance : new Connection_Manager(); + } + + /** + * Build the wp-admin URL the site owner should visit to register the + * site to WordPress.com. We deliberately return a stable admin URL (no + * secret generation, no XML-RPC roundtrip) so this read stays side-effect + * free and cheap to poll. The Jetpack admin page handles the actual + * registration handshake from there. + * + * @return string + */ + private static function registration_url(): string { + return admin_url( 'admin.php?page=jetpack' ); + } + + /** + * Build the wp-admin URL the current user should visit to link their + * WordPress.com account. Same rationale as `registration_url()` — stable, + * side-effect free. The Jetpack admin page brokers the actual + * authorization request. + * + * @return string + */ + private static function connect_url(): string { + return admin_url( 'admin.php?page=jetpack' ); + } +} diff --git a/projects/packages/connection/tests/php/abilities/Connection_Abilities_Test.php b/projects/packages/connection/tests/php/abilities/Connection_Abilities_Test.php new file mode 100644 index 000000000000..e44679f24f76 --- /dev/null +++ b/projects/packages/connection/tests/php/abilities/Connection_Abilities_Test.php @@ -0,0 +1,478 @@ +deregister_category_and_abilities(); + } + + WorDBless_Users::init()->clear_all_users(); + WorDBless_Options::init()->clear_options(); + Constants::clear_constants(); + } + + /** + * Remove our category + abilities from the registry so tests don't leak. + */ + private function deregister_category_and_abilities(): void { + if ( function_exists( 'wp_has_ability' ) && function_exists( 'wp_unregister_ability' ) ) { + foreach ( array_keys( Connection_Abilities::get_abilities() ) as $slug ) { + if ( wp_has_ability( $slug ) ) { + wp_unregister_ability( $slug ); + } + } + } + if ( function_exists( 'wp_has_ability_category' ) && function_exists( 'wp_unregister_ability_category' ) ) { + if ( wp_has_ability_category( Connection_Abilities::CATEGORY_SLUG ) ) { + wp_unregister_ability_category( Connection_Abilities::CATEGORY_SLUG ); + } + } + } + + /** + * Push an entry on `$wp_current_filter` so `doing_action()` reports true + * for the duration of the callback, then pop it back off. + * + * @param string $action Action name to simulate. + * @param callable $fn Callable to run while the action is "firing". + */ + private function with_simulated_action( string $action, callable $fn ): void { + global $wp_current_filter; + $wp_current_filter[] = $action; + try { + $fn(); + } finally { + for ( $i = count( $wp_current_filter ) - 1; $i >= 0; $i-- ) { + if ( $wp_current_filter[ $i ] === $action ) { + array_splice( $wp_current_filter, $i, 1 ); + break; + } + } + } + } + + /** + * Inject a stub Connection_Manager via the filter the abilities use. + * + * @param array $methods Map of method name => return value. + * @return Connection_Manager + */ + private function inject_manager_mock( array $methods ): Connection_Manager { + $stub = $this->createStub( Connection_Manager::class ); + foreach ( $methods as $method => $return ) { + $stub->method( $method )->willReturn( $return ); + } + add_filter( + 'jetpack_connection_abilities_manager', + static function () use ( $stub ) { + return $stub; + } + ); + return $stub; + } + + // --- Abstract getters --------------------------------------------------. + + /** + * The category slug must be the namespaced "jetpack-connection". + */ + public function test_category_slug_is_jetpack_connection(): void { + $this->assertSame( 'jetpack-connection', Connection_Abilities::get_category_slug() ); + } + + /** + * The category definition must include both a label and description. + */ + public function test_category_definition_has_label_and_description(): void { + $def = Connection_Abilities::get_category_definition(); + $this->assertArrayHasKey( 'label', $def ); + $this->assertArrayHasKey( 'description', $def ); + $this->assertNotSame( '', $def['label'] ); + $this->assertNotSame( '', $def['description'] ); + } + + /** + * The abilities map must list exactly the two read abilities we ship in this PR. + */ + public function test_abilities_map_lists_the_two_read_abilities(): void { + $abilities = Connection_Abilities::get_abilities(); + $this->assertSame( + array( + 'jetpack-connection/get-connection-status', + 'jetpack-connection/get-current-user-connection', + ), + array_keys( $abilities ) + ); + } + + /** + * Specs must not set their own `category` — Registrar auto-injects it. + */ + public function test_no_spec_sets_category_explicitly(): void { + foreach ( Connection_Abilities::get_abilities() as $slug => $spec ) { + $this->assertArrayNotHasKey( + 'category', + $spec, + "Ability {$slug} should not set its own category — Registrar injects it." + ); + } + } + + /** + * Every spec must declare execute, permission, and read-only annotations. + */ + public function test_every_spec_declares_annotations_permission_and_execute(): void { + foreach ( Connection_Abilities::get_abilities() as $slug => $spec ) { + $this->assertArrayHasKey( 'execute_callback', $spec, "Ability {$slug} missing execute_callback" ); + $this->assertIsCallable( $spec['execute_callback'], "Ability {$slug} execute_callback not callable" ); + $this->assertArrayHasKey( 'permission_callback', $spec, "Ability {$slug} missing permission_callback" ); + $this->assertIsCallable( $spec['permission_callback'], "Ability {$slug} permission_callback not callable" ); + $this->assertArrayHasKey( 'meta', $spec ); + $this->assertArrayHasKey( 'annotations', $spec['meta'] ); + $this->assertTrue( $spec['meta']['annotations']['readonly'], "Ability {$slug} must be readonly" ); + $this->assertFalse( $spec['meta']['annotations']['destructive'], "Ability {$slug} must not be destructive" ); + $this->assertTrue( $spec['meta']['annotations']['idempotent'], "Ability {$slug} must be idempotent" ); + } + } + + // --- Registrar wiring --------------------------------------------------. + + /** + * When the `jetpack_wp_abilities_enabled` filter returns false, `init()` + * must register nothing (no category, no abilities, no hooks). + */ + public function test_init_registers_nothing_when_gate_filter_is_false(): void { + remove_filter( 'jetpack_wp_abilities_enabled', '__return_true' ); + add_filter( 'jetpack_wp_abilities_enabled', '__return_false' ); + + Connection_Abilities::init(); + + $this->assertFalse( + has_action( 'wp_abilities_api_categories_init', array( Connection_Abilities::class, 'register_category' ) ), + 'Category registration must not be hooked when the gate filter is false.' + ); + $this->assertFalse( + has_action( 'wp_abilities_api_init', array( Connection_Abilities::class, 'register_abilities' ) ), + 'Ability registration must not be hooked when the gate filter is false.' + ); + + remove_filter( 'jetpack_wp_abilities_enabled', '__return_false' ); + } + + /** + * When the gate filter returns true and the Abilities API lifecycle + * actions have not yet fired, `init()` must hook both of them. + */ + public function test_init_hooks_both_lifecycle_actions_when_gate_filter_is_true(): void { + Connection_Abilities::init(); + + $this->assertNotFalse( + has_action( 'wp_abilities_api_categories_init', array( Connection_Abilities::class, 'register_category' ) ), + 'Category registration must be hooked when the gate filter is true.' + ); + $this->assertNotFalse( + has_action( 'wp_abilities_api_init', array( Connection_Abilities::class, 'register_abilities' ) ), + 'Ability registration must be hooked when the gate filter is true.' + ); + } + + /** + * `jetpack_wp_abilities_should_register` short-circuits per-ability + * registration when the filter returns false. + */ + public function test_register_abilities_skips_when_should_register_filter_returns_false(): void { + // Deny every slug. + add_filter( 'jetpack_wp_abilities_should_register', '__return_false' ); + + $registered = array(); + $capture = static function ( $slug ) use ( &$registered ) { + $registered[] = $slug; + return false; + }; + + $this->with_simulated_action( + 'wp_abilities_api_init', + function () use ( $capture ) { + // Stub the registration function so we can observe what would be registered. + if ( ! function_exists( 'wp_register_ability' ) ) { + $this->markTestSkipped( 'wp_register_ability not available in this WordPress build.' ); + } + // We cannot redefine the function; instead, rely on the should_register + // filter to short-circuit BEFORE wp_register_ability is called and + // assert via the filter that no ability slugs got through. + add_filter( + 'jetpack_wp_abilities_should_register', + $capture, + 11, + 3 + ); + + Connection_Abilities::register_abilities(); + } + ); + + // The capture filter ran for each ability, but `should_register` returned + // false on the earlier (priority-10) filter, so no slug should have been + // passed through. We rely on no abilities being registered as the assertion. + foreach ( array_keys( Connection_Abilities::get_abilities() ) as $slug ) { + $this->assertFalse( + function_exists( 'wp_has_ability' ) && wp_has_ability( $slug ), + "Ability {$slug} must NOT register when should_register returns false." + ); + } + + remove_filter( 'jetpack_wp_abilities_should_register', '__return_false' ); + remove_filter( 'jetpack_wp_abilities_should_register', $capture, 11 ); + } + + /** + * The Registrar must auto-inject this subclass's `CATEGORY_SLUG` on every + * registered ability whose spec omits `category`. + */ + public function test_category_auto_injection_on_registered_abilities(): void { + if ( ! function_exists( 'wp_register_ability' ) || ! function_exists( 'wp_get_ability' ) ) { + $this->markTestSkipped( 'Abilities API not available in this WordPress build.' ); + } + + $this->with_simulated_action( + 'wp_abilities_api_categories_init', + function () { + Connection_Abilities::register_category(); + } + ); + + $this->with_simulated_action( + 'wp_abilities_api_init', + function () { + Connection_Abilities::register_abilities(); + } + ); + + foreach ( array_keys( Connection_Abilities::get_abilities() ) as $slug ) { + $ability = wp_get_ability( $slug ); + $this->assertNotNull( $ability, "Ability {$slug} should have been registered." ); + $this->assertSame( + Connection_Abilities::CATEGORY_SLUG, + $ability->get_category(), + "Registrar must auto-inject the category slug on {$slug}." + ); + } + } + + // --- Permission callbacks ----------------------------------------------. + + /** + * `can_view_connection()` must deny anonymous (logged-out) callers. + */ + public function test_can_view_connection_denies_anonymous_user(): void { + wp_set_current_user( 0 ); + $this->assertFalse( Connection_Abilities::can_view_connection() ); + } + + /** + * Any authenticated user (even a subscriber) may read connection state. + */ + public function test_can_view_connection_allows_logged_in_user(): void { + $user_id = wp_insert_user( + array( + 'user_login' => 'connection_abilities_subscriber', + 'user_pass' => 'pw', + 'role' => 'subscriber', + ) + ); + wp_set_current_user( (int) $user_id ); + + $this->assertTrue( Connection_Abilities::can_view_connection() ); + } + + // --- get-connection-status execute -------------------------------------. + + /** + * With no options set and a disconnected Manager stub, the read must + * return the documented zero state and a non-empty `registration_url`. + */ + public function test_get_connection_status_returns_disconnected_state_by_default(): void { + $this->inject_manager_mock( + array( + 'is_connected' => false, + 'has_connected_user' => false, + ) + ); + + $out = Connection_Abilities::get_connection_status(); + + $this->assertIsArray( $out ); + $this->assertFalse( $out['site_connected'] ); + $this->assertFalse( $out['user_connected'] ); + $this->assertNull( $out['master_user'] ); + $this->assertNull( $out['plan_class'] ); + $this->assertNull( $out['blog_id'] ); + $this->assertIsString( $out['registration_url'] ); + $this->assertNotSame( '', $out['registration_url'] ); + $this->assertNull( $out['jetpack_version'] ); + } + + /** + * With a connected Manager stub and the full set of options populated, + * the read must surface blog_id, master_user, plan_class and version, + * and return a null `registration_url` (the site is already connected). + */ + public function test_get_connection_status_reports_connected_state_with_options_set(): void { + $this->inject_manager_mock( + array( + 'is_connected' => true, + 'has_connected_user' => true, + ) + ); + Jetpack_Options::update_option( 'id', 12345 ); + Jetpack_Options::update_option( 'master_user', 42 ); + update_option( 'jetpack_active_plan', array( 'product_slug' => 'jetpack_premium' ) ); + Constants::set_constant( 'JETPACK__VERSION', '99.9' ); + + $out = Connection_Abilities::get_connection_status(); + + $this->assertTrue( $out['site_connected'] ); + $this->assertTrue( $out['user_connected'] ); + $this->assertSame( 42, $out['master_user'] ); + $this->assertSame( 12345, $out['blog_id'] ); + $this->assertSame( 'jetpack_premium', $out['plan_class'] ); + $this->assertNull( $out['registration_url'], 'registration_url must be null once the site is connected.' ); + $this->assertSame( '99.9', $out['jetpack_version'] ); + } + + // --- get-current-user-connection execute -------------------------------. + + /** + * Anonymous callers must receive the documented `jetpack_connection_not_logged_in` + * WP_Error so agents can steer the next call (e.g. authenticate first). + */ + public function test_get_current_user_connection_returns_wp_error_when_anonymous(): void { + wp_set_current_user( 0 ); + + $out = Connection_Abilities::get_current_user_connection(); + + $this->assertInstanceOf( \WP_Error::class, $out ); + $this->assertSame( 'jetpack_connection_not_logged_in', $out->get_error_code() ); + } + + /** + * A connected user who is also the connection owner must come back with + * `connected=true`, `is_master_user=true`, and a null `connect_url`. + */ + public function test_get_current_user_connection_reports_connected_master_user(): void { + $user_id = wp_insert_user( + array( + 'user_login' => 'connection_abilities_owner', + 'user_pass' => 'pw', + 'role' => 'administrator', + ) + ); + wp_set_current_user( (int) $user_id ); + Jetpack_Options::update_option( 'master_user', (int) $user_id ); + + $this->inject_manager_mock( + array( + 'is_user_connected' => true, + ) + ); + + $out = Connection_Abilities::get_current_user_connection(); + + $this->assertIsArray( $out ); + $this->assertSame( (int) $user_id, $out['id'] ); + $this->assertSame( 'connection_abilities_owner', $out['login'] ); + $this->assertTrue( $out['connected'] ); + $this->assertTrue( $out['is_master_user'] ); + $this->assertNull( $out['connect_url'], 'connect_url must be null once the user is connected.' ); + } + + /** + * A non-owner, unconnected user must come back with `connected=false`, + * `is_master_user=false`, and a non-empty `connect_url` so the agent + * can hand the URL back to the human user. + */ + public function test_get_current_user_connection_reports_non_owner_unconnected(): void { + $user_id = wp_insert_user( + array( + 'user_login' => 'connection_abilities_visitor', + 'user_pass' => 'pw', + 'role' => 'subscriber', + ) + ); + wp_set_current_user( (int) $user_id ); + Jetpack_Options::update_option( 'master_user', 99 ); + + $this->inject_manager_mock( + array( + 'is_user_connected' => false, + ) + ); + + $out = Connection_Abilities::get_current_user_connection(); + + $this->assertIsArray( $out ); + $this->assertSame( (int) $user_id, $out['id'] ); + $this->assertSame( 'connection_abilities_visitor', $out['login'] ); + $this->assertFalse( $out['connected'] ); + $this->assertFalse( $out['is_master_user'] ); + $this->assertIsString( $out['connect_url'] ); + $this->assertNotSame( '', $out['connect_url'] ); + } +} From da1022ebdc9f65e21ce42908f32409db1369d00c Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Fri, 15 May 2026 12:19:04 -0700 Subject: [PATCH 2/4] Connection abilities: single get-connection-status, jetpack-plugin-scoped loading --- projects/packages/connection/actions.php | 16 --- .../changelog/add-connection-abilities | 2 +- projects/packages/connection/composer.json | 4 +- .../abilities/class-connection-abilities.php | 106 +----------------- .../abilities/Connection_Abilities_Test.php | 84 +------------- .../changelog/add-connection-abilities | 4 + projects/plugins/jetpack/class.jetpack.php | 5 + 7 files changed, 19 insertions(+), 202 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/add-connection-abilities diff --git a/projects/packages/connection/actions.php b/projects/packages/connection/actions.php index 6d676f1546a7..486d29dad0ef 100644 --- a/projects/packages/connection/actions.php +++ b/projects/packages/connection/actions.php @@ -13,22 +13,6 @@ array( Automattic\Jetpack\Connection\Connection_Assets::class, 'configure' ), 1 ); - - // Register Connection abilities with the WordPress Abilities API once every - // connection-consuming plugin (jetpack, boost, social, search, protect, - // backup, etc.) has had a chance to load. The Registrar gates registration - // on the `jetpack_wp_abilities_enabled` filter (default false), so this is - // a no-op until a site explicitly opts in to abilities. - add_action( - 'plugins_loaded', - static function () { - if ( ! apply_filters( 'jetpack_wp_abilities_enabled', false ) ) { - return; - } - \Automattic\Jetpack\Connection\Abilities\Connection_Abilities::init(); - }, - 20 - ); } else { global $wp_filter; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited diff --git a/projects/packages/connection/changelog/add-connection-abilities b/projects/packages/connection/changelog/add-connection-abilities index 176f1f111183..40953b0e7efc 100644 --- a/projects/packages/connection/changelog/add-connection-abilities +++ b/projects/packages/connection/changelog/add-connection-abilities @@ -1,4 +1,4 @@ Significance: minor Type: added -Abilities API: register connection status reads (get-connection-status, get-current-user-connection). +Abilities API: add jetpack-connection/get-connection-status read ability. diff --git a/projects/packages/connection/composer.json b/projects/packages/connection/composer.json index 27b9aaddbc07..4f5da2919b11 100644 --- a/projects/packages/connection/composer.json +++ b/projects/packages/connection/composer.json @@ -11,8 +11,7 @@ "automattic/jetpack-constants": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", - "automattic/jetpack-redirect": "@dev", - "automattic/jetpack-wp-abilities": "@dev" + "automattic/jetpack-redirect": "@dev" }, "require-dev": { "automattic/jetpack-test-environment": "@dev", @@ -20,6 +19,7 @@ "brain/monkey": "^2.6.2", "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev" }, "suggest": { diff --git a/projects/packages/connection/src/abilities/class-connection-abilities.php b/projects/packages/connection/src/abilities/class-connection-abilities.php index 0fd4da098c8a..7c36cc1d05b7 100644 --- a/projects/packages/connection/src/abilities/class-connection-abilities.php +++ b/projects/packages/connection/src/abilities/class-connection-abilities.php @@ -17,15 +17,13 @@ use Automattic\Jetpack\Constants; use Automattic\Jetpack\WP_Abilities\Registrar; use Jetpack_Options; -use WP_Error; /** * Registers Jetpack Connection abilities with the WordPress Abilities API. * - * Exposes two read-only abilities — one for site-level connection state, one - * for the calling user's connection state — so AI agents can answer - * "is this site connected?" and "is the current user connected?" without - * having to reverse-engineer Jetpack_Options keys. + * Exposes a single read-only ability for site-level connection state so AI + * agents can answer "is this site connected?" without having to + * reverse-engineer Jetpack_Options keys. * * Writes (registering a new site, disconnecting a user, transferring * ownership) are deliberately deferred to a follow-up PR. @@ -33,7 +31,6 @@ class Connection_Abilities extends Registrar { const CATEGORY_SLUG = 'jetpack-connection'; - const ERROR_PREFIX = 'jetpack_connection_'; /** * {@inheritDoc} @@ -49,7 +46,7 @@ public static function get_category_definition(): array { return array( // "Jetpack" is a product name and should not be translated. 'label' => 'Jetpack Connection', - 'description' => __( 'Abilities for inspecting the site\'s and the current user\'s Jetpack connection state.', 'jetpack-connection' ), + 'description' => __( 'Abilities for inspecting the site\'s Jetpack connection state.', 'jetpack-connection' ), ); } @@ -59,7 +56,6 @@ public static function get_category_definition(): array { public static function get_abilities(): array { return array( 'jetpack-connection/get-connection-status' => self::spec_get_connection_status(), - 'jetpack-connection/get-current-user-connection' => self::spec_get_current_user_connection(), ); } @@ -76,7 +72,7 @@ private static function spec_get_connection_status(): array { return array( 'label' => __( 'Get Jetpack connection status', 'jetpack-connection' ), 'description' => __( - 'Return the site-level Jetpack connection state in one zero-argument call. Shape: { site_connected, user_connected, master_user, plan_class, blog_id, registration_url, jetpack_version }. `site_connected` is true when the site has a blog id and a blog token. `user_connected` is true when at least one user has linked their WordPress.com account. `master_user` is the local user id of the connection owner (the user who registered the site), or null if there is no owner. `plan_class` is the slug of the currently active Jetpack/WordPress.com plan (e.g. "free", "personal", "premium", "business"), or null when no plan is known. `blog_id` is the WordPress.com site id, or null when the site has not been registered. `registration_url` is the wp-admin URL the site owner should visit to register the site when `site_connected` is false; null once the site is connected. `jetpack_version` is the running Jetpack plugin version, or null when the constant is not defined (which means Jetpack itself is not loaded — the abilities are still callable through any other connection-consuming plugin). Read-only and idempotent — safe to poll. Call jetpack-connection/get-current-user-connection for the calling user\'s individual link state.', + 'Return the site-level Jetpack connection state in one zero-argument call. Shape: { site_connected, user_connected, master_user, plan_class, blog_id, registration_url, jetpack_version }. `site_connected` is true when the site has a blog id and a blog token. `user_connected` is true when at least one user has linked their WordPress.com account. `master_user` is the local user id of the connection owner (the user who registered the site), or null if there is no owner. `plan_class` is the slug of the currently active Jetpack/WordPress.com plan (e.g. "free", "personal", "premium", "business"), or null when no plan is known. `blog_id` is the WordPress.com site id, or null when the site has not been registered. `registration_url` is the wp-admin URL the site owner should visit to register the site when `site_connected` is false; null once the site is connected. `jetpack_version` is the running Jetpack plugin version, or null when the constant is not defined. Read-only and idempotent — safe to poll.', 'jetpack-connection' ), 'input_schema' => array( @@ -109,44 +105,6 @@ private static function spec_get_connection_status(): array { ); } - /** - * Spec: jetpack-connection/get-current-user-connection. - */ - private static function spec_get_current_user_connection(): array { - return array( - 'label' => __( 'Get current user Jetpack connection', 'jetpack-connection' ), - 'description' => __( - 'Return the calling user\'s Jetpack connection state in one zero-argument call. Shape: { id, login, connected, is_master_user, connect_url }. `id` is the local WordPress user id and `login` is their user_login. `connected` is true when the user has linked their WordPress.com account (a user token exists for this id). `is_master_user` is true when this user is the connection owner — the user who originally registered the site to WordPress.com. `connect_url` is the wp-admin URL the user should visit to link their account when `connected` is false; null when the user is already connected. Read-only and idempotent. Fails with jetpack_connection_not_logged_in when called by an anonymous request — the caller must be authenticated. Call jetpack-connection/get-connection-status for the site-wide view.', - 'jetpack-connection' - ), - 'input_schema' => array( - 'type' => 'object', - 'properties' => new \stdClass(), - 'additionalProperties' => false, - ), - 'output_schema' => array( - 'type' => 'object', - 'properties' => array( - 'id' => array( 'type' => 'integer' ), - 'login' => array( 'type' => 'string' ), - 'connected' => array( 'type' => 'boolean' ), - 'is_master_user' => array( 'type' => 'boolean' ), - 'connect_url' => array( 'type' => array( 'string', 'null' ) ), - ), - ), - 'execute_callback' => array( __CLASS__, 'get_current_user_connection' ), - 'permission_callback' => array( __CLASS__, 'can_view_connection' ), - 'meta' => array( - 'annotations' => array( - 'readonly' => true, - 'destructive' => false, - 'idempotent' => true, - ), - 'show_in_rest' => true, - ), - ); - } - /* --------------------------------------------------------------------- * Permission callbacks @@ -215,48 +173,6 @@ public static function get_connection_status( $input = null ) { ); } - /** - * Execute: get-current-user-connection. - * - * @param array|null $input Ignored — zero-arg ability. - * @return array|WP_Error - */ - public static function get_current_user_connection( $input = null ) { - unset( $input ); - - $user_id = get_current_user_id(); - if ( ! $user_id ) { - return new WP_Error( - self::ERROR_PREFIX . 'not_logged_in', - __( 'You must be logged in to inspect the current user\'s Jetpack connection. Authenticate the request and try again.', 'jetpack-connection' ) - ); - } - - $user = get_userdata( $user_id ); - if ( ! $user ) { - return new WP_Error( - self::ERROR_PREFIX . 'not_logged_in', - __( 'The current user could not be loaded. Authenticate the request and try again.', 'jetpack-connection' ) - ); - } - - $manager = self::get_manager(); - $connected = (bool) $manager->is_user_connected( $user_id ); - $is_master_user = false; - $master_user = Jetpack_Options::get_option( 'master_user' ); - if ( is_numeric( $master_user ) && (int) $master_user === $user_id ) { - $is_master_user = true; - } - - return array( - 'id' => $user_id, - 'login' => (string) $user->user_login, - 'connected' => $connected, - 'is_master_user' => $is_master_user, - 'connect_url' => $connected ? null : self::connect_url(), - ); - } - /* --------------------------------------------------------------------- * Helpers @@ -298,16 +214,4 @@ protected static function get_manager(): Connection_Manager { private static function registration_url(): string { return admin_url( 'admin.php?page=jetpack' ); } - - /** - * Build the wp-admin URL the current user should visit to link their - * WordPress.com account. Same rationale as `registration_url()` — stable, - * side-effect free. The Jetpack admin page brokers the actual - * authorization request. - * - * @return string - */ - private static function connect_url(): string { - return admin_url( 'admin.php?page=jetpack' ); - } } diff --git a/projects/packages/connection/tests/php/abilities/Connection_Abilities_Test.php b/projects/packages/connection/tests/php/abilities/Connection_Abilities_Test.php index e44679f24f76..e3f277798c9e 100644 --- a/projects/packages/connection/tests/php/abilities/Connection_Abilities_Test.php +++ b/projects/packages/connection/tests/php/abilities/Connection_Abilities_Test.php @@ -149,14 +149,13 @@ public function test_category_definition_has_label_and_description(): void { } /** - * The abilities map must list exactly the two read abilities we ship in this PR. + * The abilities map must list exactly the single read ability we ship in this PR. */ - public function test_abilities_map_lists_the_two_read_abilities(): void { + public function test_abilities_map_lists_the_single_read_ability(): void { $abilities = Connection_Abilities::get_abilities(); $this->assertSame( array( 'jetpack-connection/get-connection-status', - 'jetpack-connection/get-current-user-connection', ), array_keys( $abilities ) ); @@ -396,83 +395,4 @@ public function test_get_connection_status_reports_connected_state_with_options_ $this->assertNull( $out['registration_url'], 'registration_url must be null once the site is connected.' ); $this->assertSame( '99.9', $out['jetpack_version'] ); } - - // --- get-current-user-connection execute -------------------------------. - - /** - * Anonymous callers must receive the documented `jetpack_connection_not_logged_in` - * WP_Error so agents can steer the next call (e.g. authenticate first). - */ - public function test_get_current_user_connection_returns_wp_error_when_anonymous(): void { - wp_set_current_user( 0 ); - - $out = Connection_Abilities::get_current_user_connection(); - - $this->assertInstanceOf( \WP_Error::class, $out ); - $this->assertSame( 'jetpack_connection_not_logged_in', $out->get_error_code() ); - } - - /** - * A connected user who is also the connection owner must come back with - * `connected=true`, `is_master_user=true`, and a null `connect_url`. - */ - public function test_get_current_user_connection_reports_connected_master_user(): void { - $user_id = wp_insert_user( - array( - 'user_login' => 'connection_abilities_owner', - 'user_pass' => 'pw', - 'role' => 'administrator', - ) - ); - wp_set_current_user( (int) $user_id ); - Jetpack_Options::update_option( 'master_user', (int) $user_id ); - - $this->inject_manager_mock( - array( - 'is_user_connected' => true, - ) - ); - - $out = Connection_Abilities::get_current_user_connection(); - - $this->assertIsArray( $out ); - $this->assertSame( (int) $user_id, $out['id'] ); - $this->assertSame( 'connection_abilities_owner', $out['login'] ); - $this->assertTrue( $out['connected'] ); - $this->assertTrue( $out['is_master_user'] ); - $this->assertNull( $out['connect_url'], 'connect_url must be null once the user is connected.' ); - } - - /** - * A non-owner, unconnected user must come back with `connected=false`, - * `is_master_user=false`, and a non-empty `connect_url` so the agent - * can hand the URL back to the human user. - */ - public function test_get_current_user_connection_reports_non_owner_unconnected(): void { - $user_id = wp_insert_user( - array( - 'user_login' => 'connection_abilities_visitor', - 'user_pass' => 'pw', - 'role' => 'subscriber', - ) - ); - wp_set_current_user( (int) $user_id ); - Jetpack_Options::update_option( 'master_user', 99 ); - - $this->inject_manager_mock( - array( - 'is_user_connected' => false, - ) - ); - - $out = Connection_Abilities::get_current_user_connection(); - - $this->assertIsArray( $out ); - $this->assertSame( (int) $user_id, $out['id'] ); - $this->assertSame( 'connection_abilities_visitor', $out['login'] ); - $this->assertFalse( $out['connected'] ); - $this->assertFalse( $out['is_master_user'] ); - $this->assertIsString( $out['connect_url'] ); - $this->assertNotSame( '', $out['connect_url'] ); - } } diff --git a/projects/plugins/jetpack/changelog/add-connection-abilities b/projects/plugins/jetpack/changelog/add-connection-abilities new file mode 100644 index 000000000000..d4b79b2a6afb --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Abilities API: register the jetpack-connection/get-connection-status read ability from the Jetpack plugin. diff --git a/projects/plugins/jetpack/class.jetpack.php b/projects/plugins/jetpack/class.jetpack.php index 72eaa5ae5d64..377cb4767b14 100644 --- a/projects/plugins/jetpack/class.jetpack.php +++ b/projects/plugins/jetpack/class.jetpack.php @@ -758,6 +758,11 @@ function () { // Register Jetpack module management abilities (WordPress Abilities API, WP 6.9+). \Automattic\Jetpack\Plugin\Abilities\Modules_Abilities::init(); + + // Register Connection abilities (WordPress Abilities API, WP 6.9+). Scoped to the + // Jetpack plugin for now: the Connection package no longer auto-wires these, so + // connection-only consumers (Boost, Protect, Search, etc.) do not register them yet. + \Automattic\Jetpack\Connection\Abilities\Connection_Abilities::init(); } /** From 1ad29b5f12893e51ec198cc1b103f4feef050b0a Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Fri, 15 May 2026 12:25:55 -0700 Subject: [PATCH 3/4] Use enhancement changelog type for Jetpack plugin (changelogger validity) --- projects/plugins/jetpack/changelog/add-connection-abilities | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/changelog/add-connection-abilities b/projects/plugins/jetpack/changelog/add-connection-abilities index d4b79b2a6afb..5a1442318bdf 100644 --- a/projects/plugins/jetpack/changelog/add-connection-abilities +++ b/projects/plugins/jetpack/changelog/add-connection-abilities @@ -1,4 +1,4 @@ Significance: minor -Type: added +Type: enhancement Abilities API: register the jetpack-connection/get-connection-status read ability from the Jetpack plugin. From ccd332260ab5b57694e643d5e95873bb3464213d Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Fri, 15 May 2026 12:44:39 -0700 Subject: [PATCH 4/4] Regenerate plugin composer.lock files for connection package require-dev change --- .../changelog/add-connection-abilities | 4 ++++ projects/plugins/automattic-for-agencies-client/composer.lock | 3 ++- projects/plugins/backup/changelog/add-connection-abilities | 4 ++++ projects/plugins/backup/composer.lock | 3 ++- projects/plugins/boost/changelog/add-connection-abilities | 4 ++++ projects/plugins/boost/composer.lock | 3 ++- .../changelog/add-connection-abilities | 4 ++++ projects/plugins/classic-theme-helper-plugin/composer.lock | 3 ++- projects/plugins/inspect/changelog/add-connection-abilities | 4 ++++ projects/plugins/inspect/composer.lock | 3 ++- projects/plugins/jetpack/composer.lock | 3 ++- .../mu-wpcom-plugin/changelog/add-connection-abilities | 4 ++++ projects/plugins/mu-wpcom-plugin/composer.lock | 3 ++- .../paypal-payment-buttons/changelog/add-connection-abilities | 4 ++++ projects/plugins/paypal-payment-buttons/composer.lock | 3 ++- projects/plugins/protect/changelog/add-connection-abilities | 4 ++++ projects/plugins/protect/composer.lock | 3 ++- projects/plugins/search/changelog/add-connection-abilities | 4 ++++ projects/plugins/search/composer.lock | 3 ++- projects/plugins/social/changelog/add-connection-abilities | 4 ++++ projects/plugins/social/composer.lock | 3 ++- .../plugins/starter-plugin/changelog/add-connection-abilities | 4 ++++ projects/plugins/starter-plugin/composer.lock | 3 ++- .../plugins/videopress/changelog/add-connection-abilities | 4 ++++ projects/plugins/videopress/composer.lock | 3 ++- .../plugins/wpcloud-sso/changelog/add-connection-abilities | 4 ++++ projects/plugins/wpcloud-sso/composer.lock | 3 ++- projects/plugins/wpcomsh/changelog/add-connection-abilities | 4 ++++ projects/plugins/wpcomsh/composer.lock | 3 ++- 29 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 projects/plugins/automattic-for-agencies-client/changelog/add-connection-abilities create mode 100644 projects/plugins/backup/changelog/add-connection-abilities create mode 100644 projects/plugins/boost/changelog/add-connection-abilities create mode 100644 projects/plugins/classic-theme-helper-plugin/changelog/add-connection-abilities create mode 100644 projects/plugins/inspect/changelog/add-connection-abilities create mode 100644 projects/plugins/mu-wpcom-plugin/changelog/add-connection-abilities create mode 100644 projects/plugins/paypal-payment-buttons/changelog/add-connection-abilities create mode 100644 projects/plugins/protect/changelog/add-connection-abilities create mode 100644 projects/plugins/search/changelog/add-connection-abilities create mode 100644 projects/plugins/social/changelog/add-connection-abilities create mode 100644 projects/plugins/starter-plugin/changelog/add-connection-abilities create mode 100644 projects/plugins/videopress/changelog/add-connection-abilities create mode 100644 projects/plugins/wpcloud-sso/changelog/add-connection-abilities create mode 100644 projects/plugins/wpcomsh/changelog/add-connection-abilities diff --git a/projects/plugins/automattic-for-agencies-client/changelog/add-connection-abilities b/projects/plugins/automattic-for-agencies-client/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/automattic-for-agencies-client/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/automattic-for-agencies-client/composer.lock b/projects/plugins/automattic-for-agencies-client/composer.lock index 4c4f8b2f3514..64f48ec0b2cb 100644 --- a/projects/plugins/automattic-for-agencies-client/composer.lock +++ b/projects/plugins/automattic-for-agencies-client/composer.lock @@ -411,7 +411,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -427,6 +427,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/backup/changelog/add-connection-abilities b/projects/plugins/backup/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/backup/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/backup/composer.lock b/projects/plugins/backup/composer.lock index bac7cfbd08c8..d825c82459e0 100644 --- a/projects/plugins/backup/composer.lock +++ b/projects/plugins/backup/composer.lock @@ -669,7 +669,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -685,6 +685,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/boost/changelog/add-connection-abilities b/projects/plugins/boost/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/boost/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/boost/composer.lock b/projects/plugins/boost/composer.lock index 5178c4cc4ce4..387f7859280d 100644 --- a/projects/plugins/boost/composer.lock +++ b/projects/plugins/boost/composer.lock @@ -530,7 +530,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -546,6 +546,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/classic-theme-helper-plugin/changelog/add-connection-abilities b/projects/plugins/classic-theme-helper-plugin/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/classic-theme-helper-plugin/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/classic-theme-helper-plugin/composer.lock b/projects/plugins/classic-theme-helper-plugin/composer.lock index 4439c82deed3..b323d8cd36f9 100644 --- a/projects/plugins/classic-theme-helper-plugin/composer.lock +++ b/projects/plugins/classic-theme-helper-plugin/composer.lock @@ -596,7 +596,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -612,6 +612,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/inspect/changelog/add-connection-abilities b/projects/plugins/inspect/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/inspect/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/inspect/composer.lock b/projects/plugins/inspect/composer.lock index 9ba95d795cc0..656a1992e80a 100644 --- a/projects/plugins/inspect/composer.lock +++ b/projects/plugins/inspect/composer.lock @@ -411,7 +411,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -427,6 +427,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock index 540c4862a0a3..30467f4b43ef 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -1097,7 +1097,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -1113,6 +1113,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/mu-wpcom-plugin/changelog/add-connection-abilities b/projects/plugins/mu-wpcom-plugin/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/mu-wpcom-plugin/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/mu-wpcom-plugin/composer.lock b/projects/plugins/mu-wpcom-plugin/composer.lock index c9860b768083..1cf92402e5c1 100644 --- a/projects/plugins/mu-wpcom-plugin/composer.lock +++ b/projects/plugins/mu-wpcom-plugin/composer.lock @@ -566,7 +566,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -582,6 +582,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/paypal-payment-buttons/changelog/add-connection-abilities b/projects/plugins/paypal-payment-buttons/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/paypal-payment-buttons/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/paypal-payment-buttons/composer.lock b/projects/plugins/paypal-payment-buttons/composer.lock index f65127687cd5..7af9f3aa980c 100644 --- a/projects/plugins/paypal-payment-buttons/composer.lock +++ b/projects/plugins/paypal-payment-buttons/composer.lock @@ -396,7 +396,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -412,6 +412,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/protect/changelog/add-connection-abilities b/projects/plugins/protect/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/protect/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/protect/composer.lock b/projects/plugins/protect/composer.lock index a312235d7f42..1948d547cf42 100644 --- a/projects/plugins/protect/composer.lock +++ b/projects/plugins/protect/composer.lock @@ -652,7 +652,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -668,6 +668,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/search/changelog/add-connection-abilities b/projects/plugins/search/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/search/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/search/composer.lock b/projects/plugins/search/composer.lock index 16f37ff4241a..219b1a3089f9 100644 --- a/projects/plugins/search/composer.lock +++ b/projects/plugins/search/composer.lock @@ -530,7 +530,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -546,6 +546,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/social/changelog/add-connection-abilities b/projects/plugins/social/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/social/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/social/composer.lock b/projects/plugins/social/composer.lock index 4ba09870934c..ad2ec520a3af 100644 --- a/projects/plugins/social/composer.lock +++ b/projects/plugins/social/composer.lock @@ -593,7 +593,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -609,6 +609,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/starter-plugin/changelog/add-connection-abilities b/projects/plugins/starter-plugin/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/starter-plugin/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/starter-plugin/composer.lock b/projects/plugins/starter-plugin/composer.lock index 92c8e5b6ee4f..c1c91f298aea 100644 --- a/projects/plugins/starter-plugin/composer.lock +++ b/projects/plugins/starter-plugin/composer.lock @@ -530,7 +530,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -546,6 +546,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/videopress/changelog/add-connection-abilities b/projects/plugins/videopress/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/videopress/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/videopress/composer.lock b/projects/plugins/videopress/composer.lock index 9f04068508b8..2f1f6b3f0b58 100644 --- a/projects/plugins/videopress/composer.lock +++ b/projects/plugins/videopress/composer.lock @@ -530,7 +530,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -546,6 +546,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/wpcloud-sso/changelog/add-connection-abilities b/projects/plugins/wpcloud-sso/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/wpcloud-sso/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/wpcloud-sso/composer.lock b/projects/plugins/wpcloud-sso/composer.lock index 8dabfd6b2082..950372fdb0be 100644 --- a/projects/plugins/wpcloud-sso/composer.lock +++ b/projects/plugins/wpcloud-sso/composer.lock @@ -411,7 +411,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -427,6 +427,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0" diff --git a/projects/plugins/wpcomsh/changelog/add-connection-abilities b/projects/plugins/wpcomsh/changelog/add-connection-abilities new file mode 100644 index 000000000000..c47cb18e8299 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/add-connection-abilities @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/wpcomsh/composer.lock b/projects/plugins/wpcomsh/composer.lock index 3a5499ef29e2..51e3158d8fae 100644 --- a/projects/plugins/wpcomsh/composer.lock +++ b/projects/plugins/wpcomsh/composer.lock @@ -773,7 +773,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "37cff8191289fa89742688ce970f25f702cb6ba8" + "reference": "30612a3e27b0391db94d602d42cd765a27985abf" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -789,6 +789,7 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-sync": "@dev", "automattic/jetpack-test-environment": "@dev", + "automattic/jetpack-wp-abilities": "@dev", "automattic/phpunit-select-config": "@dev", "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^4.0.0"