Skip to content

Commit 167b080

Browse files
enejbEnej Bajgoric
andauthored
Connection: register WP Abilities API reads (#48735)
* Connection abilities: register connection status reads via Registrar * Connection abilities: single get-connection-status, jetpack-plugin-scoped loading * Use enhancement changelog type for Jetpack plugin (changelogger validity) * Regenerate plugin composer.lock files for connection package require-dev change * Connection abilities: use jetpack category, drop plan_class, surface connection package version Move the connection ability under the top-level `jetpack` category (`jetpack/get-connection-status`) so future Jetpack abilities share one namespace. Drop `plan_class` from the output — plan info isn't a connection concern. Replace `jetpack_version` with `connection_version` sourced from `Package_Version::PACKAGE_VERSION`, which is what actually ships this ability. * Connection abilities: gate get-connection-status on jetpack_admin_page cap * Connection abilities: rename site_connected output field to site_registered * Connection abilities: prefer WP 7.0 Connectors screen for registration URL * Abilities: align shared jetpack category description across registrars * Connection abilities: fix Connectors registration URL and detection --------- Co-authored-by: Enej Bajgoric <enej.bajgoric@a8c.com>
1 parent f7e7a18 commit 167b080

41 files changed

Lines changed: 778 additions & 16 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Abilities API: add jetpack/get-connection-status read ability.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: changed
3+
4+
Connection abilities: align the shared `jetpack` ability-category description with the Jetpack plugin's registrar so the visible text is consistent regardless of which subpackage registers first.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: changed
3+
4+
Connection abilities: point get-connection-status `registration_url` at the WP 7.0+ Connectors screen when available, falling back to the Jetpack admin page on older WP.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: changed
3+
4+
Connection abilities: rename get-connection-status `site_connected` output field to `site_registered` to match site-registration terminology.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: changed
3+
4+
Connection abilities: tighten get-connection-status permission to current_user_can( 'jetpack_admin_page' ).

projects/packages/connection/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"brain/monkey": "^2.6.2",
2020
"automattic/jetpack-licensing": "@dev",
2121
"automattic/jetpack-sync": "@dev",
22+
"automattic/jetpack-wp-abilities": "@dev",
2223
"automattic/phpunit-select-config": "@dev"
2324
},
2425
"suggest": {
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
<?php
2+
/**
3+
* Jetpack Connection Abilities Registration.
4+
*
5+
* Registers Jetpack Connection abilities with the WordPress Abilities API so
6+
* AI agents can inspect the site's connection state through the standard
7+
* `wp-abilities/v1` REST surface.
8+
*
9+
* @package automattic/jetpack-connection
10+
*/
11+
12+
// @phan-file-suppress PhanUndeclaredFunction, PhanUndeclaredClassMethod @phan-suppress-current-line UnusedSuppression -- Abilities API added in WP 6.9; suppressions needed for older-WP compatibility runs.
13+
14+
namespace Automattic\Jetpack\Connection\Abilities;
15+
16+
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
17+
use Automattic\Jetpack\Connection\Package_Version;
18+
use Automattic\Jetpack\WP_Abilities\Registrar;
19+
use Jetpack_Options;
20+
21+
/**
22+
* Registers Jetpack Connection abilities with the WordPress Abilities API.
23+
*
24+
* Exposes a single read-only ability for site-level connection state so AI
25+
* agents can answer "is this site registered?" without having to
26+
* reverse-engineer Jetpack_Options keys.
27+
*
28+
* Writes (registering a new site, disconnecting a user, transferring
29+
* ownership) are deliberately deferred to a follow-up PR.
30+
*/
31+
class Connection_Abilities extends Registrar {
32+
33+
const CATEGORY_SLUG = 'jetpack';
34+
35+
/**
36+
* {@inheritDoc}
37+
*/
38+
public static function get_category_slug(): string {
39+
return self::CATEGORY_SLUG;
40+
}
41+
42+
/**
43+
* {@inheritDoc}
44+
*
45+
* The `jetpack` ability-category is shared with other Jetpack registrars
46+
* (e.g. the Modules_Abilities class in the Jetpack plugin). Only the first
47+
* registration wins, so the English source string is kept byte-identical
48+
* across registrars to keep the visible category text consistent
49+
* regardless of load order.
50+
*/
51+
public static function get_category_definition(): array {
52+
return array(
53+
// "Jetpack" is a product name and should not be translated.
54+
'label' => 'Jetpack',
55+
'description' => __( 'Abilities provided by Jetpack.', 'jetpack-connection' ),
56+
);
57+
}
58+
59+
/**
60+
* {@inheritDoc}
61+
*/
62+
public static function get_abilities(): array {
63+
return array(
64+
'jetpack/get-connection-status' => self::spec_get_connection_status(),
65+
);
66+
}
67+
68+
/*
69+
---------------------------------------------------------------------
70+
* Ability specs
71+
* ---------------------------------------------------------------------
72+
*/
73+
74+
/**
75+
* Spec: jetpack/get-connection-status.
76+
*/
77+
private static function spec_get_connection_status(): array {
78+
return array(
79+
'label' => __( 'Get Jetpack connection status', 'jetpack-connection' ),
80+
'description' => __(
81+
'Return the site-level Jetpack connection state in one zero-argument call. Shape: { site_registered, user_connected, master_user, blog_id, registration_url, connection_version }. `site_registered` 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. `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_registered` is false; null once the site is registered. `connection_version` is the running Jetpack Connection package version. Read-only and idempotent — safe to poll.',
82+
'jetpack-connection'
83+
),
84+
'input_schema' => array(
85+
'type' => 'object',
86+
'properties' => new \stdClass(),
87+
'additionalProperties' => false,
88+
),
89+
'output_schema' => array(
90+
'type' => 'object',
91+
'properties' => array(
92+
'site_registered' => array( 'type' => 'boolean' ),
93+
'user_connected' => array( 'type' => 'boolean' ),
94+
'master_user' => array( 'type' => array( 'integer', 'null' ) ),
95+
'blog_id' => array( 'type' => array( 'integer', 'null' ) ),
96+
'registration_url' => array( 'type' => array( 'string', 'null' ) ),
97+
'connection_version' => array( 'type' => 'string' ),
98+
),
99+
),
100+
'execute_callback' => array( __CLASS__, 'get_connection_status' ),
101+
'permission_callback' => array( __CLASS__, 'can_view_connection' ),
102+
'meta' => array(
103+
'annotations' => array(
104+
'readonly' => true,
105+
'destructive' => false,
106+
'idempotent' => true,
107+
),
108+
'show_in_rest' => true,
109+
),
110+
);
111+
}
112+
113+
/*
114+
---------------------------------------------------------------------
115+
* Permission callbacks
116+
* ---------------------------------------------------------------------
117+
*/
118+
119+
/**
120+
* Permission check: mirrors the capability used by the Jetpack admin page.
121+
*
122+
* Connection state is not sensitive in itself (the same data is exposed
123+
* on the Jetpack admin page and through several existing REST endpoints),
124+
* but subscribers and contributors have no legitimate need to inspect it.
125+
* Gating on `jetpack_admin_page` aligns with how {@see Modules_Abilities}
126+
* scopes its read.
127+
*
128+
* @return bool
129+
*/
130+
public static function can_view_connection(): bool {
131+
return current_user_can( 'jetpack_admin_page' );
132+
}
133+
134+
/*
135+
---------------------------------------------------------------------
136+
* Execute callbacks
137+
* ---------------------------------------------------------------------
138+
*/
139+
140+
/**
141+
* Execute: get-connection-status.
142+
*
143+
* @param array|null $input Ignored — zero-arg ability.
144+
* @return array
145+
*/
146+
public static function get_connection_status( $input = null ) {
147+
unset( $input );
148+
149+
$manager = self::get_manager();
150+
$site_registered = (bool) $manager->is_connected();
151+
$user_connected = (bool) $manager->has_connected_user();
152+
153+
$master_user_raw = Jetpack_Options::get_option( 'master_user' );
154+
$master_user = is_numeric( $master_user_raw ) && (int) $master_user_raw > 0 ? (int) $master_user_raw : null;
155+
156+
$blog_id_raw = Jetpack_Options::get_option( 'id' );
157+
$blog_id = is_numeric( $blog_id_raw ) && (int) $blog_id_raw > 0 ? (int) $blog_id_raw : null;
158+
159+
return array(
160+
'site_registered' => $site_registered,
161+
'user_connected' => $user_connected,
162+
'master_user' => $master_user,
163+
'blog_id' => $blog_id,
164+
'registration_url' => $site_registered ? null : self::registration_url(),
165+
'connection_version' => Package_Version::PACKAGE_VERSION,
166+
);
167+
}
168+
169+
/*
170+
---------------------------------------------------------------------
171+
* Helpers
172+
* ---------------------------------------------------------------------
173+
*/
174+
175+
/**
176+
* Return a Connection_Manager instance. Filterable for tests so they can
177+
* inject a partial mock without having to seed Jetpack_Options + tokens.
178+
*
179+
* @return Connection_Manager
180+
*/
181+
protected static function get_manager(): Connection_Manager {
182+
/**
183+
* Filters the Connection_Manager instance used by the Connection abilities.
184+
*
185+
* Tests inject a partial mock here; production callers should leave
186+
* the default. The filter callback receives the package-default
187+
* instance and must return a Connection_Manager — non-Manager
188+
* returns are discarded.
189+
*
190+
* @since 8.4.0
191+
*
192+
* @param Connection_Manager $manager The default instance.
193+
*/
194+
$instance = apply_filters( 'jetpack_connection_abilities_manager', new Connection_Manager() );
195+
return $instance instanceof Connection_Manager ? $instance : new Connection_Manager();
196+
}
197+
198+
/**
199+
* Build the wp-admin URL the site owner should visit to register the
200+
* site to WordPress.com. We deliberately return a stable admin URL (no
201+
* secret generation, no XML-RPC roundtrip) so this read stays side-effect
202+
* free and cheap to poll. The destination page handles the actual
203+
* registration handshake from there.
204+
*
205+
* WP 7.0+ ships a core "Connectors" screen at `wp-admin/options-connectors.php`
206+
* with a Jetpack card registered by {@see Jetpack_Connector}. We probe
207+
* for the file directly (rather than a `class_exists()` on the registry)
208+
* because the file is what actually serves the URL — if it isn't on
209+
* disk, the redirect 404s regardless of which classes have loaded.
210+
*
211+
* @return string
212+
*/
213+
private static function registration_url(): string {
214+
if ( defined( 'ABSPATH' ) && file_exists( ABSPATH . 'wp-admin/options-connectors.php' ) ) {
215+
return admin_url( 'options-connectors.php' );
216+
}
217+
return admin_url( 'admin.php?page=jetpack' );
218+
}
219+
}

0 commit comments

Comments
 (0)