| Server IP : 193.86.120.172 / Your IP : 216.73.216.215 Web Server : Apache/2.4.63 (Unix) System : Linux JServices 3.10.108 #86003 SMP Wed Oct 22 13:20:46 CST 2025 x86_64 User : kubec ( 1026) PHP Version : 8.2.28 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /volume1/web/kadernictvidenisa_cz/includes/ |
Upload File : |
<?php
// ============================================
// includes/calendar.php
// Microsoft Graph Calendar API
// ============================================
require_once __DIR__ . '/mailer.php';
function ms_get_graph_token(): string {
static $cache = null;
if ($cache !== null) return $cache;
$stmt = db()->prepare(
"SELECT access_token, expires_at, refresh_token FROM oauth_tokeny
WHERE provider = 'microsoft_graph' ORDER BY id DESC LIMIT 1"
);
$stmt->execute();
$row = $stmt->fetch();
if ($row && new DateTime() < new DateTime($row['expires_at'])) {
return $cache = $row['access_token'];
}
if ($row) {
$body = http_build_query([
'client_id' => MS_CLIENT_ID,
'client_secret' => MS_CLIENT_SECRET,
'grant_type' => 'refresh_token',
'refresh_token' => $row['refresh_token'],
'scope' => 'https://graph.microsoft.com/Calendars.ReadWrite offline_access',
]);
$ctx = stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded
",
'content' => $body,
'timeout' => 10,
]]);
$resp = file_get_contents(
'https://login.microsoftonline.com/' . MS_TENANT_ID . '/oauth2/v2.0/token',
false, $ctx
);
$data = json_decode($resp, true);
if (isset($data['access_token'])) {
$expires_at = date('Y-m-d H:i:s', time() + (int)$data['expires_in'] - 60);
db()->prepare("UPDATE oauth_tokeny SET access_token=?, refresh_token=?, expires_at=? WHERE provider='microsoft_graph'")
->execute([$data['access_token'], $data['refresh_token'] ?? $row['refresh_token'], $expires_at]);
return $cache = $data['access_token'];
}
}
throw new RuntimeException('Graph token není k dispozici. Spusťte znovu oauth-setup.php.');
}
function graph_vytvor_udalost(array $rez, string $sluzba_nazev): ?string {
try {
$access_token = ms_get_graph_token();
} catch (Exception $e) {
error_log('[Calendar] Token error: ' . $e->getMessage());
return null;
}
$salon = salon_nazev();
$adresa = nastaveni('salon_adresa', '');
$telefon = nastaveni('salon_telefon', '');
// Sestavení datetime v ISO 8601
$start = $rez['datum'] . 'T' . $rez['cas_od'] . ':00';
$end = $rez['datum'] . 'T' . $rez['cas_do'] . ':00';
$body_text = "Zákazník: {$rez['jmeno']} {$rez['prijmeni']}\n"
. "E-mail: {$rez['email']}\n"
. ($rez['telefon'] ? "Telefon: {$rez['telefon']}\n" : '')
. ($rez['poznamka'] ? "Poznámka: {$rez['poznamka']}\n" : '')
. (isset($rez["cena"]) && $rez["cena"] !== null ? "Cena: {$rez['cena']} Kč" : "Cena: Individuální");
$event = [
'subject' => $sluzba_nazev . ' – ' . $rez['jmeno'] . ' ' . $rez['prijmeni'],
'body' => [
'contentType' => 'text',
'content' => $body_text,
],
'start' => [
'dateTime' => $start,
'timeZone' => 'Europe/Prague',
],
'end' => [
'dateTime' => $end,
'timeZone' => 'Europe/Prague',
],
'location' => $adresa ? ['displayName' => $adresa] : null,
'reminderMinutesBeforeStart' => 30,
'isReminderOn' => true,
'showAs' => 'busy',
'categories' => ['Kadeřnictví'],
];
// Odstraň null hodnoty
$event = array_filter($event, function($v){ return $v !== null; });
$url = 'https://graph.microsoft.com/v1.0/me/events';
$response = graph_request('POST', $url, $access_token, $event);
if (isset($response['id'])) {
return $response['id'];
}
error_log('[Calendar] Vytvoření události selhalo: ' . json_encode($response));
return null;
}
function graph_aktualizuj_udalost(string $event_id, array $rez, string $sluzba_nazev): bool {
try {
$access_token = ms_get_graph_token();
} catch (Exception $e) {
error_log('[Calendar] Token error: ' . $e->getMessage());
return false;
}
$start = $rez['datum'] . 'T' . $rez['cas_od'] . ':00';
$end = $rez['datum'] . 'T' . $rez['cas_do'] . ':00';
$body_text = "Zákazník: {$rez['jmeno']} {$rez['prijmeni']}\n"
. "E-mail: {$rez['email']}\n"
. ($rez['telefon'] ? "Telefon: {$rez['telefon']}\n" : '')
. ($rez['poznamka'] ? "Poznámka: {$rez['poznamka']}\n" : '')
. (isset($rez["cena"]) && $rez["cena"] !== null ? "Cena: {$rez['cena']} Kč" : "Cena: Individuální");
$event = [
'subject' => $sluzba_nazev . ' – ' . $rez['jmeno'] . ' ' . $rez['prijmeni'],
'body' => [
'contentType' => 'text',
'content' => $body_text,
],
'start' => [
'dateTime' => $start,
'timeZone' => 'Europe/Prague',
],
'end' => [
'dateTime' => $end,
'timeZone' => 'Europe/Prague',
],
];
$url = 'https://graph.microsoft.com/v1.0/me/events/' . urlencode($event_id);
$response = graph_request('PATCH', $url, $access_token, $event);
return isset($response['id']);
}
function graph_zrus_udalost(string $event_id): bool {
try {
$access_token = ms_get_graph_token();
} catch (Exception $e) {
error_log('[Calendar] Token error: ' . $e->getMessage());
return false;
}
$url = 'https://graph.microsoft.com/v1.0/me/events/' . urlencode($event_id);
$response = graph_request('DELETE', $url, $access_token);
return $response === null; // DELETE vrací 204 No Content = null
}
function graph_request(string $method, string $url, string $token, ?array $body = null): ?array {
$opts = [
'http' => [
'method' => $method,
'header' => "Authorization: Bearer {$token}\r\n"
. "Content-Type: application/json\r\n"
. "Accept: application/json\r\n",
'timeout' => 15,
'ignore_errors' => true,
]
];
if ($body !== null) {
$opts['http']['content'] = json_encode($body);
}
$ctx = stream_context_create($opts);
$result = file_get_contents($url, false, $ctx);
$headers = $http_response_header ?? [];
$status = 200;
foreach ($headers as $h) {
if (preg_match('/HTTP\/\d\.\d\s+(\d+)/', $h, $m)) {
$status = (int) $m[1];
}
}
if ($status === 204) return null; // No Content (DELETE úspěch)
if ($result === false || $result === '') return [];
return json_decode($result, true) ?? [];
}