309 lines
9.4 KiB
PHP
309 lines
9.4 KiB
PHP
<?php
|
|
|
|
class GraphCalendarUserAuth {
|
|
private $clientId;
|
|
private $clientSecret;
|
|
private $tenantId;
|
|
private $redirectUri;
|
|
private $accessToken;
|
|
|
|
public function __construct($clientId, $clientSecret, $tenantId, $redirectUri) {
|
|
$this->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 "<pre><strong>Fehler bei Token-Antwort:</strong>\n";
|
|
print_r($tokens);
|
|
echo "\nResponse RAW:\n" . htmlspecialchars($response);
|
|
echo "</pre>";
|
|
exit;
|
|
}
|
|
|
|
$this->accessToken = $tokens['access_token'];
|
|
$user = $this->getUserInfo();
|
|
|
|
if (!isset($user['id'])) {
|
|
echo "<pre><strong>Fehler: Benutzerinformationen konnten nicht abgerufen werden.</strong>\n";
|
|
print_r($user);
|
|
echo "</pre>";
|
|
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 "<pre><strong>Fehler beim Refresh-Token:</strong>\n";
|
|
print_r($tokens);
|
|
echo "\nResponse RAW:\n" . htmlspecialchars($response);
|
|
echo "</pre>";
|
|
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 "<pre><strong>Fehler beim automatischen Refresh:</strong>\n";
|
|
print_r($tokens);
|
|
echo "\nResponse RAW:\n" . htmlspecialchars($response);
|
|
echo "</pre>";
|
|
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
|
|
}
|
|
|
|
}
|
|
?>
|