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) . '"'; } }