$this->apiKey])->asForm(); } private function logInteraction(string $action, string $url, Response $response): void { Log::debug('Mailwizz API', [ 'action' => $action, 'url' => $url, 'http_status' => $response->status(), 'response_preview' => mb_substr($response->body(), 0, 2000), ]); } /** * @return list */ public function getLists(): array { $url = "{$this->baseUrl}/lists"; $response = $this->client()->get($url); $this->logInteraction('getLists', $url, $response); if ($response->failed()) { throw new RuntimeException(__('Invalid API key or connection failed.')); } $json = $response->json(); if (! is_array($json)) { throw new RuntimeException(__('Invalid API key or connection failed.')); } if (($json['status'] ?? null) === 'error') { throw new RuntimeException(__('Invalid API key or connection failed.')); } return $this->normalizeListsPayload($json); } /** * @return list}> */ public function getListFields(string $listUid): array { $url = "{$this->baseUrl}/lists/{$listUid}/fields"; $response = $this->client()->get($url); $this->logInteraction('getListFields', $url, $response); if ($response->failed()) { throw new RuntimeException(__('Failed to fetch list fields.')); } $json = $response->json(); if (! is_array($json)) { throw new RuntimeException(__('Failed to fetch list fields.')); } if (($json['status'] ?? null) === 'error') { throw new RuntimeException(__('Failed to fetch list fields.')); } return $this->normalizeFieldsPayload($json); } /** * Search by email. Mailwizz uses query param EMAIL regardless of list field mapping. * * @return array{subscriber_uid: string}|null */ public function searchSubscriber(string $listUid, string $email): ?array { $url = "{$this->baseUrl}/lists/{$listUid}/subscribers/search-by-email"; $response = $this->client()->get($url, [ 'EMAIL' => $email, ]); $this->logInteraction('searchSubscriber', $url, $response); if ($response->failed()) { if ($response->status() === 404) { return null; } throw new RuntimeException(__('Mailwizz subscriber search failed.')); } $json = $response->json(); if (! is_array($json)) { return null; } if (($json['status'] ?? null) !== 'success') { return null; } $uid = data_get($json, 'data.subscriber_uid') ?? data_get($json, 'data.record.subscriber_uid'); if (! is_string($uid) || $uid === '') { return null; } return ['subscriber_uid' => $uid]; } /** * Full subscriber payload from Mailwizz (structure varies; callers inspect data.record). * * @return array|null */ public function getSubscriber(string $listUid, string $subscriberUid): ?array { $url = "{$this->baseUrl}/lists/{$listUid}/subscribers/{$subscriberUid}"; $response = $this->client()->get($url); $this->logInteraction('getSubscriber', $url, $response); if ($response->failed()) { throw new RuntimeException(__('Mailwizz could not load subscriber.')); } $json = $response->json(); if (! is_array($json) || ($json['status'] ?? null) !== 'success') { return null; } return $json; } /** * @param array $data Form fields; checkboxlist values as string[] per field tag * @return array */ public function createSubscriber(string $listUid, array $data): array { $url = "{$this->baseUrl}/lists/{$listUid}/subscribers"; $response = $this->client()->post($url, $this->encodeFormPayload($data)); $this->logInteraction('createSubscriber', $url, $response); if ($response->failed()) { throw new RuntimeException(__('Mailwizz rejected subscriber creation.')); } $json = $response->json(); if (! is_array($json) || ($json['status'] ?? null) !== 'success') { $msg = is_array($json) ? (string) ($json['error'] ?? $json['message'] ?? '') : ''; throw new RuntimeException($msg !== '' ? $msg : __('Mailwizz rejected subscriber creation.')); } return $json; } /** * @param array $data * @return array */ public function updateSubscriber(string $listUid, string $subscriberUid, array $data): array { $url = "{$this->baseUrl}/lists/{$listUid}/subscribers/{$subscriberUid}"; $response = $this->client()->put($url, $this->encodeFormPayload($data)); $this->logInteraction('updateSubscriber', $url, $response); if ($response->failed()) { throw new RuntimeException(__('Mailwizz rejected subscriber update.')); } $json = $response->json(); if (! is_array($json) || ($json['status'] ?? null) !== 'success') { $msg = is_array($json) ? (string) ($json['error'] ?? $json['message'] ?? '') : ''; throw new RuntimeException($msg !== '' ? $msg : __('Mailwizz rejected subscriber update.')); } return $json; } /** * @param array $data * @return array */ private function encodeFormPayload(array $data): array { $out = []; foreach ($data as $key => $value) { if (is_array($value)) { // Empty arrays are omitted by Laravel's multipart encoder (no KEY[] parts), so Mailwizz never clears checkboxlist fields. $out[$key] = $value === [] ? '' : $value; continue; } if ($value === null) { continue; } $out[$key] = $value; } return $out; } /** * @return list */ private function normalizeListsPayload(array $json): array { $out = []; $records = data_get($json, 'data.records'); if (! is_array($records)) { return $out; } foreach ($records as $row) { if (! is_array($row)) { continue; } $uid = data_get($row, 'general.list_uid') ?? data_get($row, 'list_uid'); $name = data_get($row, 'general.name') ?? data_get($row, 'name'); if (is_string($uid) && $uid !== '' && is_string($name) && $name !== '') { $out[] = ['list_uid' => $uid, 'name' => $name]; } } return $out; } /** * @return list}> */ private function normalizeFieldsPayload(array $json): array { $out = []; $records = data_get($json, 'data.records'); if (! is_array($records)) { return $out; } foreach ($records as $row) { if (! is_array($row)) { continue; } $tag = data_get($row, 'tag'); $label = data_get($row, 'label'); $typeId = data_get($row, 'type.identifier'); if (! is_string($tag) || $tag === '' || ! is_string($label)) { continue; } $typeIdentifier = is_string($typeId) ? $typeId : ''; $rawOptions = data_get($row, 'options'); $options = $this->normalizeFieldOptions($rawOptions); $out[] = [ 'tag' => $tag, 'label' => $label, 'type_identifier' => $typeIdentifier, 'options' => $options, ]; } return $out; } /** * @return array */ private function normalizeFieldOptions(mixed $rawOptions): array { if ($rawOptions === null || $rawOptions === '') { return []; } if (is_string($rawOptions)) { $decoded = json_decode($rawOptions, true); if (is_array($decoded)) { $rawOptions = $decoded; } else { return []; } } if (! is_array($rawOptions)) { return []; } $out = []; foreach ($rawOptions as $key => $value) { if (is_string($key) || is_int($key)) { $k = (string) $key; $out[$k] = is_scalar($value) ? (string) $value : ''; } } return $out; } }