| Server IP : 188.114.96.3 / Your IP : 216.73.216.143 Web Server : Apache System : Linux sv058.home.net.pl 6.8.2 husak_dirty_pages_fix #1 SMP PREEMPT_DYNAMIC Tue Apr 2 22:06:51 CEST 2024 x86_64 User : ( 3194769) PHP Version : 8.5.12 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /polana/includes/ |
Upload File : |
<?php
declare(strict_types=1);
/**
* TKLM Polana V3: ntfy and Google Calendar iCal subscription helpers.
* No guest personal details are sent to ntfy / Google Calendar.
*/
function polana_integration_save(string $key, string $value): void
{
// Only invoke from authenticated administrator form with CSRF.
$stmt = db()->prepare('INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) '
. 'ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value)');
$stmt->execute([$key, $value]);
}
function polana_calendar_feed_token(): string
{
return setting('polana_calendar_ics_token', '');
}
function polana_ntfy_publish(string $title, string $message, string $clickUrl = ''): void
{
$base = trim(setting('polana_ntfy_server', ''));
$topic = trim(setting('polana_ntfy_topic', ''));
$encryptedToken = setting('polana_ntfy_token', '');
$token = $encryptedToken !== '' ? smtp_decrypt_password($encryptedToken) : '';
if ($base === '' || $topic === '') throw new RuntimeException('Brak serwera ntfy lub tematu.');
if (!preg_match('~^[a-zA-Z0-9_-]{1,64}$~D', $topic)) {
throw new RuntimeException('Temat ntfy ma nieprawidłowy format.');
}
// Strict HTTPS: no credentials, paths, query strings or fragments.
$parts = parse_url($base);
if (!is_array($parts) || ($parts['scheme'] ?? '') !== 'https'
|| !isset($parts['host']) || isset($parts['user']) || isset($parts['pass'])
|| isset($parts['query']) || isset($parts['fragment'])
|| !in_array($parts['path'] ?? '', ['', '/'], true)
|| strlen($base) > 240) {
throw new RuntimeException('Serwer ntfy musi być adresem bazowym HTTPS bez ścieżki ani parametrów.');
}
$host = strtolower((string)$parts['host']);
if (in_array($host, ['localhost', '127.0.0.1', '::1'], true)) {
throw new RuntimeException('Nie używaj localhost do ntfy; ustaw domenę HTTPS.');
}
$url = rtrim($base, '/') . '/' . rawurlencode($topic);
$headers = [
'Content-Type: text/plain; charset=utf-8',
'Title: ' . preg_replace('/[^\x20-\x7E]/', '', $title),
'Priority: 4',
'Tags: calendar',
];
if ($token !== '') $headers[] = 'Authorization: Bearer ' . $token;
if ($clickUrl !== '' && str_starts_with($clickUrl, 'https://')) {
$headers[] = 'Click: ' . $clickUrl;
}
if (function_exists('curl_init')) {
$ch = curl_init($url);
if (!$ch) throw new RuntimeException('Nie udało się zainicjować żądania ntfy.');
try {
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $message,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 5,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
if (defined('CURLOPT_PROTOCOLS')) curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
$response = curl_exec($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false || $status < 200 || $status >= 300) {
throw new RuntimeException('Serwer ntfy nie zaakceptował żądania (HTTP ' . $status . ').');
}
} finally {
curl_close($ch);
}
} else {
// Hosting without cURL: native PHP HTTPS HTTP stream, no redirects.
$context = stream_context_create([
'http' => [
'method' => 'POST', 'header' => implode("\r\n", $headers) . "\r\n",
'content' => $message, 'timeout' => 5, 'ignore_errors' => true,
'follow_location' => 0, 'max_redirects' => 0,
],
'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
]);
$response = @file_get_contents($url, false, $context);
$responseHeaders = $http_response_header ?? [];
$status = preg_match('~^HTTP/\S+\s+(\d{3})~', (string)($responseHeaders[0] ?? ''), $match)
? (int)$match[1] : 0;
if ($response === false || $status < 200 || $status >= 300) {
throw new RuntimeException('Serwer ntfy nie zaakceptował żądania (HTTP ' . $status . ').');
}
}
}
function polana_ntfy_reservation(array $reservation, string $event = 'new'): void
{
if (setting('polana_ntfy_enabled', '0') !== '1') return;
$status = (string)($reservation['status'] ?? 'new');
$date = (string)($reservation['reservation_date'] ?? '');
$time = substr((string)($reservation['reservation_time'] ?? ''), 0, 5);
$guests = max(1, (int)($reservation['guests'] ?? 1));
$code = (string)($reservation['reservation_code'] ?? '');
$title = $event === 'new' ? 'Moja Polana - nowa rezerwacja' : 'Moja Polana - zmiana rezerwacji';
$body = ($event === 'new' ? 'Nowa prosba o rezerwacje' : 'Zmieniono status: ' . reservation_status_label($status))
. "\nTermin: " . $date . ' ' . $time
. "\nOsoby: " . $guests
. "\nKod: " . $code;
$click = site_url('admin/reservation.php?id=' . (int)($reservation['id'] ?? 0));
polana_ntfy_publish($title, $body, $click);
}
function polana_ics_escape(string $text): string
{
return str_replace(["\\", ";", ",", "\r\n", "\n", "\r"],
["\\\\", "\\;", "\\,", "\\n", "\\n", "\\n"], $text);
}
function polana_ics_fold(string $line): string
{
// 75-byte maximum octets, folding at valid UTF-8 boundaries.
$out = '';
while (strlen($line) > 73) {
$chunk = function_exists('mb_strcut') ? mb_strcut($line, 0, 73, 'UTF-8') : substr($line, 0, 73);
$out .= $chunk . "\r\n ";
$line = substr($line, strlen($chunk));
}
return $out . $line . "\r\n";
}