query(" SELECT i.settings FROM integrations i JOIN users u ON i.user_id = u.id WHERE u.role = 'ADMIN' AND i.provider = 'JIRA' AND i.settings IS NOT NULL ORDER BY i.updated_at DESC LIMIT 1 "); $row = $stmt->fetch(); if (!empty($row['settings'])) { $settings = json_decode($row['settings'], true) ?: []; if (!empty($settings['jira_domain'])) { return jira_normalize_domain($settings['jira_domain']); } } // Fallback naar een willekeurige JIRA rij $fallback = $db->query(" SELECT settings FROM integrations WHERE provider = 'JIRA' AND settings LIKE '%jira_domain%' LIMIT 1 ")->fetch(); if (!empty($fallback['settings'])) { $settings = json_decode($fallback['settings'], true) ?: []; if (!empty($settings['jira_domain'])) { return jira_normalize_domain($settings['jira_domain']); } } } catch (Throwable $e) { error_log("jira_get_company_domain error: " . $e->getMessage()); } return ''; } /** * Genereert de Basic Authentication header voor Atlassian REST API */ function jira_get_auth_header(string $email, string $apiToken): string { return 'Basic ' . base64_encode(trim($email) . ':' . trim($apiToken)); } /** * Test de verbinding met Atlassian Jira Cloud via /rest/api/3/myself */ function jira_test_connection(string $domain, string $email, string $apiToken): array { $cleanDomain = jira_normalize_domain($domain); if (empty($cleanDomain) || empty($email) || empty($apiToken)) { return [ 'success' => false, 'error' => 'Vul een Jira domein, e-mailadres en Atlassian API token in.', ]; } $authHeader = jira_get_auth_header($email, $apiToken); $url = $cleanDomain . '/rest/api/3/myself'; $response = fociz_http_request($url, [ 'method' => 'GET', 'headers' => [ 'Authorization' => $authHeader, 'Accept' => 'application/json', ], 'timeout' => 10, ]); // Fallback naar api v2 indien v3 niet beschikbaar is if ($response['status'] === 404) { $urlV2 = $cleanDomain . '/rest/api/2/myself'; $response = fociz_http_request($urlV2, [ 'method' => 'GET', 'headers' => [ 'Authorization' => $authHeader, 'Accept' => 'application/json', ], 'timeout' => 10, ]); } if ($response['status'] === 200 && is_array($response['json'])) { $user = $response['json']; return [ 'success' => true, 'user' => [ 'displayName' => $user['displayName'] ?? 'Atlassian Gebruiker', 'emailAddress' => $user['emailAddress'] ?? $email, 'accountId' => $user['accountId'] ?? ($user['key'] ?? ''), 'active' => $user['active'] ?? true, 'timeZone' => $user['timeZone'] ?? 'Europe/Amsterdam', ], ]; } $errMsg = 'Verbinding mislukt met Jira.'; if ($response['status'] === 401) { $errMsg = 'Authenticatie geweigerd (HTTP 401). Controleer uw e-mailadres en zorg dat u een geldig Atlassian API token gebruikt.'; } elseif ($response['status'] === 403) { $errMsg = 'Toegang geweigerd (HTTP 403). Uw account heeft onvoldoende rechten op dit Jira domein.'; } elseif ($response['status'] === 404) { $errMsg = 'Jira Cloud domein niet gevonden (HTTP 404). Controleer het ingevoerde domein (bijv. uwbedrijf.atlassian.net).'; } elseif (!empty($response['error'])) { $errMsg = "Netwerkfout: {$response['error']}"; } return [ 'success' => false, 'error' => $errMsg, 'status' => $response['status'], ]; } /** * Haalt alle actieve issues op die zijn toegewezen aan de ingelogde Jira gebruiker */ function jira_fetch_assigned_issues(string $domain, string $email, string $apiToken, int $maxResults = 50): array { $cleanDomain = jira_normalize_domain($domain); $authHeader = jira_get_auth_header($email, $apiToken); // JQL: Alle openstaande taken toegewezen aan mij, gesorteerd op recent bijgewerkt $jql = 'assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC'; $params = [ 'jql' => $jql, 'maxResults' => min($maxResults, 100), 'fields' => 'summary,status,priority,issuetype,parent,project,duedate,updated', ]; $url = $cleanDomain . '/rest/api/3/search?' . http_build_query($params); $response = fociz_http_request($url, [ 'method' => 'GET', 'headers' => [ 'Authorization' => $authHeader, 'Accept' => 'application/json', ], 'timeout' => 12, ]); // Fallback naar api v2 if ($response['status'] === 404) { $urlV2 = $cleanDomain . '/rest/api/2/search?' . http_build_query($params); $response = fociz_http_request($urlV2, [ 'method' => 'GET', 'headers' => [ 'Authorization' => $authHeader, 'Accept' => 'application/json', ], 'timeout' => 12, ]); } if ($response['status'] !== 200 || !isset($response['json']['issues'])) { return [ 'success' => false, 'error' => $response['json']['errorMessages'][0] ?? ($response['error'] ?? 'Kan Jira tickets niet ophalen'), 'issues' => [], ]; } $rawIssues = $response['json']['issues']; $issues = []; foreach ($rawIssues as $item) { $fields = $item['fields'] ?? []; $key = $item['key'] ?? ''; $summary = $fields['summary'] ?? '(Geen titel)'; $statusName = $fields['status']['name'] ?? 'To Do'; $priorityName = $fields['priority']['name'] ?? 'Medium'; $issueTypeName = $fields['issuetype']['name'] ?? 'Taak'; $projectName = $fields['project']['name'] ?? 'Project'; $dueDate = $fields['duedate'] ?? null; $issues[] = [ 'key' => $key, 'summary' => $summary, 'status' => $statusName, 'priority' => $priorityName, 'issue_type' => $issueTypeName, 'project' => $projectName, 'due_date' => $dueDate, 'url' => "{$cleanDomain}/browse/{$key}", 'source' => 'JIRA_LIVE', ]; } return [ 'success' => true, 'issues' => $issues, 'total' => count($issues), ]; } /** * Haalt de Jira tickets op voor een Fociz gebruiker * Schakelt naadloos tussen live Jira tickets en sandbox demotickets */ function jira_get_user_issues(int $userId, bool $forceRefresh = false): array { $db = get_db_connection(); $stmt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'JIRA' AND is_enabled = 1"); $stmt->execute([$userId]); $integration = $stmt->fetch(); // Als de integratie niet is ingesteld of in demo mode staat, geef interactieve fallback issues terug if (!$integration || !empty($integration['is_demo_mode'])) { return [ 'is_demo' => true, 'success' => true, 'issues' => [ [ 'key' => 'FOC-101', 'summary' => 'Tweestapsverificatie (2FA TOTP) flow implementeren', 'status' => 'In Progress', 'priority' => 'High', 'issue_type' => 'Story', 'project' => 'Fociz Platform', 'due_date' => date('Y-m-d', strtotime('+2 days')), 'url' => '#', 'source' => 'SANDBOX', ], [ 'key' => 'FOC-102', 'summary' => 'Interactief whiteboard canvas voor brainstormsessies', 'status' => 'To Do', 'priority' => 'High', 'issue_type' => 'Feature', 'project' => 'Fociz Platform', 'due_date' => date('Y-m-d', strtotime('+4 days')), 'url' => '#', 'source' => 'SANDBOX', ], [ 'key' => 'FOC-103', 'summary' => 'Koppeling met Microsoft Outlook Calendar & Teams Graph API', 'status' => 'In Review', 'priority' => 'Medium', 'issue_type' => 'Task', 'project' => 'Integrations Hub', 'due_date' => date('Y-m-d'), 'url' => '#', 'source' => 'SANDBOX', ], [ 'key' => 'FOC-104', 'summary' => 'Roadmap kwartaalweergave en voortgangsindicatoren', 'status' => 'To Do', 'priority' => 'Medium', 'issue_type' => 'Story', 'project' => 'Roadmap Module', 'due_date' => date('Y-m-d', strtotime('+7 days')), 'url' => '#', 'source' => 'SANDBOX', ], ], ]; } $settings = json_decode($integration['settings'] ?? '{}', true) ?: []; $domain = !empty($settings['jira_domain']) ? $settings['jira_domain'] : jira_get_company_domain(); $email = $settings['jira_email'] ?? fociz_env('JIRA_EMAIL', ''); $apiToken = $settings['jira_api_token'] ?? fociz_env('JIRA_API_TOKEN', ''); if (empty($domain) || empty($email) || empty($apiToken)) { return [ 'is_demo' => false, 'success' => false, 'error' => 'Jira configuratie is onvolledig.', 'issues' => [], ]; } // Sessiecache voor 90 seconden om onnodige Jira API calls bij snelle pagina refreshes te beperken if (session_status() === PHP_SESSION_NONE) { @session_start(); } $cacheKey = "jira_issues_user_{$userId}"; $cached = $_SESSION[$cacheKey] ?? null; if (!$forceRefresh && $cached && is_array($cached) && ($cached['timestamp'] ?? 0) > (time() - 90)) { return [ 'is_demo' => false, 'success' => true, 'issues' => $cached['issues'], 'from_cache' => true, ]; } $result = jira_fetch_assigned_issues($domain, $email, $apiToken, 50); if ($result['success']) { $_SESSION[$cacheKey] = [ 'timestamp' => time(), 'issues' => $result['issues'], ]; // Werk last_sync_at bij $stmtUpdate = $db->prepare("UPDATE integrations SET last_sync_at = NOW() WHERE id = ?"); $stmtUpdate->execute([$integration['id']]); return [ 'is_demo' => false, 'success' => true, 'issues' => $result['issues'], ]; } return [ 'is_demo' => false, 'success' => false, 'error' => $result['error'], 'issues' => [], ]; } /** * Ontkoppelt Jira integratie voor een gebruiker */ function jira_disconnect(int $userId): bool { $db = get_db_connection(); $stmt = $db->prepare("DELETE FROM integrations WHERE user_id = ? AND provider = 'JIRA'"); if (session_status() === PHP_SESSION_NONE) { @session_start(); } unset($_SESSION["jira_issues_user_{$userId}"]); return $stmt->execute([$userId]); }