403Webshell
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 :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /polana/includes/polana_gcal.php
<?php
declare(strict_types=1);
/** POLANA V17.3: private Google Calendar OAuth2 + idempotent reservation sync, no Composer dependency. */

function pg173_private_dir(): string {
    // V18.16: existing Google Calendar secrets always take precedence.
    $legacy = dirname(__DIR__, 2) . '/.polana_gcal_private';
    if (is_file($legacy . '/oauth.enc') || is_file($legacy . '/encryption.key')) return $legacy;
    require_once __DIR__ . '/polana_backup_v182.php';
    if (!function_exists('pbk_private')) throw new RuntimeException('Brak bezpiecznej lokalizacji danych Google Calendar.');
    $base = pbk_private(); // configured private storage, already verified outside WWW
    $target = $base . '/gcal-oauth';
    if (is_link($target)) throw new RuntimeException('Niedozwolony link symboliczny katalogu Google Calendar.');
    return $target;
}
function pg173_init_private(): string {
    $dir = pg173_private_dir();
    if (!is_dir($dir) && !@mkdir($dir, 0700, true)) throw new RuntimeException('Nie można utworzyć prywatnego folderu poza WWW.');
    if (!is_dir($dir) || !is_writable($dir)) throw new RuntimeException('Prywatny folder jest niedostępny.');
    @chmod($dir, 0700);
    // Defence in depth if the hosting also exposes a parent folder via a different virtual host.
    $deny = $dir . '/.htaccess';
    if (!is_file($deny)) { @file_put_contents($deny, "Require all denied\nDeny from all\n", LOCK_EX); @chmod($deny, 0600); }
    return $dir;
}
function pg173_key(): string {
    $dir = pg173_init_private();
    $file = $dir . '/encryption.key';
    if (!is_file($file)) {
        $key = random_bytes(32);
        $handle = @fopen($file, 'x');
        if (!$handle) throw new RuntimeException('Nie można zapisać klucza szyfrowania.');
        try { if (fwrite($handle, $key) !== 32) throw new RuntimeException('Błąd zapisu klucza.'); }
        finally { fclose($handle); @chmod($file, 0600); }
    }
    $key = @file_get_contents($file);
    if (!is_string($key) || strlen($key) !== 32) throw new RuntimeException('Nieprawidłowy klucz szyfrowania.');
    return $key;
}
function pg173_read_secrets(): array {
    $file = pg173_private_dir() . '/oauth.enc';
    if (!is_file($file)) return [];
    if (!function_exists('openssl_decrypt')) throw new RuntimeException('Brak rozszerzenia OpenSSL na hostingu.');
    $blob = json_decode((string)file_get_contents($file), true);
    if (!is_array($blob) || !isset($blob['nonce'], $blob['tag'], $blob['cipher'])) throw new RuntimeException('Konfiguracja Google jest uszkodzona.');
    $nonce = base64_decode((string)$blob['nonce'], true);
    $tag = base64_decode((string)$blob['tag'], true);
    $cipher = base64_decode((string)$blob['cipher'], true);
    if ($nonce === false || $tag === false || $cipher === false) throw new RuntimeException('Niepoprawny zapis konfiguracji Google.');
    $plain = openssl_decrypt($cipher, 'aes-256-gcm', pg173_key(), OPENSSL_RAW_DATA, $nonce, $tag, 'polana-gcal-v173');
    if (!is_string($plain)) throw new RuntimeException('Nie można odszyfrować Google OAuth.');
    $data = json_decode($plain, true);
    if (!is_array($data)) throw new RuntimeException('Nieprawidłowy JSON konfiguracji.');
    return $data;
}
function pg173_write_secrets(array $data): void {
    if (!function_exists('openssl_encrypt')) throw new RuntimeException('Brak OpenSSL, nie można bezpiecznie zapisać Google OAuth.');
    $dir = pg173_init_private();
    $nonce = random_bytes(12); $tag = '';
    $plain = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
    $cipher = openssl_encrypt($plain, 'aes-256-gcm', pg173_key(), OPENSSL_RAW_DATA, $nonce, $tag, 'polana-gcal-v173', 16);
    if (!is_string($cipher)) throw new RuntimeException('Nie udało się zaszyfrować danych Google.');
    $encoded = json_encode(['nonce'=>base64_encode($nonce),'tag'=>base64_encode($tag),'cipher'=>base64_encode($cipher)], JSON_THROW_ON_ERROR);
    $tmp = $dir . '/.oauth-' . bin2hex(random_bytes(6)) . '.tmp';
    if (@file_put_contents($tmp, $encoded, LOCK_EX) !== strlen($encoded)) { @unlink($tmp); throw new RuntimeException('Brak zapisu konfiguracji Google.'); }
    @chmod($tmp, 0600);
    if (!@rename($tmp, $dir . '/oauth.enc')) { @unlink($tmp); throw new RuntimeException('Błąd przenoszenia zaszyfrowanej konfiguracji.'); }
}
function pg173_callback_url(): string { return 'https://mojapolanasmakow.pl/admin/polana-kalendarz.php'; }
function pg173_calendar_id(): string { return trim(setting('pg173_calendar_id', 'primary')) ?: 'primary'; }
function pg173_duration(): int { return max(30, min(360, (int)setting('pg173_duration_minutes', '90'))); }
function pg173_enabled(): bool { return setting('pg173_enabled', '1') === '1'; }
function pg173_connected(): bool {
    try { $data = pg173_read_secrets(); return !empty($data['refresh_token']) && !empty($data['client_id']) && !empty($data['client_secret']); }
    catch (Throwable $e) { return false; }
}
function pg173_upsert_setting(string $key, string $value): void {
    $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 pg173_ensure_schema(): void {
    db()->exec("CREATE TABLE IF NOT EXISTS polana_calendar_events (
        reservation_id INT UNSIGNED NOT NULL PRIMARY KEY,
        calendar_id VARCHAR(255) NOT NULL,
        event_id VARCHAR(128) NOT NULL,
        sync_status VARCHAR(20) NOT NULL DEFAULT 'pending',
        last_error VARCHAR(500) DEFAULT NULL,
        last_synced_at DATETIME DEFAULT NULL,
        updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_pg173_status (sync_status)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci");
}
function pg173_http(string $method, string $url, ?array $body=null, array $headers=[]): array {
    if (!function_exists('curl_init')) throw new RuntimeException('Hosting nie ma rozszerzenia PHP cURL.');
    $ch = curl_init($url);
    $h = array_merge(['Accept: application/json'], $headers);
    $opts = [CURLOPT_RETURNTRANSFER=>true,CURLOPT_FOLLOWLOCATION=>false,CURLOPT_TIMEOUT=>10,CURLOPT_CONNECTTIMEOUT=>4,
        CURLOPT_CUSTOMREQUEST=>$method,CURLOPT_HTTPHEADER=>$h,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2];
    if ($body !== null) { $opts[CURLOPT_POSTFIELDS] = json_encode($body, JSON_THROW_ON_ERROR); $h[] = 'Content-Type: application/json'; $opts[CURLOPT_HTTPHEADER] = $h; }
    curl_setopt_array($ch, $opts);
    $raw = curl_exec($ch);
    $errno = curl_errno($ch); $err = curl_error($ch); $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if ($raw === false || $errno !== 0) throw new RuntimeException('Połączenie Google: ' . substr($err,0,170));
    $decoded = json_decode((string)$raw, true);
    return [$status, is_array($decoded) ? $decoded : []];
}
function pg173_oauth_token_request(array $fields): array {
    if (!function_exists('curl_init')) throw new RuntimeException('Brak PHP cURL.');
    $ch = curl_init('https://oauth2.googleapis.com/token');
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>http_build_query($fields),
        CURLOPT_HTTPHEADER=>['Content-Type: application/x-www-form-urlencoded','Accept: application/json'],
        CURLOPT_TIMEOUT=>20,CURLOPT_CONNECTTIMEOUT=>8,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_FOLLOWLOCATION=>false]);
    $raw = curl_exec($ch); $status=(int)curl_getinfo($ch,CURLINFO_RESPONSE_CODE); $err=curl_error($ch);curl_close($ch);
    if ($raw === false) throw new RuntimeException('Brak połączenia z Google OAuth: '.substr($err,0,140));
    $data=json_decode((string)$raw,true);
    if ($status!==200 || !is_array($data) || empty($data['access_token'])) {
        $why = is_array($data) ? (string)($data['error_description'] ?? $data['error'] ?? 'błąd Google') : 'błąd odpowiedzi';
        throw new RuntimeException('Google OAuth: '.substr($why,0,220));
    }
    return $data;
}
function pg173_access_token(bool $forceRefresh=false): string {
    $s=pg173_read_secrets();
    if (!$forceRefresh && !empty($s['access_token']) && (int)($s['expires_at']??0)>time()+80) return (string)$s['access_token'];
    if (empty($s['refresh_token']) || empty($s['client_id']) || empty($s['client_secret'])) throw new RuntimeException('Połącz konto Google w panelu kalendarza.');
    $new=pg173_oauth_token_request(['client_id'=>$s['client_id'],'client_secret'=>$s['client_secret'],
        'refresh_token'=>$s['refresh_token'],'grant_type'=>'refresh_token']);
    $s['access_token']=(string)$new['access_token']; $s['expires_at']=time()+max(60,(int)($new['expires_in']??3600));
    if (!empty($new['refresh_token'])) $s['refresh_token']=(string)$new['refresh_token'];
    pg173_write_secrets($s);
    return $s['access_token'];
}
function pg173_api(string $method, string $route, ?array $body=null): array {
    if (!str_starts_with($route,'/calendar/v3/')) throw new RuntimeException('Niedozwolona ścieżka Google.');
    $url='https://www.googleapis.com'.$route;
    [$code,$data]=pg173_http($method,$url,$body,['Authorization: Bearer '.pg173_access_token()]);
    if ($code===401) [$code,$data]=pg173_http($method,$url,$body,['Authorization: Bearer '.pg173_access_token(true)]);
    return [$code,$data];
}
function pg173_throw_api(int $status, array $data): never {
    $msg=(string)($data['error']['message']??$data['error_description']??'Google nie przyjął operacji.');
    throw new RuntimeException('Kalendarz Google HTTP '.$status.': '.mb_substr($msg,0,230,'UTF-8'));
}
function pg173_event_id(int $reservationId): string {
    // Deterministic, valid Google Calendar base32hex event ID (a-v and 0-9 only).
    return 'pola'.substr(hash('sha256','mojapolanasmakow.pl:reservation:'.$reservationId),0,36);
}
/** POLANA_GCAL_COLOR_V1817: indywidualne, stabilne kolory wydarzen. */
function pg173_event_color_id(array $reservation): string {
    // Google Calendar event.colorId: 1..11. Kolejnosc mieszana, nie losowana ponownie.
    $palette = ['9','2','10','5','11','7','6','4','3','1','8'];
    $id = (int)($reservation['id'] ?? 0);
    $date = (string)($reservation['reservation_date'] ?? '');
    $time = (string)($reservation['reservation_time'] ?? '00:00:00');
    $start = static function(string $value): int {
        $parts = explode(':', $value);
        return ((int)($parts[0] ?? 0) * 3600) + ((int)($parts[1] ?? 0) * 60) + (int)($parts[2] ?? 0);
    };
    $initial = static function(int $reservationId) use ($date, $palette): int {
        return hexdec(substr(hash('sha256', 'polana-gcal-color-1:'.$date.':'.$reservationId), 0, 7)) % count($palette);
    };
    $fallback = $palette[$initial($id)];
    if ($id <= 0 || !preg_match('/^\d{4}-\d{2}-\d{2}$/D', $date)) return $fallback;
    try {
        // Algorytm przydziela kolory wg rosnacego ID, by nowa rezerwacja nie
        // wymuszala zmiany kolorow wczesniej utworzonych rezerwacji.
        $q = db()->prepare("SELECT id, reservation_time FROM reservations WHERE reservation_date=? AND status='confirmed' AND id<=? ORDER BY id ASC LIMIT 1000");
        $q->execute([$date, $id]);
        $rows = $q->fetchAll(PDO::FETCH_ASSOC);
        if (!is_array($rows)) return $fallback;
        $duration = pg173_duration() * 60;
        $assigned = [];
        $found = false;
        foreach ($rows as $row) {
            $otherId = (int)($row['id'] ?? 0);
            $otherStart = $start((string)($row['reservation_time'] ?? '00:00:00'));
            $otherEnd = $otherStart + $duration;
            $used = [];
            foreach ($assigned as $previous) {
                if ($previous['start'] < $otherEnd && $previous['end'] > $otherStart) {
                    $used[$previous['color']] = true;
                }
            }
            $base = $initial($otherId);
            $chosen = $palette[$base];
            for ($step = 0; $step < count($palette); $step++) {
                $candidate = $palette[($base + $step) % count($palette)];
                if (!isset($used[$candidate])) { $chosen = $candidate; break; }
            }
            $assigned[] = ['start'=>$otherStart, 'end'=>$otherEnd, 'color'=>$chosen];
            if ($otherId === $id) { $fallback = $chosen; $found = true; break; }
        }
        // Bez wymuszonej modyfikacji innych wydarzen i bez zmian schematu SQL.
        return $found ? $fallback : $palette[$initial($id)];
    } catch (Throwable $ignored) {
        // Problem zapytania o kolory nie moze zatrzymac synchronizacji kalendarza.
        return $fallback;
    }
}
function pg173_event_payload(array $r): array {
    $tz=new DateTimeZone('Europe/Warsaw');
    $when=DateTimeImmutable::createFromFormat('!Y-m-d H:i:s', (string)$r['reservation_date'].' '.(string)$r['reservation_time'], $tz);
    if (!$when) throw new RuntimeException('Nieprawidłowa data lub godzina rezerwacji.');
    $end=$when->modify('+'.pg173_duration().' minutes');
    $name=trim((string)$r['guest_name']);
    $description="Rezerwacja potwierdzona — Moja Polana Smaków\n".
        'Kod: '.(string)$r['reservation_code']."\n".
        'Gość: '.$name."\n".
        'Liczba osób: '.(int)$r['guests']."\n".
        'Panel: https://mojapolanasmakow.pl/admin/reservation.php?id='.(int)$r['id'];
    if (setting('pg173_include_contact','0')==='1') {
        $description.="\nTelefon: ".(string)$r['phone']."\nE-mail: ".(string)$r['email'];
    }
    return [
        'id'=>pg173_event_id((int)$r['id']),
        'colorId'=>pg173_event_color_id($r),
        'summary'=>'Stolik: '.$name.' · '.(int)$r['guests'].' os.',
        'description'=>$description,
        'location'=>'Moja Polana Smaków, ul. Bożego Ciała 15, Kraków',
        'start'=>['dateTime'=>$when->format('c'),'timeZone'=>'Europe/Warsaw'],
        'end'=>['dateTime'=>$end->format('c'),'timeZone'=>'Europe/Warsaw'],
        'transparency'=>'opaque',
        'visibility'=>'default',
        'reminders'=>['useDefault'=>true],
        'extendedProperties'=>['private'=>['polana_reservation_id'=>(string)$r['id'],'polana_code'=>(string)$r['reservation_code']]],
    ];
}
function pg173_row(int $id): ?array {
    $s=db()->prepare('SELECT * FROM polana_calendar_events WHERE reservation_id=? LIMIT 1');
    $s->execute([$id]);$v=$s->fetch(PDO::FETCH_ASSOC);return $v?:null;
}
function pg173_store(int $id, string $calendar, string $event, string $status, ?string $error=null): void {
    $stmt=db()->prepare('INSERT INTO polana_calendar_events(reservation_id,calendar_id,event_id,sync_status,last_error,last_synced_at) VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE calendar_id=VALUES(calendar_id),event_id=VALUES(event_id),sync_status=VALUES(sync_status),last_error=VALUES(last_error),last_synced_at=VALUES(last_synced_at)');
    $stmt->execute([$id,$calendar,$event,$status,$error,$status==='synced'?date('Y-m-d H:i:s'):null]);
}
function pg173_delete_remote(string $calendar,string $event): void {
    [$code,$data]=pg173_api('DELETE','/calendar/v3/calendars/'.rawurlencode($calendar).'/events/'.rawurlencode($event));
    if (!in_array($code,[200,204,404,410],true))pg173_throw_api($code,$data);
}
function pg173_sync(int $reservationId): array {
    pg173_ensure_schema();
    $r=reservation_by_id($reservationId);
    if (!$r)throw new RuntimeException('Rezerwacja nie istnieje.');
    $prev=pg173_row($reservationId);
    $calendar=pg173_calendar_id();$event=pg173_event_id($reservationId);
    if (!pg173_enabled())return ['result'=>'disabled','message'=>'Integracja wyłączona.'];
    // 
    if (($r['status']??'')==='completed')return ['result'=>'skipped','message'=>'Zrealizowana — zachowano wydarzenie.'];
    $shouldExist=($r['status']??'')==='confirmed';
    if (!$shouldExist && !$prev)return ['result'=>'skipped','message'=>'Niepotwierdzona — brak wydarzenia.'];
    if (!pg173_connected()) {
        pg173_store($reservationId,$prev['calendar_id']??$calendar,$prev['event_id']??$event,'error','Nie połączono konta Google.');
        return ['result'=>'error','message'=>'Nie połączono konta Google.'];
    }
    try {
        if (!$shouldExist) {
            pg173_delete_remote((string)$prev['calendar_id'],(string)$prev['event_id']);
            $stmt=db()->prepare('DELETE FROM polana_calendar_events WHERE reservation_id=?');$stmt->execute([$reservationId]);
            return ['result'=>'deleted','message'=>'Usunięto wydarzenie z kalendarza.'];
        }
        if ($prev && $prev['calendar_id']!==$calendar) {
            pg173_delete_remote((string)$prev['calendar_id'],(string)$prev['event_id']);
        }
        $payload=pg173_event_payload($r);
        $path='/calendar/v3/calendars/'.rawurlencode($calendar).'/events';
        $target=$path.'/'.rawurlencode($event);
        // One request for first confirmation; idempotent 409 -> PATCH for repeated confirmation.
        [$code,$response]=pg173_api('POST',$path.'?sendUpdates=none',$payload);
        if ($code===409) { unset($payload['id']);[$code,$response]=pg173_api('PATCH',$target,$payload); }
        if (!in_array($code,[200,201],true))pg173_throw_api($code,$response);
        pg173_store($reservationId,$calendar,$event,'synced');
        return ['result'=>'synced','message'=>'Zapisano w Google Kalendarzu.'];
    } catch(Throwable $e) {
        // Keep old association if calendar migration fails.
        $keepOld = $prev && $prev['calendar_id']!==$calendar;
        pg173_store($reservationId,$keepOld?(string)$prev['calendar_id']:$calendar,$keepOld?(string)$prev['event_id']:$event,'error',mb_substr($e->getMessage(),0,450,'UTF-8'));
        return ['result'=>'error','message'=>$e->getMessage()];
    }
}
function pg173_after_change(int $id, ?int $adminId=null): void {
    try {
        $result=pg173_sync($id);
        if ($result['result']==='error') {
            reservation_add_event($id,'calendar_error','Kalendarz Google: '.mb_substr((string)$result['message'],0,300,'UTF-8'),$adminId);
        } elseif (in_array($result['result'],['synced','deleted'],true)) {
            reservation_add_event($id,'calendar','Kalendarz Google: '.$result['message'],$adminId);
        }
    } catch(Throwable $e) {
        error_log('[POLANA GCAL] #'.$id.': '.$e->getMessage());
        try {reservation_add_event($id,'calendar_error','Wymaga ręcznej synchronizacji kalendarza.',$adminId);}catch(Throwable $ignored){}
    }
}

Youez - 2016 - github.com/yon3zu
LinuXploit