xref: /webtrees/app/Http/RequestHandlers/ExportGedcomClient.php (revision c4943cff72f95a28fbb9404e3c20b169ff098e5c)
16d576906SGreg Roach<?php
26d576906SGreg Roach
36d576906SGreg Roach/**
46d576906SGreg Roach * webtrees: online genealogy
589f7189bSGreg Roach * Copyright (C) 2021 webtrees development team
66d576906SGreg Roach * This program is free software: you can redistribute it and/or modify
76d576906SGreg Roach * it under the terms of the GNU General Public License as published by
86d576906SGreg Roach * the Free Software Foundation, either version 3 of the License, or
96d576906SGreg Roach * (at your option) any later version.
106d576906SGreg Roach * This program is distributed in the hope that it will be useful,
116d576906SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
126d576906SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
136d576906SGreg Roach * GNU General Public License for more details.
146d576906SGreg Roach * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
166d576906SGreg Roach */
176d576906SGreg Roach
186d576906SGreg Roachdeclare(strict_types=1);
196d576906SGreg Roach
206d576906SGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers;
216d576906SGreg Roach
226d576906SGreg Roachuse Fisharebest\Webtrees\Auth;
236d576906SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
246d576906SGreg Roachuse Fisharebest\Webtrees\Http\ViewResponseTrait;
256b9cb339SGreg Roachuse Fisharebest\Webtrees\Registry;
2669c05a6eSGreg Roachuse Fisharebest\Webtrees\Services\GedcomExportService;
276d576906SGreg Roachuse Fisharebest\Webtrees\Tree;
286d576906SGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
296d576906SGreg Roachuse League\Flysystem\Filesystem;
30f0448b68SGreg Roachuse League\Flysystem\FilesystemException;
31f32d77e6SGreg Roachuse League\Flysystem\ZipArchive\FilesystemZipArchiveProvider;
326d576906SGreg Roachuse League\Flysystem\ZipArchive\ZipArchiveAdapter;
3300c45d23SGreg Roachuse Psr\Http\Message\ResponseFactoryInterface;
346d576906SGreg Roachuse Psr\Http\Message\ResponseInterface;
356d576906SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
3600c45d23SGreg Roachuse Psr\Http\Message\StreamFactoryInterface;
376d576906SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
3869c05a6eSGreg Roachuse RuntimeException;
396d576906SGreg Roach
406d576906SGreg Roachuse function addcslashes;
4100c45d23SGreg Roachuse function app;
426d576906SGreg Roachuse function assert;
436d576906SGreg Roachuse function fclose;
446d576906SGreg Roachuse function fopen;
456d576906SGreg Roachuse function pathinfo;
466d576906SGreg Roachuse function rewind;
476d576906SGreg Roachuse function strtolower;
486d576906SGreg Roachuse function tmpfile;
496d576906SGreg Roach
506d576906SGreg Roachuse const PATHINFO_EXTENSION;
516d576906SGreg Roach
526d576906SGreg Roach/**
536d576906SGreg Roach * Download a GEDCOM file to the client.
546d576906SGreg Roach */
556d576906SGreg Roachclass ExportGedcomClient implements RequestHandlerInterface
566d576906SGreg Roach{
576d576906SGreg Roach    use ViewResponseTrait;
586d576906SGreg Roach
59*c4943cffSGreg Roach    private GedcomExportService $gedcom_export_service;
6069c05a6eSGreg Roach
6169c05a6eSGreg Roach    /**
6269c05a6eSGreg Roach     * ExportGedcomServer constructor.
6369c05a6eSGreg Roach     *
6469c05a6eSGreg Roach     * @param GedcomExportService $gedcom_export_service
6569c05a6eSGreg Roach     */
6669c05a6eSGreg Roach    public function __construct(GedcomExportService $gedcom_export_service)
6769c05a6eSGreg Roach    {
6869c05a6eSGreg Roach        $this->gedcom_export_service = $gedcom_export_service;
6969c05a6eSGreg Roach    }
7069c05a6eSGreg Roach
716d576906SGreg Roach    /**
726d576906SGreg Roach     * @param ServerRequestInterface $request
736d576906SGreg Roach     *
746d576906SGreg Roach     * @return ResponseInterface
75f0448b68SGreg Roach     * @throws FilesystemException
766d576906SGreg Roach     */
776d576906SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
786d576906SGreg Roach    {
796d576906SGreg Roach        $tree = $request->getAttribute('tree');
806d576906SGreg Roach        assert($tree instanceof Tree);
816d576906SGreg Roach
826b9cb339SGreg Roach        $data_filesystem = Registry::filesystem()->data();
83a04bb9a2SGreg Roach
84b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
85b46c87bdSGreg Roach
86b46c87bdSGreg Roach        $convert          = (bool) ($params['convert'] ?? false);
87b46c87bdSGreg Roach        $zip              = (bool) ($params['zip'] ?? false);
88b46c87bdSGreg Roach        $media            = (bool) ($params['media'] ?? false);
89b46c87bdSGreg Roach        $media_path       = $params['media-path'] ?? '';
90b46c87bdSGreg Roach        $privatize_export = $params['privatize_export'];
916d576906SGreg Roach
926d576906SGreg Roach        $access_levels = [
936d576906SGreg Roach            'gedadmin' => Auth::PRIV_NONE,
946d576906SGreg Roach            'user'     => Auth::PRIV_USER,
956d576906SGreg Roach            'visitor'  => Auth::PRIV_PRIVATE,
966d576906SGreg Roach            'none'     => Auth::PRIV_HIDE,
976d576906SGreg Roach        ];
986d576906SGreg Roach
996d576906SGreg Roach        $access_level = $access_levels[$privatize_export];
1006d576906SGreg Roach        $encoding     = $convert ? 'ANSI' : 'UTF-8';
1016d576906SGreg Roach
1026d576906SGreg Roach        // What to call the downloaded file
1036d576906SGreg Roach        $download_filename = $tree->name();
1046d576906SGreg Roach
1056d576906SGreg Roach        // Force a ".ged" suffix
1066d576906SGreg Roach        if (strtolower(pathinfo($download_filename, PATHINFO_EXTENSION)) !== 'ged') {
1076d576906SGreg Roach            $download_filename .= '.ged';
1086d576906SGreg Roach        }
1096d576906SGreg Roach
1106d576906SGreg Roach        if ($zip || $media) {
1116d576906SGreg Roach            // Export the GEDCOM to an in-memory stream.
11269c05a6eSGreg Roach            $tmp_stream = fopen('php://temp', 'wb+');
11369c05a6eSGreg Roach
11469c05a6eSGreg Roach            if ($tmp_stream === false) {
11569c05a6eSGreg Roach                throw new RuntimeException('Failed to create temporary stream');
11669c05a6eSGreg Roach            }
11769c05a6eSGreg Roach
11869c05a6eSGreg Roach            $this->gedcom_export_service->export($tree, $tmp_stream, true, $encoding, $access_level, $media_path);
11969c05a6eSGreg Roach
1206d576906SGreg Roach            rewind($tmp_stream);
1216d576906SGreg Roach
1226d576906SGreg Roach            $path = $tree->getPreference('MEDIA_DIRECTORY', 'media/');
1236d576906SGreg Roach
1246d576906SGreg Roach            // Create a new/empty .ZIP file
125a00baf47SGreg Roach            $temp_zip_file  = stream_get_meta_data(tmpfile())['uri'];
126f32d77e6SGreg Roach            $zip_provider   = new FilesystemZipArchiveProvider($temp_zip_file, 0755);
127f32d77e6SGreg Roach            $zip_adapter    = new ZipArchiveAdapter($zip_provider);
1286d576906SGreg Roach            $zip_filesystem = new Filesystem($zip_adapter);
129f7cf8a15SGreg Roach            $zip_filesystem->writeStream($download_filename, $tmp_stream);
1306d576906SGreg Roach            fclose($tmp_stream);
1316d576906SGreg Roach
1326d576906SGreg Roach            if ($media) {
133fa695506SGreg Roach                $media_filesystem = $tree->mediaFilesystem($data_filesystem);
1346d576906SGreg Roach
1356d576906SGreg Roach                $records = DB::table('media')
1366d576906SGreg Roach                    ->where('m_file', '=', $tree->id())
1376d576906SGreg Roach                    ->get()
1386b9cb339SGreg Roach                    ->map(Registry::mediaFactory()->mapper($tree))
1396d576906SGreg Roach                    ->filter(GedcomRecord::accessFilter());
1406d576906SGreg Roach
1416d576906SGreg Roach                foreach ($records as $record) {
1426d576906SGreg Roach                    foreach ($record->mediaFiles() as $media_file) {
143fa695506SGreg Roach                        $from = $media_file->filename();
144fa695506SGreg Roach                        $to   = $path . $media_file->filename();
145f7cf8a15SGreg Roach                        if (!$media_file->isExternal() && $media_filesystem->fileExists($from) && !$zip_filesystem->fileExists($to)) {
146fa695506SGreg Roach                            $zip_filesystem->writeStream($to, $media_filesystem->readStream($from));
1476d576906SGreg Roach                        }
1486d576906SGreg Roach                    }
1496d576906SGreg Roach                }
1506d576906SGreg Roach            }
1516d576906SGreg Roach
1526d576906SGreg Roach            // Use a stream, so that we do not have to load the entire file into memory.
15300c45d23SGreg Roach            $stream_factory = app(StreamFactoryInterface::class);
15400c45d23SGreg Roach            assert($stream_factory instanceof StreamFactoryInterface);
15500c45d23SGreg Roach
15600c45d23SGreg Roach            $http_stream   = $stream_factory->createStreamFromFile($temp_zip_file);
1576d576906SGreg Roach            $filename = addcslashes($download_filename, '"') . '.zip';
1586d576906SGreg Roach
15900c45d23SGreg Roach            /** @var ResponseFactoryInterface $response_factory */
16000c45d23SGreg Roach            $response_factory = app(ResponseFactoryInterface::class);
16100c45d23SGreg Roach
16200c45d23SGreg Roach            return $response_factory->createResponse()
16369c05a6eSGreg Roach                ->withBody($http_stream)
1646d576906SGreg Roach                ->withHeader('Content-Type', 'application/zip')
1656d576906SGreg Roach                ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
1666d576906SGreg Roach        }
1676d576906SGreg Roach
1686d576906SGreg Roach        $resource = fopen('php://temp', 'wb+');
16969c05a6eSGreg Roach
17069c05a6eSGreg Roach        if ($resource === false) {
17169c05a6eSGreg Roach            throw new RuntimeException('Failed to create temporary stream');
17269c05a6eSGreg Roach        }
17369c05a6eSGreg Roach
17469c05a6eSGreg Roach        $this->gedcom_export_service->export($tree, $resource, true, $encoding, $access_level, $media_path);
1756d576906SGreg Roach        rewind($resource);
1766d576906SGreg Roach
1776d576906SGreg Roach        $charset = $convert ? 'ISO-8859-1' : 'UTF-8';
1786d576906SGreg Roach
17900c45d23SGreg Roach        $stream_factory = app(StreamFactoryInterface::class);
18000c45d23SGreg Roach        assert($stream_factory instanceof StreamFactoryInterface);
1816d576906SGreg Roach
18200c45d23SGreg Roach        $http_stream = $stream_factory->createStreamFromResource($resource);
18300c45d23SGreg Roach
18400c45d23SGreg Roach        /** @var ResponseFactoryInterface $response_factory */
18500c45d23SGreg Roach        $response_factory = app(ResponseFactoryInterface::class);
18600c45d23SGreg Roach
18700c45d23SGreg Roach        return $response_factory->createResponse()
18869c05a6eSGreg Roach            ->withBody($http_stream)
1896d576906SGreg Roach            ->withHeader('Content-Type', 'text/x-gedcom; charset=' . $charset)
1906d576906SGreg Roach            ->withHeader('Content-Disposition', 'attachment; filename="' . addcslashes($download_filename, '"') . '"');
1916d576906SGreg Roach    }
1926d576906SGreg Roach}
193