baseUrl = self::normalizeBaseUrl((string) ($settings['gitlab_base_url'] ?? '')); $this->token = (string) ($settings['gitlab_token'] ?? ''); $this->branchPattern = (string) ($settings['branch_pattern'] ?? '^v.*'); } public static function normalizeBaseUrl(string $baseUrl): string { $baseUrl = rtrim(trim($baseUrl), '/'); if (preg_match('#/api/v4$#i', $baseUrl)) { $baseUrl = (string) preg_replace('#/api/v4$#i', '', $baseUrl); } return $baseUrl; } public function getGroup(string $group): array|\WP_Error { return $this->request('GET', '/groups/' . rawurlencode($group)); } public function getProjects(string $group): array|\WP_Error { return $this->requestAll('/groups/' . rawurlencode($group) . '/projects', [ 'include_subgroups' => 'true', 'simple' => 'true', 'order_by' => 'path', 'sort' => 'asc', ]); } public function getProject(string $projectId): array|\WP_Error { return $this->request('GET', '/projects/' . rawurlencode($projectId)); } public function getBranches(string $projectId): array|\WP_Error { return $this->requestAll('/projects/' . rawurlencode($projectId) . '/repository/branches', []); } public function getDocumentationBranches(string $projectId): array|\WP_Error { $branches = $this->getBranches($projectId); if (is_wp_error($branches)) { return $branches; } $pattern = '/' . str_replace('/', '\/', $this->branchPattern) . '/'; return array_values(array_filter($branches, static function (array $branch) use ($pattern): bool { return isset($branch['name']) && @preg_match($pattern, (string) $branch['name']); })); } public function getFileRaw(string $projectId, string $path, string $ref): string|\WP_Error { $response = $this->rawRequest('/projects/' . rawurlencode($projectId) . '/repository/files/' . rawurlencode($path) . '/raw', [ 'ref' => $ref, ]); if (is_wp_error($response)) { return $response; } return wp_remote_retrieve_body($response); } public function getTree(string $projectId, string $ref, string $path = '', bool $recursive = true): array|\WP_Error { return $this->requestAll('/projects/' . rawurlencode($projectId) . '/repository/tree', [ 'ref' => $ref, 'path' => $path, 'recursive' => $recursive ? 'true' : 'false', ]); } private function requestAll(string $endpoint, array $query): array|\WP_Error { $page = 1; $items = []; do { $response = $this->rawRequest($endpoint, array_merge($query, [ 'per_page' => '100', 'page' => (string) $page, ])); if (is_wp_error($response)) { return $response; } $decoded = json_decode(wp_remote_retrieve_body($response), true); if (! is_array($decoded)) { return new \WP_Error('kb_gitlab_invalid_json', __('GitLab returned invalid JSON.', 'kb-markdown-importer')); } $items = array_merge($items, $decoded); $next = wp_remote_retrieve_header($response, 'x-next-page'); $page = $next ? (int) $next : 0; } while ($page > 0); return $items; } private function request(string $method, string $endpoint, array $query = []): array|\WP_Error { $response = $this->rawRequest($endpoint, $query, $method); if (is_wp_error($response)) { return $response; } $decoded = json_decode(wp_remote_retrieve_body($response), true); if (! is_array($decoded)) { return new \WP_Error('kb_gitlab_invalid_json', __('GitLab returned invalid JSON.', 'kb-markdown-importer')); } return $decoded; } private function rawRequest(string $endpoint, array $query = [], string $method = 'GET'): array|\WP_Error { if (! $this->baseUrl || ! $this->token) { return new \WP_Error('kb_gitlab_missing_settings', __('GitLab base URL or token is missing.', 'kb-markdown-importer')); } $url = $this->baseUrl . '/api/v4' . $endpoint; if ($query) { $url = add_query_arg($query, $url); } $response = wp_remote_request($url, [ 'method' => $method, 'timeout' => 30, 'headers' => [ 'PRIVATE-TOKEN' => $this->token, 'Accept' => 'application/json', ], ]); if (is_wp_error($response)) { return $response; } $code = (int) wp_remote_retrieve_response_code($response); if ($code >= 200 && $code < 300) { return $response; } $body = wp_strip_all_tags(wp_remote_retrieve_body($response)); $body = trim(preg_replace('/\s+/', ' ', $body) ?? $body); $body = substr($body, 0, 300); $retryAfter = wp_remote_retrieve_header($response, 'retry-after'); $message = sprintf( /* translators: %d is an HTTP status code. */ __('GitLab API request failed with HTTP %d.', 'kb-markdown-importer'), $code ); if (503 === $code) { $message .= ' ' . __('The GitLab server or a proxy returned Service Unavailable. Check whether GitLab is reachable from the WordPress server and whether the Base URL points to the GitLab root, not to /api/v4.', 'kb-markdown-importer'); } return new \WP_Error( 'kb_gitlab_http_' . $code, $message, [ 'status' => $code, 'url' => esc_url_raw($url), 'retry_after' => $retryAfter ? (string) $retryAfter : '', 'response_excerpt' => $body, ] ); } }