PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / vendor / wordpress / mcp-adapter / includes / Transport / Infrastructure / SessionManager.php

SessionManager.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF trunk, at vendor/wordpress/mcp-adapter/includes/Transport/Infrastructure/SessionManager.php

341 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MCP Session Manager using User Meta
4 *
5 *
6 * @package McpAdapter
7 */
8
9 declare( strict_types=1 );
10
11 namespace WP\MCP\Transport\Infrastructure;
12
13 use WP_Error;
14
15 /**
16 * MCP Session Manager
17 *
18 * Handles session creation, validation, and cleanup using user meta storage.
19 * Sessions are tied to authenticated users to prevent anonymous session flooding.
20 */
21 final class SessionManager {
22
23 /**
24 * User meta key for storing sessions
25 *
26 * @var string
27 */
28 private const SESSION_META_KEY = 'mcp_adapter_sessions';
29
30 /**
31 * Maximum sessions per user.
32 *
33 * @var int
34 */
35 private const DEFAULT_MAX_SESSIONS = 32;
36
37 /**
38 * Session inactivity timeout in seconds (24 hours).
39 *
40 * @var int
41 */
42 private const DEFAULT_INACTIVITY_TIMEOUT = DAY_IN_SECONDS;
43
44 /**
45 * Minimum interval between last_activity writes in seconds.
46 *
47 * @var int
48 */
49 private const DEFAULT_ACTIVITY_UPDATE_INTERVAL = 60;
50
51 /**
52 * Create a new session for a user
53 *
54 * @param int $user_id The user ID.
55 * @param array $params Client parameters from initialize request.
56 *
57 * @return string|false The session ID on success, false on failure.
58 */
59 public static function create_session( int $user_id, array $params = array() ) {
60 if ( ! $user_id || ! get_user_by( 'id', $user_id ) ) {
61 return false;
62 }
63
64 // Cleanup inactive sessions first
65 self::cleanup_expired_sessions( $user_id );
66
67 // Get current sessions
68 $sessions = self::get_all_user_sessions( $user_id );
69
70 // Check session limit - remove oldest if over limit
71 $config = self::get_config();
72 $max_sessions = $config['max_sessions'];
73 if ( count( $sessions ) >= $max_sessions ) {
74 // Remove oldest session (FIFO) - sort by created_at and remove first
75 uasort(
76 $sessions,
77 static function ( $a, $b ) {
78 return $a['created_at'] <=> $b['created_at'];
79 }
80 );
81
82 array_shift( $sessions );
83 }
84
85 // Create a new session
86 $session_id = wp_generate_uuid4();
87 $now = time();
88
89 $sessions[ $session_id ] = array(
90 'created_at' => $now,
91 'last_activity' => $now,
92 'client_params' => $params,
93 );
94
95 // Save sessions
96 update_user_meta( $user_id, self::SESSION_META_KEY, $sessions );
97
98 return $session_id;
99 }
100
101 /**
102 * Cleanup inactive sessions for a user
103 *
104 * @param int $user_id The user ID.
105 *
106 * @return int Number of sessions removed.
107 */
108 public static function cleanup_expired_sessions( int $user_id ): int {
109 if ( ! $user_id ) {
110 return 0;
111 }
112
113 $sessions = self::get_all_user_sessions( $user_id );
114 $now = time();
115 $removed = 0;
116
117 $config = self::get_config();
118 $inactivity_timeout = $config['inactivity_timeout'];
119
120 foreach ( $sessions as $session_id => $session ) {
121 // Check if still active - skip if valid
122 if ( $session['last_activity'] + $inactivity_timeout >= $now ) {
123 continue;
124 }
125
126 // Session is inactive - remove it
127 unset( $sessions[ $session_id ] );
128 ++$removed;
129 }
130
131 if ( $removed > 0 ) {
132 if ( empty( $sessions ) ) {
133 delete_user_meta( $user_id, self::SESSION_META_KEY );
134 } else {
135 update_user_meta( $user_id, self::SESSION_META_KEY, $sessions );
136 }
137 }
138
139 return $removed;
140 }
141
142 /**
143 * Get all sessions for a user
144 *
145 * @param int $user_id The user ID.
146 *
147 * @return array Array of sessions.
148 */
149 public static function get_all_user_sessions( int $user_id ): array {
150 if ( ! $user_id ) {
151 return array();
152 }
153
154 $sessions = get_user_meta( $user_id, self::SESSION_META_KEY, true );
155
156 if ( ! is_array( $sessions ) ) {
157 return array();
158 }
159
160 return $sessions;
161 }
162
163 /**
164 * Get configuration values.
165 *
166 * @return array{max_sessions: int, inactivity_timeout: int, activity_update_interval: int} Configuration array.
167 */
168 private static function get_config(): array {
169 /**
170 * Filters the maximum number of MCP sessions allowed per user.
171 *
172 * When a user exceeds this limit, the oldest inactive session is
173 * automatically removed to make room for new sessions.
174 *
175 * @since 0.3.0
176 *
177 * @param int $max_sessions Maximum sessions per user. Default 32.
178 */
179 $max_sessions = (int) apply_filters( 'mcp_adapter_session_max_per_user', self::DEFAULT_MAX_SESSIONS );
180
181 /**
182 * Filters the session inactivity timeout in seconds.
183 *
184 * Sessions that have been inactive longer than this duration are
185 * considered expired and may be cleaned up automatically.
186 *
187 * @since 0.3.0
188 *
189 * @param int $timeout Inactivity timeout in seconds. Default DAY_IN_SECONDS (86400 / 24 hours).
190 */
191 $inactivity_timeout = (int) apply_filters( 'mcp_adapter_session_inactivity_timeout', self::DEFAULT_INACTIVITY_TIMEOUT );
192
193 /**
194 * Filters the minimum interval between session last_activity writes.
195 *
196 * To reduce write amplification, the session manager only updates
197 * `last_activity` if at least this many seconds have elapsed since
198 * the last write.
199 *
200 * @since 0.5.0
201 *
202 * @param int $interval Minimum seconds between writes. Default 60.
203 */
204 $activity_update_interval = (int) apply_filters( 'mcp_adapter_session_activity_update_interval', self::DEFAULT_ACTIVITY_UPDATE_INTERVAL );
205
206 // Clamp: interval must be less than inactivity timeout to prevent
207 // sessions from expiring despite active use.
208 if ( $activity_update_interval >= $inactivity_timeout ) {
209 $activity_update_interval = (int) ( $inactivity_timeout / 2 );
210 }
211
212 return array(
213 'max_sessions' => $max_sessions,
214 'inactivity_timeout' => $inactivity_timeout,
215 'activity_update_interval' => max( 0, $activity_update_interval ),
216 );
217 }
218
219 /**
220 * Get a specific session for a user
221 *
222 * @param int $user_id The user ID.
223 * @param string $session_id The session ID.
224 *
225 * @return array|\WP_Error|false Session data on success, WP_Error on invalid input, false if not found or inactive.
226 */
227 public static function get_session( int $user_id, string $session_id ) {
228 if ( ! $user_id || ! $session_id ) {
229 return new WP_Error( 'mcp_session_invalid_input', 'Invalid user ID or session ID.' );
230 }
231
232 $sessions = self::get_all_user_sessions( $user_id );
233
234 if ( ! isset( $sessions[ $session_id ] ) ) {
235 return false;
236 }
237
238 $session = $sessions[ $session_id ];
239
240 // Check inactivity timeout
241 $config = self::get_config();
242 $inactivity_timeout = $config['inactivity_timeout'];
243 if ( $session['last_activity'] + $inactivity_timeout < time() ) {
244 self::clear_session( $user_id, $session_id );
245
246 return false;
247 }
248
249 return $session;
250 }
251
252 /**
253 * Clear an inactive session (internal cleanup).
254 *
255 * @param int $user_id The user ID.
256 * @param string $session_id The session ID to clear.
257 *
258 * @return void
259 */
260 private static function clear_session( int $user_id, string $session_id ): void {
261 $sessions = self::get_all_user_sessions( $user_id );
262
263 if ( ! isset( $sessions[ $session_id ] ) ) {
264 return;
265 }
266
267 unset( $sessions[ $session_id ] );
268 update_user_meta( $user_id, self::SESSION_META_KEY, $sessions );
269 }
270
271 /**
272 * Validate a session and update last activity
273 *
274 * @param int $user_id The user ID.
275 * @param string $session_id The session ID.
276 *
277 * @return bool True if valid, false otherwise.
278 */
279 public static function validate_session( int $user_id, string $session_id ): bool {
280 if ( ! $user_id || ! $session_id ) {
281 return false;
282 }
283
284 $sessions = self::get_all_user_sessions( $user_id );
285
286 if ( ! isset( $sessions[ $session_id ] ) ) {
287 return false;
288 }
289
290 $session = $sessions[ $session_id ];
291
292 // Check inactivity timeout
293 $config = self::get_config();
294 $inactivity_timeout = $config['inactivity_timeout'];
295 if ( $session['last_activity'] + $inactivity_timeout < time() ) {
296 self::clear_session( $user_id, $session_id );
297
298 return false;
299 }
300
301 // Throttle last_activity writes to reduce write amplification
302 $activity_update_interval = $config['activity_update_interval'];
303 if ( time() - $session['last_activity'] >= $activity_update_interval ) {
304 $sessions[ $session_id ]['last_activity'] = time();
305 update_user_meta( $user_id, self::SESSION_META_KEY, $sessions );
306 }
307
308 return true;
309 }
310
311 /**
312 * Delete a specific session
313 *
314 * @param int $user_id The user ID.
315 * @param string $session_id The session ID.
316 *
317 * @return bool True on success, false on failure.
318 */
319 public static function delete_session( int $user_id, string $session_id ): bool {
320 if ( ! $user_id || ! $session_id ) {
321 return false;
322 }
323
324 $sessions = self::get_all_user_sessions( $user_id );
325
326 if ( ! isset( $sessions[ $session_id ] ) ) {
327 return false;
328 }
329
330 unset( $sessions[ $session_id ] );
331
332 if ( empty( $sessions ) ) {
333 delete_user_meta( $user_id, self::SESSION_META_KEY );
334 } else {
335 update_user_meta( $user_id, self::SESSION_META_KEY, $sessions );
336 }
337
338 return true;
339 }
340 }
341