clientId = $clientId; $this->clientSecret = $clientSecret; $this->tenantId = $tenantId; $this->redirectUri = $redirectUri; } public function getLoginUrl() { $params = http_build_query([ 'client_id' => $this->clientId, 'response_type' => 'code', 'redirect_uri' => $this->redirectUri, 'response_mode' => 'query', 'scope' => SCOPES, 'prompt' => 'consent', // erzwingt Benutzer-Zustimmung beim ersten Mal ]); return "https://login.microsoftonline.com/{$this->tenantId}/oauth2/v2.0/authorize?$params"; } public function handleCallback($code) { $tokenUrl = "https://login.microsoftonline.com/{$this->tenantId}/oauth2/v2.0/token"; $data = [ 'grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => $this->redirectUri, 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'scope' => SCOPES, ]; $response = $this->post($tokenUrl, $data); $tokens = json_decode($response, true); // === Fehlerbehandlung === if (!isset($tokens['access_token'])) { echo "
Fehler bei Token-Antwort:\n";
            print_r($tokens);
            echo "\nResponse RAW:\n" . htmlspecialchars($response);
            echo "
"; exit; } $this->accessToken = $tokens['access_token']; $user = $this->getUserInfo(); if (!isset($user['id'])) { echo "
Fehler: Benutzerinformationen konnten nicht abgerufen werden.\n";
            print_r($user);
            echo "
"; exit; } // === E-Mail auslesen === $email = $user['mail'] ?? $user['userPrincipalName'] ?? 'unknown@example.com'; // === Tokens + Zeit + E-Mail speichern === $tokens['created_at'] = time(); $this->saveTokensToFile($user['id'], $tokens, $email); // === User zurückgeben === $user['email'] = $email; return $user; } public function refreshAccessTokenFromFile($userId) { $tokenFile = TOKEN_DIR . "/$userId.json"; if (!file_exists($tokenFile)) throw new Exception("Token nicht gefunden"); $stored = json_decode(file_get_contents($tokenFile), true); $data = [ 'grant_type' => 'refresh_token', 'refresh_token' => $stored['refresh_token'], 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'redirect_uri' => $this->redirectUri, 'scope' => SCOPES, ]; $tokenUrl = "https://login.microsoftonline.com/{$this->tenantId}/oauth2/v2.0/token"; $response = $this->post($tokenUrl, $data); $tokens = json_decode($response, true); if (!isset($tokens['access_token'])) { echo "
Fehler beim Refresh-Token:\n";
            print_r($tokens);
            echo "\nResponse RAW:\n" . htmlspecialchars($response);
            echo "
"; exit; } $this->accessToken = $tokens['access_token']; $tokens['created_at'] = time(); $email = $stored['email'] ?? null; $this->saveTokensToFile($userId, $tokens, $email); } // =========================================== // NEU: Automatischer Refresh / Offline Access // =========================================== public function getValidAccessToken($userId) { $tokenFile = TOKEN_DIR . "/$userId.json"; if (!file_exists($tokenFile)) { throw new Exception("Token-Datei für Benutzer $userId nicht gefunden"); } $stored = json_decode(file_get_contents($tokenFile), true); // Prüfen, ob Access Token noch gültig ist (mit 2-Minuten-Puffer) $expiresAt = ($stored['created_at'] ?? 0) + ($stored['expires_in'] ?? 0) - 120; if (time() < $expiresAt && isset($stored['access_token'])) { $this->accessToken = $stored['access_token']; return $this->accessToken; } // Token ist abgelaufen → refresh $data = [ 'grant_type' => 'refresh_token', 'refresh_token' => $stored['refresh_token'], 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'redirect_uri' => $this->redirectUri, 'scope' => SCOPES, ]; $tokenUrl = "https://login.microsoftonline.com/{$this->tenantId}/oauth2/v2.0/token"; $response = $this->post($tokenUrl, $data); $tokens = json_decode($response, true); if (!isset($tokens['access_token'])) { echo "
Fehler beim automatischen Refresh:\n";
            print_r($tokens);
            echo "\nResponse RAW:\n" . htmlspecialchars($response);
            echo "
"; throw new Exception("Access Token konnte nicht erneuert werden."); } $tokens['created_at'] = time(); $email = $stored['email'] ?? null; $this->saveTokensToFile($userId, $tokens, $email); $this->accessToken = $tokens['access_token']; return $this->accessToken; } // =========================================== // Hilfsfunktionen // =========================================== private function saveTokensToFile($userId, $tokens, $email = null) { $tokens['email'] = $email; file_put_contents(TOKEN_DIR . "/$userId.json", json_encode($tokens, JSON_PRETTY_PRINT)); } public function getUserInfo() { $headers = [ "Authorization: Bearer {$this->accessToken}", "Content-Type: application/json" ]; $ch = curl_init("https://graph.microsoft.com/v1.0/me"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers ]); $res = curl_exec($ch); curl_close($ch); return json_decode($res, true); } public function getAccessToken() { return $this->accessToken; } private function post($url, $data) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["Content-Type: application/x-www-form-urlencoded"], CURLOPT_POSTFIELDS => http_build_query($data), ]); $response = curl_exec($ch); curl_close($ch); return $response; } // ======================= // Kalender-Funktionen // ======================= /** * Holt die nächsten X Termine des Benutzers * @param int $top Anzahl der Termine, default 10 * @return array Termine */ public function getCalendarEvents($top = 10) { // Sicherstellen, dass Access Token gültig ist $token = $this->accessToken ?? null; if (!$token) throw new Exception("Kein Access Token vorhanden."); $headers = [ "Authorization: Bearer {$token}", "Content-Type: application/json" ]; $url = "https://graph.microsoft.com/v1.0/me/events?\$top={$top}&\$orderby=start/dateTime"; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers ]); $res = curl_exec($ch); curl_close($ch); $data = json_decode($res, true); return $data['value'] ?? []; } /** * Erstellt einen neuen Termin * @param string $subject Betreff * @param string $start ISO8601 Startzeit, z.B. 2025-11-11T10:00:00 * @param string $end ISO8601 Endzeit * @param string $body Textinhalt des Termins * @return array Microsoft Graph Antwort */ public function createCalendarEvent($subject, $start, $end, $body = '') { $token = $this->accessToken ?? null; if (!$token) throw new Exception("Kein Access Token vorhanden."); $headers = [ "Authorization: Bearer {$token}", "Content-Type: application/json" ]; $data = [ 'subject' => $subject, 'start' => [ 'dateTime' => $start, 'timeZone' => 'UTC' ], 'end' => [ 'dateTime' => $end, 'timeZone' => 'UTC' ], 'body' => [ 'contentType' => 'Text', 'content' => $body ] ]; $ch = curl_init("https://graph.microsoft.com/v1.0/me/events"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => json_encode($data), ]); $res = curl_exec($ch); curl_close($ch); return json_decode($res, true); } /** * Löscht einen Termin anhand der Event-ID * @param string $eventId ID des Termins * @return bool Erfolg */ public function deleteCalendarEvent($eventId) { $token = $this->accessToken ?? null; if (!$token) throw new Exception("Kein Access Token vorhanden."); $headers = [ "Authorization: Bearer {$token}" ]; $ch = curl_init("https://graph.microsoft.com/v1.0/me/events/{$eventId}"); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => "DELETE", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers ]); curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $httpCode === 204; // 204 = gelöscht erfolgreich } } ?>