| 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);
/** Strumieniowy eksport SQL na podstawie działającego eksportu POLANA (bez auth/UI). */
function pbk_sql_identifier(string $name): string
{
return '`' . str_replace('`', '``', $name) . '`';
}
function pbk_sql_write($stream, string $value): void
{
$length = strlen($value);
// Zamiast fatalnego "Allowed memory size exhausted" zwracamy bezpieczny błąd,
// zanim niekompletny SQL trafi do przeglądarki. php://memory zajmuje RAM.
static $memLimit = null;
if ($memLimit === null) {
$ini = trim((string)ini_get('memory_limit'));
if ($ini === '-1' || $ini === '') {
$memLimit = PHP_INT_MAX;
} else {
$amount = (int)$ini;
$suffix = strtolower(substr($ini, -1));
$scale = ['k' => 1024, 'm' => 1048576, 'g' => 1073741824];
$memLimit = $amount * ($scale[$suffix] ?? 1);
}
}
if ($memLimit !== PHP_INT_MAX && memory_get_usage(true) + ($length * 2) + 12 * 1048576 > $memLimit) {
throw new RuntimeException('Kopia SQL wymaga więcej RAM niż pozwala limit memory_limit PHP.');
}
$offset = 0;
while ($offset < $length) {
$written = fwrite($stream, substr($value, $offset));
if (!is_int($written) || $written < 1) {
throw new RuntimeException('Błąd zapisu do pamięci backupu.');
}
$offset += $written;
}
}
function pbk_sql_sql_value(PDO $pdo, mixed $value, bool $binary): string
{
if ($value === null) return 'NULL';
if ($binary) return '0x' . bin2hex((string)$value);
$result = $pdo->quote((string)$value);
if (!is_string($result)) {
throw new RuntimeException('Nie udało się zakodować wartości SQL.');
}
return $result;
}
/** @param resource $stream */
function pbk_sql_build(PDO $pdo, $stream, bool $onlyReservations): array
{
$dbName = (string)$pdo->query('SELECT DATABASE()')->fetchColumn();
$objects = $pdo->query('SHOW FULL TABLES')->fetchAll(PDO::FETCH_NUM);
$tables = [];
$views = [];
$selected = $onlyReservations ? ['reservations', 'reservation_events'] : null;
foreach ($objects as $item) {
$name = (string)($item[0] ?? '');
$kind = strtoupper((string)($item[1] ?? ''));
if ($name === '' || ($selected !== null && !in_array($name, $selected, true))) continue;
if ($kind === 'BASE TABLE') $tables[] = $name;
elseif ($kind === 'VIEW') $views[] = $name;
else throw new RuntimeException('Nieznany typ obiektu bazy: ' . $kind);
}
sort($tables, SORT_STRING);
sort($views, SORT_STRING);
if ($onlyReservations && !in_array('reservations', $tables, true)) {
throw new RuntimeException('Brak tabeli reservations w bazie.');
}
if (!$tables) throw new RuntimeException('Brak tabel do eksportu.');
pbk_sql_write($stream, '-- TKLM / MOJA POLANA SMAKÓW – KOPIA SQL' . "\n"
. '-- Utworzono: ' . date(DATE_ATOM) . "\n"
. '-- Typ: ' . ($onlyReservations ? 'Rezerwacje + historia' : 'Pełna baza (tabele + widoki + triggery)') . "\n"
. '-- Uwaga: zawiera dane osobowe; przechowuj bezpiecznie i NIE udostępniaj publicznie!' . "\n"
. '-- Eksportowana baza: ' . str_replace(["\r", "\n"], '', $dbName) . "\n"
. '-- Odtwarzanie może NADPISAĆ dane. Importuj tylko do właściwej bazy.' . "\n\n"
. 'SET NAMES utf8mb4;' . "\n"
. 'SET FOREIGN_KEY_CHECKS=0;' . "\n\n");
// Metadane przed rozpoczęciem niebuforowanych odczytów.
$defs = [];
$columns = [];
foreach ($tables as $table) {
$id = pbk_sql_identifier($table);
$row = $pdo->query('SHOW CREATE TABLE ' . $id)->fetch(PDO::FETCH_ASSOC);
$defs[$table] = (string)($row['Create Table'] ?? '');
if ($defs[$table] === '') throw new RuntimeException('Brak definicji tabeli: ' . $table);
$cols = $pdo->query('SHOW FULL COLUMNS FROM ' . $id)->fetchAll(PDO::FETCH_ASSOC);
if (!$cols) throw new RuntimeException('Brak kolumn tabeli: ' . $table);
$columns[$table] = $cols;
}
$viewDefs = [];
foreach ($views as $view) {
$row = $pdo->query('SHOW CREATE VIEW ' . pbk_sql_identifier($view))->fetch(PDO::FETCH_ASSOC);
$definition = (string)($row['Create View'] ?? '');
if ($definition === '') throw new RuntimeException('Brak definicji widoku: ' . $view);
// Nie wymuszamy nieistniejącego użytkownika DEFINER przy odtwarzaniu.
$viewDefs[$view] = preg_replace('/\s+DEFINER\s*=\s*`[^`]+`@`[^`]+`/i', '', $definition) ?? $definition;
}
// Nie ukrywamy problemów z uprawnieniami: błąd = brak pobierania niepełnego backupu.
$triggers = $pdo->query('SHOW TRIGGERS')->fetchAll(PDO::FETCH_ASSOC);
if ($selected !== null) {
$triggers = array_values(array_filter($triggers, static fn(array $x): bool => in_array((string)($x['Table'] ?? ''), $selected, true)));
}
foreach ($tables as $table) {
$id = pbk_sql_identifier($table);
pbk_sql_write($stream, "-- Struktura: $table\nDROP TABLE IF EXISTS $id;\n" . $defs[$table] . ";\n\n");
}
// START TRANSACTION WITH CONSISTENT SNAPSHOT dla InnoDB. Pozostałe silniki mogą
// nie zapewniać spójności transakcyjnej – warto zrobić backup przy małym ruchu.
$pdo->exec('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
$pdo->exec('START TRANSACTION WITH CONSISTENT SNAPSHOT');
$rowCounts = [];
try {
foreach ($tables as $table) {
$id = pbk_sql_identifier($table);
$cols = $columns[$table];
$names = array_map(static fn(array $c): string => (string)$c['Field'], $cols);
$quotedNames = implode(',', array_map('pbk_sql_identifier', $names));
$binCols = [];
foreach ($cols as $col) {
$binCols[(string)$col['Field']] = (bool)preg_match('/\b(blob|binary|varbinary|bit)\b/i', (string)($col['Type'] ?? ''));
}
$count = 0;
$unbuffered = defined('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY');
if ($unbuffered) $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
try {
$statement = $pdo->query('SELECT * FROM ' . $id);
while (($record = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
$values = [];
foreach ($names as $column) {
$values[] = pbk_sql_sql_value($pdo, $record[$column] ?? null, $binCols[$column]);
}
pbk_sql_write($stream, 'INSERT INTO ' . $id . ' (' . $quotedNames . ') VALUES (' . implode(',', $values) . ");\n");
$count++;
}
$statement->closeCursor();
} finally {
if ($unbuffered) $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
}
pbk_sql_write($stream, "\n");
$rowCounts[$table] = $count;
}
$pdo->exec('COMMIT');
} catch (Throwable $e) {
try { $pdo->exec('ROLLBACK'); } catch (Throwable $ignored) {}
throw $e;
}
foreach ($viewDefs as $name => $definition) {
$id = pbk_sql_identifier($name);
pbk_sql_write($stream, "-- Widok: $name\nDROP VIEW IF EXISTS $id;\n" . $definition . ";\n\n");
}
if ($triggers) {
pbk_sql_write($stream, "DELIMITER $$\n");
foreach ($triggers as $trigger) {
$name = pbk_sql_identifier((string)$trigger['Trigger']);
$table = pbk_sql_identifier((string)$trigger['Table']);
$timing = strtoupper((string)$trigger['Timing']);
$event = strtoupper((string)$trigger['Event']);
if (!in_array($timing, ['BEFORE', 'AFTER'], true)
|| !in_array($event, ['INSERT', 'UPDATE', 'DELETE'], true)) {
throw new RuntimeException('Nieobsługiwany trigger.');
}
pbk_sql_write($stream,
"DROP TRIGGER IF EXISTS $name$$\n"
. "CREATE TRIGGER $name $timing $event ON $table FOR EACH ROW "
. (string)$trigger['Statement'] . "$$\n\n");
}
pbk_sql_write($stream, "DELIMITER ;\n\n");
}
pbk_sql_write($stream, "SET FOREIGN_KEY_CHECKS=1;\n-- KONIEC POPRAWNEGO BACKUPU SQL\n");
return ['rows' => $rowCounts, 'tables' => count($tables), 'views' => count($views), 'triggers' => count($triggers)];
}