PluginProbe
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity / trunk
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity vtrunk
3.3.8 trunk 1.0 1.1.0 1.10.0 1.11.0 1.11.1 1.12.0 1.13.0 1.14.0 1.15.0 1.16.0 1.17.0 1.17.1 1.18.0 1.19.0 1.2.0 1.20.0 1.20.1 1.3.0 1.3.1 1.4.0 1.5.0 1.6.0 1.6.1 All 66 releases
logtivity / Base / Logtivity_Api.php

Logtivity_Api.php in Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity trunk, at Base/Logtivity_Api.php

428 lines 11.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @package Logtivity
5 * @contact logtivity.io, hello@logtivity.io
6 * @copyright 2024-2026 Logtivity. All rights reserved
7 * @license https://www.gnu.org/licenses/gpl.html GNU/GPL
8 *
9 * This file is part of Logtivity.
10 *
11 * Logtivity is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * Logtivity is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with Logtivity. If not, see <https://www.gnu.org/licenses/>.
23 */
24
25 class Logtivity_Api
26 {
27 public const CURL_TIMEOUT = 28;
28
29 /**
30 * @var array
31 */
32 protected static $responseData = [
33 'code' => null,
34 'message' => null,
35 'error' => null,
36 'body' => null,
37 ];
38
39 /**
40 * Option class to access the plugin settings
41 *
42 * @var Logtivity_Options
43 */
44 protected Logtivity_Options $options;
45
46 /**
47 * We generally don't want to wait for a response from the API.
48 *
49 * @var bool
50 */
51 protected bool $waitForResponse = false;
52
53 /**
54 * If the currrent status is not 'fail' or 'success'
55 * attempt to send anyway
56 *
57 * @var bool
58 */
59 protected bool $ignoreStatus = false;
60
61 /**
62 * The API Key for either the site or team
63 *
64 * @var ?string
65 */
66 protected ?string $api_key = null;
67
68 public function __construct()
69 {
70 $this->options = new Logtivity_Options();
71 }
72
73 /**
74 * @return $this
75 */
76 public function ignoreStatus(): self
77 {
78 $this->ignoreStatus = true;
79
80 return $this;
81 }
82
83 /**
84 * @param string $url
85 * @param array $body
86 *
87 * @return ?array
88 */
89 public function post(string $url, array $body): ?array
90 {
91 return $this->makeRequest($url, $body);
92 }
93
94 /**
95 * Make a request to the Logtivity API
96 *
97 * @param string $url
98 * @param ?array $body
99 * @param string $method
100 *
101 * @return ?array
102 */
103 public function makeRequest(string $url, ?array $body = [], string $method = 'POST'): ?array
104 {
105 $body = $body ?: [];
106
107 $body['occurred_at'] = time();
108
109 if ($this->options->urlHash() == false) {
110 $this->options->update(['logtivity_url_hash' => md5(home_url())], false);
111 }
112
113 if ($this->ready()) {
114 if ($this->getOption('logtivity_app_verify_url')) {
115 $body['site_hash'] = $this->options->urlHash();
116 }
117
118 $waitForResponse = $this->waitForResponse || $this->options->shouldLogLatestResponse();
119
120 $request = [
121 'method' => $method,
122 'timeout' => ($waitForResponse ? 6 : 0.01),
123 'blocking' => $waitForResponse,
124 'redirection' => 5,
125 'httpversion' => '1.0',
126 'headers' => [
127 'Authorization' => 'Bearer ' . $this->getApiKey(),
128 ],
129 'body' => $body,
130 'cookies' => [],
131 ];
132
133 // @TODO: Switch to Logtivity_Response class to get standardized responses
134 $response = wp_remote_request($this->getEndpoint($url), $request);
135 if ($waitForResponse && $this->notUpdatingWidgetInCustomizer()) {
136 // We waited and received a response
137 if ($response instanceof WP_Error) {
138 $responseData = array_merge(static::$responseData, [
139 'code' => 500,
140 'message' => $response->get_error_code(),
141 'error' => $response->get_error_message() ?: $response->get_error_code(),
142 'body' => get_object_vars($response),
143 ]);
144
145 } else {
146 $responseCode = wp_remote_retrieve_response_code($response);
147 $responseMessage = wp_remote_retrieve_response_message($response);
148 $responseBody = json_decode(wp_remote_retrieve_body($response), true);
149
150 if ($responseCode < 400) {
151 $responseError = $responseBody['error'] ?? null;
152 } else {
153 $responseError = $responseMessage;
154 $responseMessage = (($responseBody['message'] ?? $responseMessage) ?: 'Unknown error');
155 }
156 $responseData = [
157 'code' => $responseCode,
158 'message' => $responseMessage,
159 'error' => $responseError,
160 'body' => $responseBody,
161 ];
162 }
163
164 $newStatus = 'success';
165 if ($responseData['code']) {
166 if ($responseData['code'] < 400) {
167 // Successful request, check if api is telling us to pause
168 if ($responseData['error']) {
169 $newStatus = 'paused';
170 }
171
172 $body = json_decode($response['body'] ?? 'null', true);
173 $this->updateSettings($body['settings'] ?? []);
174
175 } else {
176 if ($this->getCurlError($responseData['error']) != static::CURL_TIMEOUT) {
177 // Something other than a timeout went wrong with the request
178 $newStatus = 'fail';
179 }
180
181 $this->updateLastCheckin();
182 }
183
184 $this->options->update([
185 'logtivity_api_key_check' => $newStatus,
186 'logtivity_latest_response' => array_merge(
187 ['date' => date('Y-m-d H:i:s')],
188 $responseData
189 ),
190 ],
191 false
192 );
193
194 return $responseData;
195 }
196 }
197 }
198
199 return null;
200 }
201
202 /**
203 * @return bool
204 */
205 protected function ready(): bool
206 {
207 return ($this->getApiKey())
208 && logtivity_has_site_url_changed() == false
209 && (
210 $this->ignoreStatus
211 || $this->getOption('logtivity_api_key_check') == 'success'
212 || $this->options->shouldCheckInWithApi()
213 );
214 }
215
216 /**
217 * @return ?string
218 */
219 public function getApiKey(): ?string
220 {
221 if ($this->api_key == false) {
222 $this->api_key = $this->options->getApiKey();
223 }
224
225 return $this->api_key;
226 }
227
228 /**
229 * @param string $apikey
230 *
231 * @return $this
232 */
233 public function setApiKey(string $apikey): self
234 {
235 $this->api_key = $apikey;
236
237 return $this;
238 }
239
240 /**
241 * @param ?string $key
242 *
243 * @return Logtivity_Options|mixed
244 */
245 public function getOption(?string $key = null)
246 {
247 if ($key) {
248 return $this->options->getOption($key);
249 }
250
251 return $this->options;
252 }
253
254 /**
255 * @param string $endpoint
256 *
257 * @return string
258 */
259 public function getEndpoint(string $endpoint): string
260 {
261 return logtivity_get_api_url() . $endpoint;
262 }
263
264 /**
265 * You cannot call an extra update_option during a widget update so we make
266 * sure not to log the most recent log response in this case.
267 *
268 * @return bool
269 */
270 protected function notUpdatingWidgetInCustomizer(): bool
271 {
272 $customize = sanitize_text_field($_POST['wp_customize'] ?? null);
273 $action = sanitize_text_field($_POST['action'] ?? null);
274
275 return ($action == 'update-widget' && $customize == 'on') == false;
276 }
277
278 /**
279 * @param array $settings
280 *
281 * @return void
282 */
283 public function updateSettings(array $settings): void
284 {
285 if ($settings) {
286 $verifiedSettings = [];
287 foreach ($settings as $key => $value) {
288 if ($key == 'disabled_logs') {
289 $key = 'global_' . $key;
290 }
291 $verifiedSettings['logtivity_' . $key] = is_null($value) ? '' : $value;
292 }
293
294 $this->options->update($verifiedSettings, false);
295 }
296
297 $this->updateLastCheckin();
298 }
299
300 /**
301 * @return void
302 */
303 protected function updateLastCheckin(): void
304 {
305 update_option('logtivity_last_settings_check_in_at', ['date' => date('Y-m-d H:i:s')]);
306 }
307
308 /**
309 * @param ?string $message
310 *
311 * @return int
312 */
313 protected function getCurlError(?string $message): ?int
314 {
315 preg_match('/curl\s+error\s+(\d+)/i', $message, $matches);
316
317 return $matches[1] ?? null;
318 }
319
320 /**
321 * @return ?array
322 */
323 public function getLatestResponse(): ?array
324 {
325 $response = $this->getOption('logtivity_latest_response');
326
327 return $response ?: null;
328 }
329
330 /**
331 * @return ?Exception
332 */
333 public function getLastError(): ?Exception
334 {
335 $latestResponse = $this->getLatestResponse();
336
337 $code = $latestResponse['code'] ?? null;
338
339 if ($code >= 400 && isset($latestResponse['body']['message'])) {
340 $message = $latestResponse['body']['message'];
341
342 } elseif (isset($latestResponse['body']['errors'])) {
343 $message = array_shift($latestResponse['body']['errors']);
344 while (is_array($message)) {
345 $message = array_shift($message);
346 }
347
348 } elseif (isset($latestResponse['body']['error'])) {
349 $message = $latestResponse['body']['error'];
350 } else {
351 $message = $latestResponse['error'] ?? $latestResponse['message'] ?? 'Unknown error';
352 }
353
354 if (($code == false || $message == false) && $this->getApiKey()) {
355 $message = 'Not connected. Please check API Key';
356 }
357
358 return new Exception($message, $code);
359 }
360
361 /**
362 * @param string $url
363 * @param ?array $body
364 *
365 * @return ?array
366 */
367 public function get(string $url, ?array $body = []): ?array
368 {
369 return $this->waitForResponse()->makeRequest($url, $body, 'GET');
370 }
371
372 /**
373 * @return $this
374 */
375 public function waitForResponse(): self
376 {
377 $this->waitForResponse = true;
378
379 return $this;
380 }
381
382 /**
383 * @TODO: This method is duplicated in the App.
384 *
385 * @return ?string
386 */
387 public function getConnectionMessage(): ?string
388 {
389 $status = $this->getConnectionStatus();
390
391 switch ($status) {
392 case 'success':
393 $message = 'Connected';
394 break;
395
396 case 'paused':
397 case 'fail':
398 $error = $this->getLastError();
399 $message = $error
400 ? sprintf('Disconnected (%s - %s)', $error->getCode(), $error->getMessage())
401 : null;
402 break;
403
404 case 'staging':
405 $message = 'Connection disabled. Site url has changed.';
406 break;
407
408 default:
409 $message = $this->getApiKey() ? null : 'API Key has not been set';
410 break;
411 }
412
413 return $message ?: 'Unknown Status';
414 }
415
416 /**
417 * @return ?string
418 */
419 public function getConnectionStatus(): ?string
420 {
421 if (logtivity_has_site_url_changed()) {
422 return 'staging';
423 }
424
425 return $this->getApiKey() ? $this->getOption('logtivity_api_key_check') : null;
426 }
427 }
428