PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
metricool / app / Http / RSPAL / RspalApiClient.php

RspalApiClient.php in Metricool – Social media and site statistics 2.1.1, at app/Http/RSPAL/RspalApiClient.php

211 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Metricool\Http\RSPAL;
4
5 use Metricool\Vendor\GuzzleHttp\Client;
6 use Metricool\Vendor\GuzzleHttp\Exception\GuzzleException;
7 use Metricool\Vendor\GuzzleHttp\Exception\RequestException;
8 use Metricool\Exceptions\RestDataException;
9 use Metricool\Http\Metricool\MetricoolClient;
10 use Metricool\Support\Helpers\Storages\EnvironmentConfig;
11
12 class RspalApiClient
13 {
14 /**
15 * The option name for storing the InstallationID.
16 */
17 private const INSTALLATION_ID_OPTION = '_metricool_rspal_installation_id';
18
19 /**
20 * The default value for the InstallationID when it is not set.
21 */
22 private const UNKNOWN_INSTALLATION_ID = 'unknown';
23
24 /**
25 * The Guzzle HTTP client for making API requests
26 */
27 protected Client $client;
28
29 /**
30 * The base endpoint URL for the brand API
31 */
32 private string $baseEndpoint;
33
34 /**
35 * The default headers to send with each request
36 */
37 private array $headers = [
38 'Content-Type' => 'application/json',
39 'Accept' => 'application/json',
40 ];
41
42 private EnvironmentConfig $env;
43 private MetricoolClient $metricool;
44
45 public function __construct(EnvironmentConfig $env, MetricoolClient $metricool)
46 {
47 $this->env = $env;
48 $this->metricool = $metricool;
49
50 $this->baseEndpoint = $this->env->getUrl('metricool.rsp_auth_url');
51 $this->client = $this->client();
52 }
53
54 /**
55 * Sign Up Endpoint
56 *
57 * @throws RestDataException
58 */
59 public function signUp(array $data, array $headers = []): RspalApiResponse
60 {
61 $response = $this->request('v2/integrations/wp-plugin/users/sign-ups', [
62 'json' => $data,
63 'headers' => $this->headers($headers)
64 ], 'post');
65
66 if ($response->getStatusCode() == 400) {
67 throw new RestDataException('Email or password error', 422);
68 }
69
70 // Check if the response contains the required fields
71 if (empty($response->data->accessToken) || empty($response->data->refreshToken)) {
72 throw new RestDataException('Signup response is missing required fields', 500);
73 }
74
75 return $response;
76 }
77
78 /**
79 * Make a request to the RSPAL API.
80 *
81 * @throws RestDataException
82 */
83 private function request(string $path, array $params = [], string $method = 'get'): RspalApiResponse
84 {
85 try {
86 // Request and store installationId if needed before any request
87 if (!$this->hasInstallationId()) {
88 $installation = $this->requestInstallation();
89 $this->setInstallationId($installation->data->uuid);
90 }
91
92 $response = $this->client->request(strtoupper($method), $this->uri($path), $params);
93 } catch (RequestException $e) {
94 throw (new RestDataException($e->getMessage()))
95 ->setResponseCode($e->getResponse()->getStatusCode());
96 } catch (GuzzleException $e) {
97 throw new RestDataException($e->getMessage());
98 }
99
100 return RspalApiResponse::fromResponse($response);
101 }
102
103 /**
104 * Request a new InstallationID from the RSPAL API.
105 *
106 * @throws GuzzleException
107 * @throws RestDataException
108 */
109 private function requestInstallation(): RspalApiResponse
110 {
111 $response = $this->client->request('POST', $this->uri('installation/create'), [
112 'headers' => $this->headers()
113 ]);
114
115 return RspalApiResponse::fromResponse($response);
116 }
117
118 /**
119 * Create a new Guzzle HTTP client instance.
120 */
121 private function client(): Client
122 {
123 return new Client([
124 'http_errors' => false,
125 'verify' => false,
126 ]);
127 }
128
129 /**
130 * Get any headers that should be sent with the request.
131 */
132 private function headers(array $headers = []): array
133 {
134 $headers['User-Agent'] = $this->metricool->getRequestUserAgent();
135
136 return array_merge($this->headers, $this->rspalHeaders(), $headers);
137 }
138
139 protected function rspalHeaders(): array
140 {
141 $installationId = $this->getInstallationId();
142
143 $rspalHeaders = [
144 'RSPAL-PluginName' => $this->env->getString('plugin.name'),
145 'RSPAL-PluginVersion' => $this->env->getString('plugin.version'),
146 'RSPAL-PluginPath' => $this->getPluginPathHeader(),
147 'RSPAL-Origin' => trailingslashit(site_url()),
148 'RSPAL-InstallationId' => $installationId,
149 ];
150
151 $rspalHeaders['RSPAL-Signature'] = $this->getInstallationSignature($rspalHeaders, $installationId);
152
153 return $rspalHeaders;
154 }
155
156 /**
157 * Get the plugin path relative to the WordPress root directory.
158 */
159 private function getPluginPathHeader(): string
160 {
161 $pluginFullPath = wp_normalize_path(realpath($this->env->getString('plugin.path')));
162 $wpRoot = wp_normalize_path(realpath(ABSPATH));
163
164 return str_replace($wpRoot, '', $pluginFullPath);
165 }
166
167 /**
168 * Get the full URI for the given path.
169 */
170 private function uri(string $path): string
171 {
172 return rtrim($this->baseEndpoint, '/') . '/' . ltrim($path, '/');
173 }
174
175 /**
176 * Generate the installation signature.
177 */
178 private function getInstallationSignature(array $format, string $id): string
179 {
180 return hash_hmac('sha256', json_encode($format), $id);
181 }
182
183 /**
184 * Stores the InstallationID in wp_options
185 */
186 private function setInstallationId(string $installationId): void
187 {
188 $installation = update_option(self::INSTALLATION_ID_OPTION, $installationId, false);
189
190 if (!$installation) {
191 throw new \RuntimeException('Failed to store InstallationID');
192 }
193 }
194
195 /**
196 * Check if the InstallationID has already been stored
197 */
198 private function hasInstallationId(): bool
199 {
200 return $this->getInstallationId() !== self::UNKNOWN_INSTALLATION_ID;
201 }
202
203 /**
204 * Return the installationId from wp_options
205 */
206 private function getInstallationId(): string
207 {
208 return get_option(self::INSTALLATION_ID_OPTION, self::UNKNOWN_INSTALLATION_ID);
209 }
210 }
211