Kufrik/app/Model/Sdf/SdfCompactExporter.php

141 lines
4.6 KiB
PHP

<?php
namespace App\Model\Sdf;
use Nette\Utils\FileSystem;
use Nette\Utils\Json;
class SdfCompactExporter
{
public function __construct(
private string $scriptPath,
private string $sdfPassword
) {
}
/**
* Přečte SQL Compact .sdf (Windows) nebo hotový JSON export a vrátí tabulky jako pole.
*
* @return array{uzivatel: array, zakaznik: array, faktura: array, polozka: array, platba: array, konfigurace: array}
*/
public function export(string $sdfPath): array
{
if (!is_file($sdfPath)) {
throw new \RuntimeException('SDF soubor nebyl nalezen.');
}
$extension = strtolower(pathinfo($sdfPath, PATHINFO_EXTENSION));
if ($extension === 'json') {
return $this->normalizeExport(Json::decode(FileSystem::read($sdfPath), forceArrays: true));
}
if (!is_file($this->scriptPath)) {
throw new \RuntimeException('Exportní skript bin/sdf-export.ps1 chybí.');
}
$powershell = $this->findPowershell();
if ($powershell === null) {
throw new \RuntimeException(
'Čtení .sdf vyžaduje Windows a PowerShell se SQL Server Compact 4.0. '
. 'Na Linux serveru nejdřív na Windows spusťte bin/sdf-export.ps1 '
. 'a nahrajte výsledný .json.'
);
}
$workDir = dirname($sdfPath) . DIRECTORY_SEPARATOR . 'export-' . bin2hex(random_bytes(4));
FileSystem::createDir($workDir);
$outFile = $workDir . DIRECTORY_SEPARATOR . 'export.json';
try {
$this->runPowershell($powershell, $sdfPath, $outFile);
$raw = FileSystem::read($outFile);
return $this->normalizeExport(Json::decode($raw, forceArrays: true));
} finally {
FileSystem::delete($workDir);
}
}
/**
* @return array{uzivatel: array, zakaznik: array, faktura: array, polozka: array, platba: array, konfigurace: array}
*/
private function normalizeExport(mixed $data): array
{
if (!is_array($data)) {
throw new \RuntimeException('Export SDF nevrátil platná data.');
}
$hasTables = isset($data['uzivatel']) || isset($data['zakaznik']) || isset($data['faktura']);
if (!$hasTables) {
throw new \RuntimeException(
'JSON nemá očekávanou strukturu exportu (uzivatel/zakaznik/faktura). '
. 'Vygenerujte ho přes bin/sdf-export.ps1.'
);
}
return [
'uzivatel' => $data['uzivatel'] ?? [],
'zakaznik' => $data['zakaznik'] ?? [],
'faktura' => $data['faktura'] ?? [],
'polozka' => $data['polozka'] ?? [],
'platba' => $data['platba'] ?? [],
'konfigurace' => $data['konfigurace'] ?? [],
];
}
private function runPowershell(string $powershell, string $sdfPath, string $outFile): void
{
$cmd = sprintf(
'%s -NoProfile -NonInteractive -ExecutionPolicy Bypass -File %s -SdfPath %s -OutFile %s -Password %s',
$this->winArg($powershell),
$this->winArg($this->scriptPath),
$this->winArg($sdfPath),
$this->winArg($outFile),
$this->winArg($this->sdfPassword)
);
$descriptor = [
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($cmd, $descriptor, $pipes, null, null, ['bypass_shell' => false]);
if (!is_resource($process)) {
throw new \RuntimeException('Nepodařilo se spustit PowerShell pro čtení SDF.');
}
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$code = proc_close($process);
if ($code !== 0 || !is_file($outFile)) {
$detail = trim($stderr . "\n" . $stdout);
throw new \RuntimeException('Export SDF selhal' . ($detail !== '' ? ': ' . $detail : '.'));
}
}
private function findPowershell(): ?string
{
if (PHP_OS_FAMILY !== 'Windows') {
return null;
}
$candidates = [
getenv('SystemRoot') . '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
];
foreach ($candidates as $path) {
if ($path && is_file($path)) {
return $path;
}
}
return 'powershell.exe';
}
private function winArg(string $value): string
{
return '"' . str_replace('"', '\\"', $value) . '"';
}
}