status = $status; $this->errorCode = $errorCode; $this->payload = $payload; } } class ZoraLinks { private string $apiKey; private string $baseUrl; private int $timeout; private ?string $caBundle; public function __construct(?string $apiKey = null, string $baseUrl = 'https://zoralinks.com/api/v1', int $timeout = 20, ?string $caBundle = null) { $apiKey = $apiKey ?: getenv('ZORALINKS_API_KEY'); if (!$apiKey) { throw new InvalidArgumentException('ZoraLinks: falta apiKey o ZORALINKS_API_KEY'); } $this->apiKey = $apiKey; $this->baseUrl = rtrim($baseUrl, '/'); $this->timeout = $timeout; $this->caBundle = $caBundle ?: (getenv('ZORALINKS_CA_BUNDLE') ?: null); } private function request(string $method, string $path, array $params = [], ?array $body = null): array { $url = $this->baseUrl . $path; $clean = array_filter($params, static fn($value) => $value !== null && $value !== ''); if ($clean) { $url .= '?' . http_build_query($clean); } $headers = [ 'Accept: application/json', 'Authorization: Bearer ' . $this->apiKey, 'User-Agent: ZoraLinks-PHP-SDK/1.0', ]; $options = [ CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => $this->timeout, ]; if ($this->caBundle) { $options[CURLOPT_CAINFO] = $this->caBundle; } if ($body !== null) { $headers[] = 'Content-Type: application/json'; $options[CURLOPT_HTTPHEADER] = $headers; $options[CURLOPT_POSTFIELDS] = json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } $curl = curl_init($url); curl_setopt_array($curl, $options); $raw = curl_exec($curl); if ($raw === false) { $message = curl_error($curl); curl_close($curl); throw new ZoraLinksError(0, 'network_error', $message, []); } $status = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); $payload = json_decode($raw, true); if (!is_array($payload)) { $payload = ['error' => 'invalid_response', 'message' => substr((string) $raw, 0, 300)]; } if ($status >= 400) { throw new ZoraLinksError( $status, (string) ($payload['error'] ?? 'request_error'), (string) ($payload['message'] ?? ''), $payload ); } return $payload; } public function me(): array { return $this->request('GET', '/me'); } public function listLinks(int $limit = 50, int $offset = 0, ?string $status = null): array { return $this->request('GET', '/links', [ 'limit' => $limit, 'offset' => $offset, 'status' => $status, ]); } public function getLink(string $idOrAlias): array { return $this->request('GET', '/links/' . rawurlencode($idOrAlias)); } public function createLink(string $alias, string $destination, ?string $title = null, int $steps = 3): array { return $this->request('POST', '/links', [], [ 'alias' => $alias, 'destination' => $destination, 'title' => $title, 'steps' => $steps, ]); } public function updateLink(string $idOrAlias, array $data, string $method = 'PATCH'): array { return $this->request($method, '/links/' . rawurlencode($idOrAlias), [], $data); } public function deleteLink(string $idOrAlias): array { return $this->request('DELETE', '/links/' . rawurlencode($idOrAlias)); } public function stats(string $period = 'all'): array { return $this->request('GET', '/stats', ['period' => $period]); } public function analytics(string $period = '30d'): array { return $this->request('GET', '/analytics', ['period' => $period]); } public function linkAnalytics(string $idOrAlias, string $period = '30d'): array { return $this->request('GET', '/links/' . rawurlencode($idOrAlias) . '/analytics', ['period' => $period]); } }