PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / feedback / FeedbackSiteTokenStore.php

FeedbackSiteTokenStore.php in 404 Solution trunk, at includes/feedback/FeedbackSiteTokenStore.php

100 lines 3.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 require_once __DIR__ . '/FeedbackTransportLog.php';
8
9 /**
10 * Owns the plugin-side diagnostic identity credential stored in wp_options.
11 * The server mints every token; this class only validates, persists, reads,
12 * and cache-bypasses that confirmed value.
13 */
14 class ABJ_404_Solution_FeedbackSiteTokenStore {
15
16 const TOKEN_OPTION = 'abj404_site_token';
17 const NOTICE_TRANSIENT = 'abj404_plugin_db_notice';
18 const NOTICE_TYPE = 'diagnostic_identity_reestablished';
19 const NOTICE_TTL = 86400;
20
21 /**
22 * @param mixed $value
23 */
24 public static function isValidToken($value): bool {
25 return is_string($value) && preg_match('/\A[a-f0-9]{64}\z/', $value) === 1;
26 }
27
28 public static function storedToken(): string {
29 if (!function_exists('get_option')) {
30 return '';
31 }
32 $raw = get_option(self::TOKEN_OPTION, '');
33 if ($raw === '' || $raw === false || $raw === null) {
34 return '';
35 }
36 if (!is_string($raw) || !self::isValidToken($raw)) {
37 ABJ_404_Solution_FeedbackTransportLog::log(
38 'warn',
39 'abj404_transport: stored site token is malformed; ignoring it for this send'
40 );
41 return '';
42 }
43 return $raw;
44 }
45
46 public static function freshStoredToken(): string {
47 self::clearOptionCaches();
48 return self::storedToken();
49 }
50
51 public static function persistToken(string $token): bool {
52 if (!self::isValidToken($token)) {
53 return false;
54 }
55 if (!function_exists('update_option')) {
56 ABJ_404_Solution_FeedbackTransportLog::log(
57 'warn',
58 'abj404_transport: update_option unavailable; could not persist site token'
59 );
60 return false;
61 }
62 return (bool) update_option(self::TOKEN_OPTION, $token, false);
63 }
64
65 public static function recordIdentityRecoveryNotice(): void {
66 ABJ_404_Solution_FeedbackTransportLog::log(
67 'warn',
68 'abj404_transport: site diagnostic identity was re-established after a 401 response'
69 );
70 if (!function_exists('get_transient') || !function_exists('set_transient')) {
71 return;
72 }
73 $existing = get_transient(self::NOTICE_TRANSIENT);
74 if (is_array($existing) && (($existing['type'] ?? null) === self::NOTICE_TYPE)) {
75 return;
76 }
77 // allow-cache-empty: locally generated admin-notice payload, not a cached fetch result.
78 set_transient(self::NOTICE_TRANSIENT, array(
79 'type' => self::NOTICE_TYPE,
80 'message' => __(
81 "This site's diagnostic identity had to be re-established after the reports server rejected its stored credential.",
82 '404-solution'
83 ),
84 'guidance' => __(
85 'Reporting has recovered. Older self-service privacy history may require the manual privacy request path.',
86 '404-solution'
87 ),
88 ), self::NOTICE_TTL);
89 }
90
91 private static function clearOptionCaches(): void {
92 if (!function_exists('wp_cache_delete')) {
93 return;
94 }
95 wp_cache_delete(self::TOKEN_OPTION, 'options');
96 wp_cache_delete('notoptions', 'options');
97 wp_cache_delete('alloptions', 'options');
98 }
99 }
100