This commit is contained in:
Sven Steinert
2026-05-13 11:57:52 +02:00
parent 6abf6f9c3d
commit f4511b9213
76 changed files with 4494 additions and 1940 deletions

View File

@@ -0,0 +1,194 @@
<?php
declare(strict_types=1);
namespace KbMarkdownImporter\GitLab;
final class GitLabClient
{
private string $baseUrl;
private string $token;
private string $branchPattern;
public function __construct(array $settings)
{
$this->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,
]
);
}
}