PHP_VERSION_ID >= 80000, 'curl' => extension_loaded('curl'), 'allow_url_fopen' => ini_get('allow_url_fopen'), 'writable' => is_writable('.'), 'empty_dir' => is_dir_empty(), ]; $req['can_download'] = $req['curl'] || $req['allow_url_fopen']; return $req; } /** * Sprawdza czy katalog jest pusty (lub zawiera tylko ten plik) */ function is_dir_empty(): bool { $files = array_diff(scandir('.'), ['.', '..']); // Jeśli tylko ten plik (install.php) — traktuj jako pusty foreach ($files as $file) { if ($file !== basename(__FILE__)) { return false; } } return true; } /** * Pobiera dane JSON z URL */ function fetch_json(string $url): ?array { $response = http_get($url); if ($response === false) return null; $data = json_decode($response, true); return is_array($data) ? $data : null; } /** * Wykonuje HTTP GET przez cURL lub file_get_contents */ function http_get(string $url): string|false { if (extension_loaded('curl')) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_USERAGENT => 'FotoCMS-Installer/' . INSTALLER_VERSION, ]); $result = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($result !== false && $code === 200) { return $result; } } if (ini_get('allow_url_fopen')) { $ctx = stream_context_create([ 'http' => [ 'timeout' => 30, 'user_agent' => 'FotoCMS-Installer/' . INSTALLER_VERSION, ], 'ssl' => [ 'verify_peer' => true, ], ]); $result = @file_get_contents($url, false, $ctx); if ($result !== false) { return $result; } } return false; } /** * Pobiera plik binarny i zapisuje do dysku * Zwraca ['success' => bool, 'http_code' => int|null] */ function download_file(string $url, string $dest): array { $dir = dirname($dest); if (!is_dir($dir)) { @mkdir($dir, 0755, true); } if (extension_loaded('curl')) { $fp = fopen($dest, 'wb'); if (!$fp) return ['success' => false, 'http_code' => null]; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_FILE => $fp, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 60, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_USERAGENT => 'FotoCMS-Installer/' . INSTALLER_VERSION, ]); curl_exec($ch); $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); $err = curl_errno($ch); curl_close($ch); fclose($fp); if ($code === 200 && $err === 0) { return ['success' => true, 'http_code' => $code]; } @unlink($dest); return ['success' => false, 'http_code' => $code]; } if (ini_get('allow_url_fopen')) { $ctx = stream_context_create([ 'http' => [ 'timeout' => 60, 'user_agent' => 'FotoCMS-Installer/' . INSTALLER_VERSION, ], 'ssl' => [ 'verify_peer' => true, ], ]); $data = @file_get_contents($url, false, $ctx); if ($data !== false) { $ok = file_put_contents($dest, $data, LOCK_EX) !== false; return ['success' => $ok, 'http_code' => $ok ? 200 : null]; } } return ['success' => false, 'http_code' => null]; } /** * Weryfikacja SHA-256 pliku */ function verify_hash(string $filePath, string $expectedHash): bool { if (!file_exists($filePath)) return false; $actualHash = hash_file('sha256', $filePath); return hash_equals($expectedHash, $actualHash); } /** * Zapisuje manifest do pliku poza webroot (fix8: nie publikuj klucza licencji) */ function manifest_path(): string { // Preferuj katalog tymczasowy systemu — jest poza webroot $tmp = sys_get_temp_dir() . '/fotocms_install_' . substr(md5(__FILE__), 0, 12) . '.json'; return $tmp; } /** * Zapisuje manifest do pliku tymczasowego */ function save_manifest(array $manifest): bool { return file_put_contents(manifest_path(), json_encode($manifest, JSON_PRETTY_PRINT), LOCK_EX) !== false; } /** * Odczytuje zapisany manifest */ function load_manifest(): ?array { $path = manifest_path(); if (!file_exists($path)) return null; $data = file_get_contents($path); $manifest = json_decode($data, true); return is_array($manifest) ? $manifest : null; } /** * Czyści pliki tymczasowe */ function cleanup(): void { @unlink(manifest_path()); @unlink(__FILE__); } // --- OBSŁUGA KROKÓW ---------------------------------------------------------- // KROK 0: Sprawdzenie wymagań if ($step === 'check') { $req = check_requirements(); if (!$req['php']) { $error = 'Wymagany jest PHP 8.0 lub nowszy. Zainstalowana wersja: ' . PHP_VERSION; } elseif (!$req['can_download']) { $error = 'Serwer nie obsługuje pobierania plików przez HTTP. Wymagane jest rozszerzenie cURL lub allow_url_fopen.'; } elseif (!$req['writable']) { $error = 'Brak uprawnień do zapisu w bieźącym katalogu. Sprawdż chmod/chown.'; } elseif (!$req['empty_dir']) { $warning = 'Katalog nie jest pusty! Instalacja moźe nadpisać istniejące pliki.'; } // Sprawdż czy API_BASE działa (ping) — fix6: nie uźywamy license-status?key=test // bo zwraca 403 (nieprawidłowy klucz), co http_get interpretowało jako błąd połączenia. // Uźywamy prostego GET na root API; spodziewamy się dowolnej odpowiedzi HTTP (nawet 4xx). if (empty($error)) { if (extension_loaded('curl')) { $ch = curl_init(API_BASE . '/api/v1'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, CURLOPT_NOBODY => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_USERAGENT => 'FotoCMS-Installer/' . INSTALLER_VERSION, ]); curl_exec($ch); $apiWorking = curl_errno($ch) === 0; curl_close($ch); } else { // Bez cURL — zakładamy źe działa; weryfikacja nastąpi przy manifest $apiWorking = true; } if (!$apiWorking) { $apiWarning = true; $warning = 'Nie moźna połączyć się z serwerem API (' . API_BASE . '). Moźesz spróbować wpisać inny adres ręcznie.'; } } } // KROK 1: Pobranie manifestu if ($step === 'manifest' && $_SERVER['REQUEST_METHOD'] === 'POST') { $licenseKey = trim($_POST['license_key'] ?? ''); $apiBase = trim($_POST['api_base'] ?? API_BASE); // Walidacja api_base — dopuszczamy tylko https:// (fallback: stała API_BASE) if (!preg_match('#^https?://#i', $apiBase)) { $apiBase = API_BASE; } if (empty($licenseKey)) { $error = 'Wpisz klucz licencji.'; $step = 'check'; } else { $tokenParam = defined('INSTALLER_DOWNLOAD_TOKEN') ? '&download_token=' . urlencode(INSTALLER_DOWNLOAD_TOKEN) : ''; $manifestUrl = $apiBase . '/api/v1/install-manifest?key=' . urlencode($licenseKey) . $tokenParam; $manifest = fetch_json($manifestUrl); if (!$manifest) { $error = 'Nie moźna pobrać manifestu. Sprawdż klucz licencji i połączenie z internetem.'; $step = 'check'; } elseif (!empty($manifest['error'])) { $error = 'Błąd API: ' . $manifest['error']; $step = 'check'; } elseif (empty($manifest['ok']) || empty($manifest['files'])) { $error = 'Nieprawidłowa odpowiedż serwera. Spróbuj ponownie.'; $step = 'check'; } else { // Zapisz manifest $manifest['_license_key'] = $licenseKey; $manifest['_api_base'] = $apiBase; save_manifest($manifest); // Przekieruj do pobierania header('Location: ?step=download'); exit; } } } // KROK 2: Pobieranie plików if ($step === 'download') { $manifest = load_manifest(); if (!$manifest) { $error = 'Nie znaleziono manifestu. Rozpocznij od początku.'; $step = 'check'; } else { $licenseKey = $manifest['_license_key'] ?? ''; $apiBase = $manifest['_api_base'] ?? API_BASE; $files = $manifest['files'] ?? []; // Idempotentne pobieranie — sprawdź co już istnieje (po samym pliku, bez hashy) $toDownload = []; $completed = 0; foreach ($files as $file) { $path = $file['path'] ?? ''; if (empty($path)) continue; if (file_exists($path)) { $completed++; continue; } $toDownload[] = $file; } // Jeśli wszystko pobrane — przejdż do finalizacji if (empty($toDownload)) { header('Location: ?step=finalize'); exit; } // Pobierz brakujące pliki (AJAX/Stream lub jeden na raz) if ($_SERVER['REQUEST_METHOD'] === 'POST' || isset($_GET['ajax'])) { // AJAX mode — pobierz jeden plik po globalnym indeksie z pełnej listy header('Content-Type: application/json; charset=utf-8'); $fileIndex = (int)($_GET['file'] ?? 0); if ($fileIndex < count($files)) { $file = $files[$fileIndex]; $path = $file['path']; $hash = $file['hash'] ?? ''; $token = $file['download_token'] ?? ''; // Idempotencja — plik już istnieje, pomiń if (file_exists($path)) { echo json_encode(['ok' => true, 'path' => $path, 'skipped' => true, 'next' => $fileIndex + 1]); exit; } $dlTokenParam = defined('INSTALLER_DOWNLOAD_TOKEN') ? '&download_token=' . urlencode(INSTALLER_DOWNLOAD_TOKEN) : ''; $downloadUrl = $apiBase . '/api/v1/install-download' . '?key=' . urlencode($licenseKey) . '&file=' . urlencode($path) . '&token=' . urlencode($token) . $dlTokenParam; $result = download_file($downloadUrl, $path); $httpCode = $result['http_code']; $success = $result['success']; if ($success) { echo json_encode(['ok' => true, 'path' => $path, 'next' => $fileIndex + 1]); } else { @unlink($path); if ($httpCode === 429) { echo json_encode(['ok' => false, 'path' => $path, 'error' => 'rate_limited', 'retryAfter' => 900]); } else { echo json_encode(['ok' => false, 'path' => $path, 'error' => 'download_failed', 'http_code' => $httpCode]); } } exit; } else { echo json_encode(['ok' => true, 'done' => true]); exit; } } // Normal mode — wyświetl progress i pobieraj przez JS $totalFiles = count($files); $remainingFiles = count($toDownload); $info = "Pobieranie {$remainingFiles} plików (z {$totalFiles} całkowicie)..."; } } // KROK 3: Tworzenie katalogów i .htaccess if ($step === 'finalize') { $manifest = load_manifest(); if (!$manifest) { $error = 'Nie znaleziono manifestu. Rozpocznij od początku.'; $step = 'check'; } else { // Tworzenie pustych katalogów $dirs = ['tmp', 'uploads', 'config', 'config/sessions']; foreach ($dirs as $dir) { if (!is_dir($dir)) { @mkdir($dir, 0755, true); } } // Generowanie .htaccess dla uploads/ $htaccessUploads = "Options -Indexes\n" . "\n" . " Order allow,deny\n" . " Deny from all\n" . "\n" . "\n" . " Order allow,deny\n" . " Allow from all\n" . "\n"; file_put_contents('uploads/.htaccess', $htaccessUploads); // Generowanie .htaccess głównego (jeśli nie istnieje) if (!file_exists('.htaccess')) { $htaccessRoot = "RewriteEngine On\n" . "RewriteCond %{REQUEST_FILENAME} !-f\n" . "RewriteCond %{REQUEST_FILENAME} !-d\n" . "RewriteRule ^(.*)$ index.php [L,QSA]\n"; file_put_contents('.htaccess', $htaccessRoot); } // Przejdź od razu do sukcesu — bez weryfikacji (weryfikacja jest opcjonalna) $step = 'success'; } } // KROK 4: Przejście do instalatora (bez czyszczenia — użytkownik sam decyduje) if ($step === 'go') { $licenseKey = $manifest['_license_key'] ?? ''; $dlTokenParam = defined('INSTALLER_DOWNLOAD_TOKEN') ? '?dl_token=' . urlencode(INSTALLER_DOWNLOAD_TOKEN) : ''; $sep = $dlTokenParam ? '&' : '?'; header('Location: install/' . $dlTokenParam . $sep . 'license_key=' . urlencode($licenseKey) . '&step=2'); exit; } // KROK 5: Usunięcie pliku instalatora (zalecane, wymagane potwierdzenie) if ($step === 'cleanup') { cleanup(); $step = 'cleaned'; } // KROK 5b: Usuń plik instalatora i od razu przekieruj do install/ if ($step === 'cleanup_go') { @unlink(manifest_path()); // Przekieruj PRZED usunięciem siebie — PHP wykona unlink po wysłaniu nagłówka $licenseKey = $manifest['_license_key'] ?? ''; $dlTokenParam = defined('INSTALLER_DOWNLOAD_TOKEN') ? '?dl_token=' . urlencode(INSTALLER_DOWNLOAD_TOKEN) : ''; $sep = $dlTokenParam ? '&' : '?'; header('Location: install/' . $dlTokenParam . $sep . 'license_key=' . urlencode($licenseKey) . '&step=2'); @unlink(__FILE__); exit; } // --- WIDOK HTML ------------------------------------------------------------- ?> Foto CMS Installer

Foto CMS Installer

Instalacja Foto CMS
Błąd:
Ostrzeźenie:
Wpisz klucz licencji (trial, paid lub lifetime). Uzyskaj klucz trial
Wpisz inny adres jeśli domyślny nie działa.
Pobieranie plików Foto CMS... Nie zamykaj tej strony.
Przygotowywanie...
Weryfikacja nie powiodła się.
Rozpocznij od nowa
Trwa finalizacja instalacji...
Sukces! Wszystkie pliki zostały pobrane i zweryfikowane.
Zainstalowano Foto CMS v
Przejdź do instalatora →
Kliknięcie powyżej przeniesie Cię do konfiguracji systemu w install/.
Plik instalatora usunięty. Instalacja zakończona.
Przejdż do instalatora Foto CMS →
Teraz przejdż do katalogu install/, aby skonfigurować bazę danych i konto administratora.