diff --git a/app/Modules/Academic/Services/DapodikClient.php b/app/Modules/Academic/Services/DapodikClient.php index f21cb32..0c8c782 100644 --- a/app/Modules/Academic/Services/DapodikClient.php +++ b/app/Modules/Academic/Services/DapodikClient.php @@ -58,15 +58,21 @@ class DapodikClient */ protected function request(string $method, string $url): array { + // Dapodik WebService commonly expects `token` as query param. + // We still send Authorization header for compatibility. + if ($this->token !== '' && strpos($url, 'token=') === false) { + $url = $this->appendQueryParam($url, 'token', $this->token); + } + $ch = curl_init(); if ($ch === false) { return ['success' => false, 'error' => 'cURL init failed']; } - $headers = [ - 'Accept: application/json', - 'Authorization: Bearer ' . $this->token, - ]; + $headers = ['Accept: application/json']; + if ($this->token !== '') { + $headers[] = 'Authorization: Bearer ' . $this->token; + } curl_setopt_array($ch, [ CURLOPT_URL => $url, @@ -79,15 +85,24 @@ class DapodikClient $body = curl_exec($ch); $errno = curl_errno($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + $contentType = (string) (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?? ''); curl_close($ch); if ($errno) { return ['success' => false, 'error' => 'Network error: ' . ($errno === CURLE_OPERATION_TIMEDOUT ? 'timeout' : 'curl ' . $errno)]; } - $decoded = json_decode($body, true); + $bodyStr = is_string($body) ? $body : ''; + if (trim($bodyStr) === '') { + return ['success' => false, 'error' => 'Empty response from Dapodik', 'http_code' => $httpCode, 'content_type' => $contentType]; + } + + $decoded = json_decode($bodyStr, true); if (json_last_error() !== JSON_ERROR_NONE) { - return ['success' => false, 'error' => 'Invalid JSON response', 'http_code' => $httpCode]; + $snippet = substr(trim(strip_tags($bodyStr)), 0, 200); + $extra = $snippet !== '' ? ' — ' . $snippet : ''; + $codePart = $httpCode > 0 ? ' (HTTP ' . $httpCode . ')' : ''; + return ['success' => false, 'error' => 'Invalid JSON response' . $codePart . $extra, 'http_code' => $httpCode, 'content_type' => $contentType]; } if ($httpCode < 200 || $httpCode >= 300) { @@ -98,6 +113,23 @@ class DapodikClient return array_merge(['success' => true], $decoded); } + /** + * Append a query param to URL, preserving fragment. + */ + private function appendQueryParam(string $url, string $key, string $value): string + { + $frag = ''; + $base = $url; + $hashPos = strpos($url, '#'); + if ($hashPos !== false) { + $base = substr($url, 0, $hashPos); + $frag = substr($url, $hashPos); + } + + $sep = (strpos($base, '?') === false) ? '?' : '&'; + return $base . $sep . rawurlencode($key) . '=' . rawurlencode($value) . $frag; + } + /** * Normalize Dapodik response to a list of rows. * Dapodik returns { results, id, start, limit, rows: [...] }