xref: /webtrees/app/Http/RequestHandlers/ExportGedcomClient.php (revision 6b9cb339562c8aa4681c7eff2bf3bdf401cc9edd)
16d576906SGreg Roach<?php
26d576906SGreg Roach
36d576906SGreg Roach/**
46d576906SGreg Roach * webtrees: online genealogy
5a091ac74SGreg Roach * Copyright (C) 2020 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
156d576906SGreg Roach * along with this program. If not, see <http://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;
25*6b9cb339SGreg 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;
306d576906SGreg Roachuse League\Flysystem\ZipArchive\ZipArchiveAdapter;
316d576906SGreg Roachuse Psr\Http\Message\ResponseFactoryInterface;
326d576906SGreg Roachuse Psr\Http\Message\ResponseInterface;
336d576906SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
346d576906SGreg Roachuse Psr\Http\Message\StreamFactoryInterface;
356d576906SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
3669c05a6eSGreg Roachuse RuntimeException;
376d576906SGreg Roach
386d576906SGreg Roachuse function addcslashes;
396d576906SGreg Roachuse function app;
406d576906SGreg Roachuse function assert;
416d576906SGreg Roachuse function fclose;
426d576906SGreg Roachuse function fopen;
436d576906SGreg Roachuse function pathinfo;
446d576906SGreg Roachuse function rewind;
456d576906SGreg Roachuse function strtolower;
466d576906SGreg Roachuse function tmpfile;
476d576906SGreg Roach
486d576906SGreg Roachuse const PATHINFO_EXTENSION;
496d576906SGreg Roach
506d576906SGreg Roach/**
516d576906SGreg Roach * Download a GEDCOM file to the client.
526d576906SGreg Roach */
536d576906SGreg Roachclass ExportGedcomClient implements RequestHandlerInterface
546d576906SGreg Roach{
556d576906SGreg Roach    use ViewResponseTrait;
566d576906SGreg Roach
5769c05a6eSGreg Roach    /** @var GedcomExportService */
5869c05a6eSGreg Roach    private $gedcom_export_service;
5969c05a6eSGreg Roach
6069c05a6eSGreg Roach    /**
6169c05a6eSGreg Roach     * ExportGedcomServer constructor.
6269c05a6eSGreg Roach     *
6369c05a6eSGreg Roach     * @param GedcomExportService $gedcom_export_service
6469c05a6eSGreg Roach     */
6569c05a6eSGreg Roach    public function __construct(GedcomExportService $gedcom_export_service)
6669c05a6eSGreg Roach    {
6769c05a6eSGreg Roach        $this->gedcom_export_service = $gedcom_export_service;
6869c05a6eSGreg Roach    }
6969c05a6eSGreg Roach
706d576906SGreg Roach    /**
716d576906SGreg Roach     * @param ServerRequestInterface $request
726d576906SGreg Roach     *
736d576906SGreg Roach     * @return ResponseInterface
746d576906SGreg Roach     */
756d576906SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
766d576906SGreg Roach    {
776d576906SGreg Roach        $tree = $request->getAttribute('tree');
786d576906SGreg Roach        assert($tree instanceof Tree);
796d576906SGreg Roach
80*6b9cb339SGreg Roach        $data_filesystem = Registry::filesystem()->data();
81a04bb9a2SGreg Roach
82b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
83b46c87bdSGreg Roach
84b46c87bdSGreg Roach        $convert          = (bool) ($params['convert'] ?? false);
85b46c87bdSGreg Roach        $zip              = (bool) ($params['zip'] ?? false);
86b46c87bdSGreg Roach        $media            = (bool) ($params['media'] ?? false);
87b46c87bdSGreg Roach        $media_path       = $params['media-path'] ?? '';
88b46c87bdSGreg Roach        $privatize_export = $params['privatize_export'];
896d576906SGreg Roach
906d576906SGreg Roach        $access_levels = [
916d576906SGreg Roach            'gedadmin' => Auth::PRIV_NONE,
926d576906SGreg Roach            'user'     => Auth::PRIV_USER,
936d576906SGreg Roach            'visitor'  => Auth::PRIV_PRIVATE,
946d576906SGreg Roach            'none'     => Auth::PRIV_HIDE,
956d576906SGreg Roach        ];
966d576906SGreg Roach
976d576906SGreg Roach        $access_level = $access_levels[$privatize_export];
986d576906SGreg Roach        $encoding     = $convert ? 'ANSI' : 'UTF-8';
996d576906SGreg Roach
1006d576906SGreg Roach        // What to call the downloaded file
1016d576906SGreg Roach        $download_filename = $tree->name();
1026d576906SGreg Roach
1036d576906SGreg Roach        // Force a ".ged" suffix
1046d576906SGreg Roach        if (strtolower(pathinfo($download_filename, PATHINFO_EXTENSION)) !== 'ged') {
1056d576906SGreg Roach            $download_filename .= '.ged';
1066d576906SGreg Roach        }
1076d576906SGreg Roach
1086d576906SGreg Roach        if ($zip || $media) {
1096d576906SGreg Roach            // Export the GEDCOM to an in-memory stream.
11069c05a6eSGreg Roach            $tmp_stream = fopen('php://temp', 'wb+');
11169c05a6eSGreg Roach
11269c05a6eSGreg Roach            if ($tmp_stream === false) {
11369c05a6eSGreg Roach                throw new RuntimeException('Failed to create temporary stream');
11469c05a6eSGreg Roach            }
11569c05a6eSGreg Roach
11669c05a6eSGreg Roach            $this->gedcom_export_service->export($tree, $tmp_stream, true, $encoding, $access_level, $media_path);
11769c05a6eSGreg Roach
1186d576906SGreg Roach            rewind($tmp_stream);
1196d576906SGreg Roach
1206d576906SGreg Roach            $path = $tree->getPreference('MEDIA_DIRECTORY', 'media/');
1216d576906SGreg Roach
1226d576906SGreg Roach            // Create a new/empty .ZIP file
123a00baf47SGreg Roach            $temp_zip_file  = stream_get_meta_data(tmpfile())['uri'];
1246d576906SGreg Roach            $zip_adapter    = new ZipArchiveAdapter($temp_zip_file);
1256d576906SGreg Roach            $zip_filesystem = new Filesystem($zip_adapter);
12669c05a6eSGreg Roach            $zip_filesystem->putStream($download_filename, $tmp_stream);
1276d576906SGreg Roach            fclose($tmp_stream);
1286d576906SGreg Roach
1296d576906SGreg Roach            if ($media) {
130fa695506SGreg Roach                $media_filesystem = $tree->mediaFilesystem($data_filesystem);
1316d576906SGreg Roach
1326d576906SGreg Roach                $records = DB::table('media')
1336d576906SGreg Roach                    ->where('m_file', '=', $tree->id())
1346d576906SGreg Roach                    ->get()
135*6b9cb339SGreg Roach                    ->map(Registry::mediaFactory()->mapper($tree))
1366d576906SGreg Roach                    ->filter(GedcomRecord::accessFilter());
1376d576906SGreg Roach
1386d576906SGreg Roach                foreach ($records as $record) {
1396d576906SGreg Roach                    foreach ($record->mediaFiles() as $media_file) {
140fa695506SGreg Roach                        $from = $media_file->filename();
141fa695506SGreg Roach                        $to   = $path . $media_file->filename();
142fa695506SGreg Roach                        if (!$media_file->isExternal() && $media_filesystem->has($from) && !$zip_filesystem->has($to)) {
143fa695506SGreg Roach                            $zip_filesystem->writeStream($to, $media_filesystem->readStream($from));
1446d576906SGreg Roach                        }
1456d576906SGreg Roach                    }
1466d576906SGreg Roach                }
1476d576906SGreg Roach            }
1486d576906SGreg Roach
1496d576906SGreg Roach            // Need to force-close ZipArchive filesystems.
1506d576906SGreg Roach            $zip_adapter->getArchive()->close();
1516d576906SGreg Roach
1526d576906SGreg Roach            // Use a stream, so that we do not have to load the entire file into memory.
15369c05a6eSGreg Roach            $stream_factory = app(StreamFactoryInterface::class);
15469c05a6eSGreg Roach            assert($stream_factory instanceof StreamFactoryInterface);
15569c05a6eSGreg Roach
15669c05a6eSGreg Roach            $http_stream   = $stream_factory->createStreamFromFile($temp_zip_file);
1576d576906SGreg Roach            $filename = addcslashes($download_filename, '"') . '.zip';
1586d576906SGreg Roach
1596d576906SGreg Roach            /** @var ResponseFactoryInterface $response_factory */
1606d576906SGreg Roach            $response_factory = app(ResponseFactoryInterface::class);
1616d576906SGreg Roach
1626d576906SGreg 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
1796d576906SGreg Roach        $stream_factory = app(StreamFactoryInterface::class);
18069c05a6eSGreg Roach        assert($stream_factory instanceof StreamFactoryInterface);
1816d576906SGreg Roach
18269c05a6eSGreg Roach        $http_stream = $stream_factory->createStreamFromResource($resource);
1836d576906SGreg Roach
1846d576906SGreg Roach        /** @var ResponseFactoryInterface $response_factory */
1856d576906SGreg Roach        $response_factory = app(ResponseFactoryInterface::class);
1866d576906SGreg Roach
1876d576906SGreg 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