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
}
}
?>