| @@ -43,8 +43,15 @@ | ||
| 43 | 43 | */ |
| 44 | 44 | protected Logtivity_Options $options; |
| 45 | 45 | |
| 46 | 46 | /** |
| 47 | + * Writes API call data to a file when debug mode is enabled. | |
| 48 | + * | |
| 49 | + * @var Logtivity_Debug_Log | |
| 50 | + */ | |
| 51 | + protected Logtivity_Debug_Log $debugLog; | |
| 52 | + | |
| 53 | + /** | |
| 47 | 54 | * We generally don't want to wait for a response from the API. |
| 48 | 55 | * |
| 49 | 56 | * @var bool |
| 50 | 57 | */ |
| @@ -66,9 +73,10 @@ | ||
| 66 | 73 | protected ?string $api_key = null; |
| 67 | 74 | |
| 68 | 75 | public function __construct() |
| 69 | 76 | { |
| 70 | - $this->options = new Logtivity_Options(); | |
| 77 | + $this->options = new Logtivity_Options(); | |
| 78 | + $this->debugLog = new Logtivity_Debug_Log(); | |
| 71 | 79 | } |
| 72 | 80 | |
| 73 | 81 | /** |
| 74 | 82 | * @return $this |
| @@ -115,8 +123,9 @@ | ||
| 115 | 123 | $body['site_hash'] = $this->options->urlHash(); |
| 116 | 124 | } |
| 117 | 125 | |
| 118 | 126 | $waitForResponse = $this->waitForResponse || $this->options->shouldLogLatestResponse(); |
| 127 | + $requestBody = $body; | |
| 119 | 128 | |
| 120 | 129 | $request = [ |
| 121 | 130 | 'method' => $method, |
| 122 | 131 | 'timeout' => ($waitForResponse ? 6 : 0.01), |
| @@ -129,14 +138,30 @@ | ||
| 129 | 138 | 'body' => $body, |
| 130 | 139 | 'cookies' => [], |
| 131 | 140 | ]; |
| 132 | 141 | |
| 133 | - // @TODO: Switch to Logtivity_Response class to get standardized responses | |
| 134 | - $response = wp_remote_request($this->getEndpoint($url), $request); | |
| 142 | + /** | |
| 143 | + * This request must not be cached. It writes a log entry to the API rather | |
| 144 | + * than reading a cacheable resource, so a cached response would silently | |
| 145 | + * drop logs. The default path is also non-blocking with a 0.01s timeout, | |
| 146 | + * so it does not hold up the page load. | |
| 147 | + * | |
| 148 | + * Settings check-ins are already rate limited by | |
| 149 | + * Logtivity_Options::shouldCheckInWithApi(), which gates on the | |
| 150 | + * logtivity_last_settings_check_in_at option. | |
| 151 | + * | |
| 152 | + * @TODO: Switch to Logtivity_Response class to get standardized responses | |
| 153 | + */ | |
| 154 | + $requestStartedAt = microtime(true); | |
| 155 | + $response = wp_remote_request($this->getEndpoint($url), $request); | |
| 156 | + $responseTimeMs = (int)round((microtime(true) - $requestStartedAt) * 1000); | |
| 157 | + | |
| 158 | + $responseData = null; | |
| 159 | + | |
| 135 | 160 | if ($waitForResponse && $this->notUpdatingWidgetInCustomizer()) { |
| 136 | 161 | // We waited and received a response |
| 137 | 162 | if ($response instanceof WP_Error) { |
| 138 | - $responseData = array_merge(static::$responseData, [ | |
| 163 | + $parsedResponse = array_merge(static::$responseData, [ | |
| 139 | 164 | 'code' => 500, |
| 140 | 165 | 'message' => $response->get_error_code(), |
| 141 | 166 | 'error' => $response->get_error_message() ?: $response->get_error_code(), |
| 142 | 167 | 'body' => get_object_vars($response), |
| @@ -152,9 +177,9 @@ | ||
| 152 | 177 | } else { |
| 153 | 178 | $responseError = $responseMessage; |
| 154 | 179 | $responseMessage = (($responseBody['message'] ?? $responseMessage) ?: 'Unknown error'); |
| 155 | 180 | } |
| 156 | - $responseData = [ | |
| 181 | + $parsedResponse = [ | |
| 157 | 182 | 'code' => $responseCode, |
| 158 | 183 | 'message' => $responseMessage, |
| 159 | 184 | 'error' => $responseError, |
| 160 | 185 | 'body' => $responseBody, |
| @@ -161,12 +186,12 @@ | ||
| 161 | 186 | ]; |
| 162 | 187 | } |
| 163 | 188 | |
| 164 | 189 | $newStatus = 'success'; |
| 165 | - if ($responseData['code']) { | |
| 166 | - if ($responseData['code'] < 400) { | |
| 190 | + if ($parsedResponse['code']) { | |
| 191 | + if ($parsedResponse['code'] < 400) { | |
| 167 | 192 | // Successful request, check if api is telling us to pause |
| 168 | - if ($responseData['error']) { | |
| 193 | + if ($parsedResponse['error']) { | |
| 169 | 194 | $newStatus = 'paused'; |
| 170 | 195 | } |
| 171 | 196 | |
| 172 | 197 | $body = json_decode($response['body'] ?? 'null', true); |
| @@ -172,9 +197,9 @@ | ||
| 172 | 197 | $body = json_decode($response['body'] ?? 'null', true); |
| 173 | 198 | $this->updateSettings($body['settings'] ?? []); |
| 174 | 199 | |
| 175 | 200 | } else { |
| 176 | - if ($this->getCurlError($responseData['error']) != static::CURL_TIMEOUT) { | |
| 201 | + if ($this->getCurlError($parsedResponse['error']) != static::CURL_TIMEOUT) { | |
| 177 | 202 | // Something other than a timeout went wrong with the request |
| 178 | 203 | $newStatus = 'fail'; |
| 179 | 204 | } |
| 180 | 205 | |
| @@ -184,17 +209,39 @@ | ||
| 184 | 209 | $this->options->update([ |
| 185 | 210 | 'logtivity_api_key_check' => $newStatus, |
| 186 | 211 | 'logtivity_latest_response' => array_merge( |
| 187 | 212 | ['date' => date('Y-m-d H:i:s')], |
| 188 | - $responseData | |
| 213 | + $parsedResponse | |
| 189 | 214 | ), |
| 190 | 215 | ], |
| 191 | 216 | false |
| 192 | 217 | ); |
| 193 | 218 | |
| 194 | - return $responseData; | |
| 219 | + $responseData = $parsedResponse; | |
| 195 | 220 | } |
| 196 | 221 | } |
| 222 | + | |
| 223 | + if ($this->getOption('logtivity_enable_debug_mode')) { | |
| 224 | + // GET responses (e.g. the paginated /logs listing) can be large | |
| 225 | + // and aren't useful to store in full; only capture the body for | |
| 226 | + // the more common POST calls (log/settings sends). | |
| 227 | + $responseBody = $method === 'GET' ? '(omitted for GET requests)' : ($responseData['body'] ?? null); | |
| 228 | + | |
| 229 | + $this->debugLog->write([ | |
| 230 | + 'timestamp' => current_time('mysql'), | |
| 231 | + 'endpoint' => $url, | |
| 232 | + 'url' => $this->getEndpoint($url), | |
| 233 | + 'method' => $method, | |
| 234 | + 'request_body' => $requestBody, | |
| 235 | + 'response_code' => $responseData['code'] ?? null, | |
| 236 | + 'response_message' => $responseData['message'] ?? null, | |
| 237 | + 'response_error' => $responseData['error'] ?? null, | |
| 238 | + 'response_body' => $responseData ? $responseBody : null, | |
| 239 | + 'response_time_ms' => $responseData ? $responseTimeMs : null, | |
| 240 | + ]); | |
| 241 | + } | |
| 242 | + | |
| 243 | + return $responseData; | |
| 197 | 244 | } |
| 198 | 245 | |
| 199 | 246 | return null; |
| 200 | 247 | } |
| @@ -203,9 +250,10 @@ | ||
| 203 | 250 | * @return bool |
| 204 | 251 | */ |
| 205 | 252 | protected function ready(): bool |
| 206 | 253 | { |
| 207 | - return ($this->getApiKey()) | |
| 254 | + return logtivity_url_environment_mismatch() == false | |
| 255 | + && ($this->getApiKey()) | |
| 208 | 256 | && logtivity_has_site_url_changed() == false |
| 209 | 257 | && ( |
| 210 | 258 | $this->ignoreStatus |
| 211 | 259 | || $this->getOption('logtivity_api_key_check') == 'success' |
| @@ -268,10 +316,16 @@ | ||
| 268 | 316 | * @return bool |
| 269 | 317 | */ |
| 270 | 318 | protected function notUpdatingWidgetInCustomizer(): bool |
| 271 | 319 | { |
| 272 | - $customize = sanitize_text_field($_POST['wp_customize'] ?? null); | |
| 273 | - $action = sanitize_text_field($_POST['action'] ?? null); | |
| 320 | + // phpcs:disable WordPress.Security.NonceVerification.Missing -- Passive check avoids option writes during core Customizer widget updates. | |
| 321 | + $customize = isset($_POST['wp_customize']) | |
| 322 | + ? sanitize_text_field(wp_unslash($_POST['wp_customize'])) | |
| 323 | + : null; | |
| 324 | + $action = isset($_POST['action']) | |
| 325 | + ? sanitize_text_field(wp_unslash($_POST['action'])) | |
| 326 | + : null; | |
| 327 | + // phpcs:enable WordPress.Security.NonceVerification.Missing | |
| 274 | 328 | |
| 275 | 329 | return ($action == 'update-widget' && $customize == 'on') == false; |
| 276 | 330 | } |
| 277 | 331 | |