<?php

/*
ET-Chat Copyright by SEDesign | ET-Chat v3.x.x Lizenz: CCPL | Autor: Evgeni Tcherkasski (info@s-e-d.de)
ET-Chat_T-FISH-MOD Weiterentwicklungen, Optimierungen und Ergänzungen am Code sind das geistige Eigentum von Tommy (Thomas Kuhn) und Harlekin (Bernd Witte).

Die darauf folgende Optimierungen und Ergänzungen am Code sind das Geistige Eigentum von Powermen (Christian K.) 
PHP 8/9 Ready, XML Parser, PDO Prepared Statements Get Set sicherheit usw.
*/

class AdminBackup extends DbConectionMaker
{
    protected $db;
    protected $prefix;
    protected $dbname;

    public function __construct()
    {
        parent::__construct();

        $desei = $this->dbObj->sqlGet(
            "SELECT etchat_config_id, etchat_config_style FROM {$this->_prefix}etchat_config WHERE etchat_config_id = '1'"
        );

        $configFile = dirname(dirname(__DIR__)) . "/config.php";
        if (!file_exists($configFile)) {
            die("FEHLER: config.php nicht gefunden!");
        }
        include $configFile;

        $this->prefix = $prefix;
        $this->dbname = $database;

        $this->db = $this->dbObj->getConnection();

        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        if (empty($_SESSION["etchat_" . $this->prefix . "csrf_token"])) {
            $_SESSION["etchat_" . $this->prefix . "csrf_token"] = bin2hex(
                random_bytes(16)
            );
        }

        if (
            !isset($_SESSION["etchat_" . $this->prefix . "user_priv"]) ||
            !in_array($_SESSION["etchat_" . $this->prefix . "user_priv"], [
                "admin",
                "grafik",
                "chatwache",
                "co_admin",
            ])
        ) {
            die("Zugriff verweigert!");
        }

        if ($_SERVER["REQUEST_METHOD"] === "POST") {
            $action = $_POST["action"] ?? "";

            // ⚡ Loading Screens dürfen OHNE CSRF laufen
			if ($action === "creating") {
				$this->renderSpinner('create', '');
				exit();
			}

			if ($action === "restoring") {
				$file = $_POST["file"] ?? '';
				$this->renderSpinner('restore', $file);
				exit();
			}

            // 🔒 ALLES ANDERE braucht CSRF
            $this->checkCsrf();

            switch ($action) {
                case "create":
                    $this->createBackup();
                    break;

                case "delete":
                    if (isset($_POST["file"])) {
                        $this->deleteBackupFile($_POST["file"]);
                    }
                    break;

                case "restore":
                    if (isset($_POST["file"])) {
                        $this->restoreBackup($_POST["file"]);
                    }
                    break;

                case "upload":
                    $this->handleUpload();
                    break;
            }

            exit();
        }
		
		
		if ($_SERVER["REQUEST_METHOD"] === "GET") {
			$action = $_GET["action"] ?? '';

			switch ($action) {
				case "create":
					$this->createBackup();
					exit();

				case "restore":
					if (!empty($_GET["file"])) {
						$this->restoreBackup($_GET["file"]);
					}
					exit();
			}
		}

        $backupFiles = $this->getBackupFiles();
			$this->renderTemplate($backupFiles);
    }

    private function createBackup()
    {
        $backupFolder = dirname(dirname(__DIR__)) . "/backup";
        if (!is_dir($backupFolder)) {
            mkdir($backupFolder, 0775, true);
        }

        $date = date("Y-m-d_H-i-s");
        $backupFiles = [];

        try {
            $dbBackupFile =
                $backupFolder . "/db_" . $this->dbname . "_" . $date . ".sql";
            $this->backupDatabaseToFile($dbBackupFile);
            $backupFiles[] = $dbBackupFile;

            $zipFile = $backupFolder . "/files_" . $date . ".zip";
            $this->backupFilesToZip($zipFile);
            $backupFiles[] = $zipFile;

            $this->renderResult("Backup erfolgreich erstellt", $backupFiles);
        } catch (Exception $e) {
            $this->renderResult("Fehler: " . $e->getMessage());
        }
    }

private function backupDatabaseToFile($filename)
{
    $output = "-- ET-Chat_T-FISH-MOD Backup\n";
    $output .= "-- Erstellt am: " . date("Y-m-d H:i:s") . "\n";
    $output .= "-- Datenbank: " . $this->dbname . "\n\n";
    
    // 🔥 Fremdschlüssel-Checks deaktivieren für reibungslosen Restore
    $output .= "SET FOREIGN_KEY_CHECKS = 0;\n\n";
    
    $tables = $this->db
        ->query("SHOW TABLES LIKE '{$this->prefix}%'")
        ->fetchAll(PDO::FETCH_COLUMN);
    
    // Tabellen die komplett gesichert werden sollen (Struktur + Daten)
    $importantTables = [
        $this->prefix . "etchat_smileys",
        $this->prefix . "etchat_smileys_cat",
        $this->prefix . "etchat_user",
        $this->prefix . "etchat_bewerbungen",
        $this->prefix . "etchat_bewerbung_fields",
        $this->prefix . "etchat_kontakt",
        $this->prefix . "etchat_kontakt_fields",
        $this->prefix . "etchat_config",
        $this->prefix . "etchat_menu",
        $this->prefix . "etchat_radio_box",
        $this->prefix . "etchat_rooms",
        $this->prefix . "etchat_start_boxes",
        $this->prefix . "etchat_texte",
    ];
    
    foreach ($tables as $table) {
        // Struktur sichern (mit DROP IF EXISTS für sauberen Restore)
        $output .= "DROP TABLE IF EXISTS `$table`;\n";
        $create = $this->db->query("SHOW CREATE TABLE `$table`")->fetch();
        $output .= $create["Create Table"] . ";\n\n";
        
        // Nur wichtige Tabellen bekommen Daten
        if (in_array($table, $importantTables)) {
            $stmt = $this->db->query("SELECT * FROM `$table`");
            
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $columns = implode("`, `", array_keys($row));
                
                $values = array_map(function ($value) {
                    if (is_null($value)) return "NULL";
                    if ($value === "") return "''";
                    if (is_numeric($value)) return $value;
                    return "'" . str_replace("'", "''", $value) . "'";
                }, array_values($row));
                
                // 🔥 WICHTIG: REPLACE INTO statt INSERT INTO
                $output .= "REPLACE INTO `$table` (`$columns`) VALUES (" . implode(", ", $values) . ");\n";
            }
            $output .= "\n";
        }
    }
    
    // Fremdschlüssel-Checks wieder aktivieren
    $output .= "\nSET FOREIGN_KEY_CHECKS = 1;\n";
    
    file_put_contents($filename, $output);
}

    private function backupFilesToZip($zipFile)
    {
        $zip = new ZipArchive();
        $zip->open($zipFile, ZipArchive::CREATE);

        $basePath = dirname(dirname(__DIR__));

        $desei = $this->dbObj->sqlGet(
            "SELECT etchat_config_id, etchat_config_style FROM {$this->_prefix}etchat_config WHERE etchat_config_id = '1'"
        );

        // Sammle styles-Pfade aus $desei (falls vorhanden)
        $stylesFolders = [];
        if (is_array($desei)) {
            foreach ($desei as $data) {
                $stylesFolders[] = "styles/" . $data[1];
            }
        }

        // Feste Ordner + styles
        $folders = array_merge(
            ["userpic", "avatar", "smilies", "img", "lang"],
            $stylesFolders
        );

        foreach ($folders as $folder) {
            $folderPath = $basePath . "/" . $folder;
            if (is_dir($folderPath)) {
                $files = new RecursiveIteratorIterator(
                    new RecursiveDirectoryIterator(
                        $folderPath,
                        RecursiveDirectoryIterator::SKIP_DOTS
                    ),
                    RecursiveIteratorIterator::LEAVES_ONLY
                );

                foreach ($files as $file) {
                    if (
                        $file->isFile() &&
                        $file->getFilename() !== "index.html"
                    ) {
                        $filePath = $file->getRealPath();
                        $relativePath = substr(
                            $filePath,
                            strlen($basePath) + 1
                        );
                        $zip->addFile($filePath, $relativePath);
                    }
                }
            }
        }

        // Einzeldateien die mit ins ZIP sollen
        $files = [
            "config.php",
            "cache/anreden.txt",
            "cache/verabschieden.txt",
            "cache/Ankuendigung.cache.php",
            "cache/bot_answers.json",
			"cache/loginbox.txt",
			"cache/verbotene_user_namen.json",
        ];

        foreach ($files as $file) {
            $filePath = $basePath . "/" . $file;
            if (file_exists($filePath)) {
                $zip->addFile($filePath, $file);
            }
        }

        $zip->close();
    }

    private function deleteBackupFile($filename)
    {
        $backupFolder = dirname(dirname(__DIR__)) . "/backup";
        $filePath = $backupFolder . "/" . basename($filename);

        if (file_exists($filePath)) {
            unlink($filePath);
            header("Location: ?AdminBackup");
            exit();
        } else {
            $this->renderResult("Datei nicht gefunden");
        }
    }

    private function restoreBackup($filename)
    {
        $backupFolder = dirname(dirname(__DIR__)) . "/backup";
        $filePath = $backupFolder . "/" . basename($filename);

        if (!file_exists($filePath)) {
            $this->renderResult("Backup-Datei nicht gefunden");
            return;
        }

        $extension = pathinfo($filePath, PATHINFO_EXTENSION);

        if ($extension === "sql") {
            $this->restoreDatabase($filePath);
            $this->renderResult("Datenbank wiederhergestellt");
        } elseif ($extension === "zip") {
			$files = $this->restoreFiles($filePath);
            $this->renderResult("Dateien wiederhergestellt", $files);
        } else {
            $this->renderResult("Ungültiger Dateityp");
        }
    }

private function restoreDatabase($sqlFile)
{
    try {
        // SQL-Datei komplett auslesen
        $sql = file_get_contents($sqlFile);
        
        if ($sql === false) {
            throw new Exception("Fehler beim Lesen der Backup-Datei: $sqlFile");
        }
        
        // 🔥 Versuche alles auf einmal auszuführen (empfohlen)
        $this->db->exec($sql);
        
        $this->renderResult("Datenbank erfolgreich wiederhergestellt");
        
    } catch (PDOException $e) {
        // Fallback: Bei Fehlern einzeln ausführen
        error_log("Massive Restore fehlgeschlagen: " . $e->getMessage());
        
        // Notfall-Modus: Einzelne Statements ausführen
        $statements = $this->splitSqlStatements($sql);
        $successCount = 0;
        
        foreach ($statements as $statement) {
            $statement = trim($statement);
            if ($statement === '') continue;
            
            try {
                $this->db->exec($statement);
                $successCount++;
            } catch (PDOException $stmtError) {
                // REPLACE INTO kann trotzdem fehlschlagen, ist aber nicht kritisch
                error_log("Statement Fehler (ignoriert): " . $stmtError->getMessage());
            }
        }
        
        $this->renderResult("Datenbank wiederhergestellt ($successCount Statements ausgeführt)");
    }
}

private function restoreFiles($zipFile): array
{
    $zip = new ZipArchive();
    $zip->open($zipFile);

    $basePath = dirname(dirname(__DIR__));
    $restoredFiles = []; // ✅ WICHTIG

    for ($i = 0; $i < $zip->numFiles; $i++) {
        $filename = $zip->getNameIndex($i);
        $targetPath = $basePath . "/" . ltrim($filename, "/");

        $realBase = realpath($basePath);
$targetDir = dirname($targetPath);

// Zielordner erstellen (WICHTIG VOR realpath)
if (!is_dir($targetDir)) {
    mkdir($targetDir, 0755, true);
}

$realTargetDir = realpath($targetDir);

if (
    $realTargetDir === false ||
    strpos($realTargetDir, $realBase) !== 0
) {
    continue;
}

        $dir = dirname($targetPath);
        
        $fileContent = $zip->getFromIndex($i);
        if ($fileContent !== false) {
            file_put_contents($targetPath, $fileContent);

            $restoredFiles[] = $filename; // ✅ sammeln
        }
    }

    $zip->close();

    return $restoredFiles; // ✅ GANZ WICHTIG
}

    private function handleUpload()
    {
        if (!isset($_FILES["backup_file"])) {
            $this->renderResult("Keine Datei hochgeladen");
            return;
        }

        $uploadedFile = $_FILES["backup_file"];
        $backupFolder = dirname(dirname(__DIR__)) . "/backup";

        $extension = pathinfo($uploadedFile["name"], PATHINFO_EXTENSION);
        if (!in_array(strtolower($extension), ["sql", "zip"])) {
            $this->renderResult("Nur .sql und .zip Dateien erlaubt");
            return;
        }

        $targetPath =
            $backupFolder .
            "/" .
            time() .
            "_" .
            basename($uploadedFile["name"]);

        if (move_uploaded_file($uploadedFile["tmp_name"], $targetPath)) {
            header("Location: ?AdminBackup");
            exit();
        } else {
            $this->renderResult("Fehler beim Hochladen");
        }
    }

	private function renderTemplate(array $backupFiles): void
{
    $csrfToken = $_SESSION['etchat_' . $this->prefix . 'csrf_token'];

    $tplPath = __DIR__ . '/../../styles/admin_tpl/indexBackup.tpl.html';

    if (!file_exists($tplPath)) {
        die('Template nicht gefunden: ' . $tplPath);
    }

    include $tplPath;
}
	
	private function getBackupFiles(): array
{
    $backupFolder = dirname(dirname(__DIR__)) . "/backup";
    $result = [];

    if (is_dir($backupFolder)) {
        foreach (scandir($backupFolder, SCANDIR_SORT_DESCENDING) as $file) {
            if ($file === '.' || $file === '..') continue;

            $filePath = $backupFolder . '/' . $file;

            $result[] = [
                'name' => $file,
                'size' => filesize($filePath),
                'date' => date('d.m.Y H:i', filemtime($filePath)),
                'type' => pathinfo($filePath, PATHINFO_EXTENSION)
            ];
        }
    }

    return $result;
}
	
private function renderSpinner(string $action, string $file = ''): void
{
    $prefix = $this->prefix;
	
	$title = ($action === 'create')
        ? 'Backup wird erstellt…'
        : 'Backup wird wiederhergestellt…';

    $message = ($action === 'create')
        ? 'Backup wird erstellt... Bitte einen Moment Geduld.'
        : 'Backup wird wiederhergestellt... Bitte einen Moment Geduld.';

    $moduleName = 'AdminBackup';

    include __DIR__ . '/../../styles/admin_tpl/admin_loading_screen.tpl.html';
}

private function splitSqlStatements(string $sql): array
{
    $statements = [];
    $buffer = '';
    $inString = false;
    $stringChar = '';

    $length = strlen($sql);

    for ($i = 0; $i < $length; $i++) {
        $char = $sql[$i];

        // String Start/Ende erkennen (' oder ")
        if (($char === "'" || $char === '"')) {
            if (!$inString) {
                $inString = true;
                $stringChar = $char;
            } elseif ($stringChar === $char) {
                $inString = false;
            }
        }

        $buffer .= $char;

        // Statement-Ende nur wenn NICHT im String
        if ($char === ';' && !$inString) {
            $statements[] = $buffer;
            $buffer = '';
        }
    }

    // Rest anhängen
    if (trim($buffer) !== '') {
        $statements[] = $buffer;
    }

    return $statements;
}

private function renderResult(string $message, array $files = []): void
{
    $prefix = $this->prefix;

    $role = $_SESSION["etchat_" . $this->_prefix . "user_priv"] ?? "";

    $roleLinks = [
        "admin" => "./?AdminIndex",
        "grafik" => "./?AdminIndexGrafik",
        "co_admin" => "./?AdminIndexCoAdmin",
        "chatwache" => "./?AdminIndexChatwache",
    ];

    $backlinkUrl = $roleLinks[$role] ?? "./";

    include __DIR__ . '/../../styles/admin_tpl/createRestoreBackup.tpl.html';
    exit();
}

    private function checkCsrf(): void
    {
        $token = $_POST["csrf_token"] ?? "";
        $session = $_SESSION["etchat_" . $this->prefix . "csrf_token"] ?? "";

        if (!$token || !hash_equals($session, $token)) {
            die("CSRF Fehler!");
        }
    }
}
