PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
jetpack / jetpack_vendor / automattic / jetpack-agents-manager / src / class-open-state-store.php

class-open-state-store.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-agents-manager/src/class-open-state-store.php

216 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Open_State_Store file.
4 *
5 * @package automattic/jetpack-agents-manager
6 */
7
8 namespace Automattic\Jetpack\Agents_Manager;
9
10 use Automattic\Jetpack\Connection\Client;
11 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12 use Automattic\Jetpack\Status\Host;
13
14 /**
15 * Reads and writes the Agents Manager open state.
16 *
17 * The state is a global, per-user wpcom preference behind the
18 * `/agents-manager/state` endpoint. How the server reads it depends on the site:
19 *
20 * - wpcom Simple: the preference is local, so read `calypso_preferences` directly.
21 * - WoA / self-hosted: the preference is remote, so reads/writes go through this
22 * store's local REST route, which calls wpcom over the Jetpack Connection and
23 * caches the result in a per-user transient. Latency-sensitive readers (the
24 * server-side pre-render) use that transient to skip the round-trip.
25 */
26 class Open_State_Store {
27
28 /**
29 * Transient key prefix for the cached per-user open state.
30 *
31 * @var string
32 */
33 private const TRANSIENT_PREFIX = 'agents_manager_open_state_';
34
35 /**
36 * Default state values.
37 *
38 * @var array
39 */
40 public const DEFAULTS = array(
41 'agents_manager_open' => false,
42 'agents_manager_docked' => false,
43 'agents_manager_minimized' => false,
44 'agents_manager_floating_position' => 'right',
45 'agents_manager_router_history' => null,
46 'agents_manager_last_activity' => null,
47 );
48
49 /**
50 * Fetch the open state from wpcom and refresh the cache.
51 *
52 * @return array|\WP_Error Normalized state, or WP_Error when the request fails.
53 */
54 public static function fetch() {
55 $body = Client::wpcom_json_api_request_as_user(
56 '/agents-manager/state',
57 '2',
58 array( 'method' => 'GET' )
59 );
60
61 if ( is_wp_error( $body ) ) {
62 return $body;
63 }
64
65 $response = json_decode( wp_remote_retrieve_body( $body ), true );
66 $state = self::normalize( is_array( $response ) ? $response : array() );
67
68 self::cache( $state );
69
70 return $state;
71 }
72
73 /**
74 * Persist the open state to wpcom and refresh the cache.
75 *
76 * @param array $state Partial state to update (subset of DEFAULTS keys).
77 * @return array|\WP_Error Normalized state, or WP_Error when the request fails.
78 */
79 public static function update( array $state ) {
80 $body = Client::wpcom_json_api_request_as_user(
81 '/agents-manager/state',
82 '2',
83 array( 'method' => 'POST' ),
84 array( 'state' => $state )
85 );
86
87 if ( is_wp_error( $body ) ) {
88 return $body;
89 }
90
91 $response = json_decode( wp_remote_retrieve_body( $body ), true );
92
93 if ( ! is_array( $response ) ) {
94 return new \WP_Error(
95 'invalid_response',
96 'Invalid response from WPCOM endpoint',
97 array( 'status' => 500 )
98 );
99 }
100
101 $normalized = self::normalize( $response );
102
103 self::cache( $normalized );
104
105 return $normalized;
106 }
107
108 /**
109 * Read the current user's open state from the fastest local source.
110 *
111 * For latency-sensitive callers like the server-side pre-render: Simple sites
112 * read `calypso_preferences` directly, everywhere else uses the cached
113 * transient (see the class docblock). Returns null when nothing is known yet,
114 * so callers can skip pre-rendering until the frontend sets the real state.
115 *
116 * @return array|null `{ agents_manager_open, agents_manager_docked, agents_manager_minimized }` or null.
117 */
118 public static function get_cached() {
119 $user_id = get_current_user_id();
120 if ( ! $user_id ) {
121 return null;
122 }
123
124 // Simple sites have the preference locally, so read it directly (the
125 // transient is never primed there).
126 if ( ( new Host() )->is_wpcom_simple() && function_exists( '\get_user_attribute' ) ) {
127 $calypso_prefs = \get_user_attribute( $user_id, 'calypso_preferences' );
128 if ( ! is_array( $calypso_prefs ) ) {
129 return null;
130 }
131
132 return array(
133 'agents_manager_open' => (bool) ( $calypso_prefs['agents_manager_open'] ?? false ),
134 'agents_manager_docked' => (bool) ( $calypso_prefs['agents_manager_docked'] ?? false ),
135 'agents_manager_minimized' => (bool) ( $calypso_prefs['agents_manager_minimized'] ?? false ),
136 );
137 }
138
139 // The cached state is only as good as the connection that produced it.
140 // Without a user connection the frontend cannot fetch or refresh state
141 // (the app never mounts), so a pre-render from a stale transient would
142 // flash a shell nothing ever takes down.
143 if ( ! ( new Connection_Manager() )->is_user_connected( $user_id ) ) {
144 return null;
145 }
146
147 $cached = get_transient( self::cache_key( $user_id ) );
148
149 return is_array( $cached ) ? $cached : null;
150 }
151
152 /**
153 * Normalize a raw endpoint response into the full state shape.
154 *
155 * @param array $response Raw decoded response.
156 * @return array Normalized state with all DEFAULTS keys present.
157 */
158 private static function normalize( array $response ): array {
159 return array(
160 'agents_manager_open' => (bool) ( $response['agents_manager_open'] ?? self::DEFAULTS['agents_manager_open'] ),
161 'agents_manager_docked' => (bool) ( $response['agents_manager_docked'] ?? self::DEFAULTS['agents_manager_docked'] ),
162 'agents_manager_minimized' => (bool) ( $response['agents_manager_minimized'] ?? self::DEFAULTS['agents_manager_minimized'] ),
163 'agents_manager_floating_position' => $response['agents_manager_floating_position'] ?? self::DEFAULTS['agents_manager_floating_position'],
164 'agents_manager_router_history' => $response['agents_manager_router_history'] ?? self::DEFAULTS['agents_manager_router_history'],
165 'agents_manager_last_activity' => $response['agents_manager_last_activity'] ?? self::DEFAULTS['agents_manager_last_activity'],
166 );
167 }
168
169 /**
170 * Cache the open, docked and minimized bits in a per-user transient.
171 *
172 * Only used on the remote (WoA / self-hosted) path — it's what get_cached()
173 * reads there. Simple sites read `calypso_preferences` directly and skip this.
174 *
175 * @param array $state Normalized state.
176 */
177 private static function cache( array $state ): void {
178 $user_id = get_current_user_id();
179 if ( ! $user_id ) {
180 return;
181 }
182
183 /**
184 * Filter how long the cached open state lives.
185 *
186 * It's refreshed on every read/write through this store, so the TTL mainly
187 * caps how long a value changed elsewhere (e.g. in Calypso) stays stale.
188 *
189 * @since 0.4.0
190 *
191 * @param int $ttl Cache lifetime in seconds.
192 */
193 $ttl = (int) apply_filters( 'agents_manager_open_state_cache_ttl', WEEK_IN_SECONDS );
194
195 set_transient(
196 self::cache_key( $user_id ),
197 array(
198 'agents_manager_open' => (bool) ( $state['agents_manager_open'] ?? false ),
199 'agents_manager_docked' => (bool) ( $state['agents_manager_docked'] ?? false ),
200 'agents_manager_minimized' => (bool) ( $state['agents_manager_minimized'] ?? false ),
201 ),
202 $ttl
203 );
204 }
205
206 /**
207 * Build the per-user transient key.
208 *
209 * @param int $user_id User ID.
210 * @return string
211 */
212 private static function cache_key( int $user_id ): string {
213 return self::TRANSIENT_PREFIX . $user_id;
214 }
215 }
216