Passed
Push — master ( da7f6d...6d5769 )
by Greg
05:57
created

ExportGedcomClient   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 62
dl 0
loc 110
rs 10
c 1
b 0
f 0
wmc 11

1 Method

Rating   Name   Duplication   Size   Complexity  
C handle() 0 101 11
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2019 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Http\RequestHandlers;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\Functions\FunctionsExport;
24
use Fisharebest\Webtrees\GedcomRecord;
25
use Fisharebest\Webtrees\Http\ViewResponseTrait;
26
use Fisharebest\Webtrees\Media;
27
use Fisharebest\Webtrees\Tree;
28
use Illuminate\Database\Capsule\Manager as DB;
29
use League\Flysystem\Filesystem;
30
use League\Flysystem\MountManager;
31
use League\Flysystem\ZipArchive\ZipArchiveAdapter;
32
use Psr\Http\Message\ResponseFactoryInterface;
33
use Psr\Http\Message\ResponseInterface;
34
use Psr\Http\Message\ServerRequestInterface;
35
use Psr\Http\Message\StreamFactoryInterface;
36
use Psr\Http\Server\RequestHandlerInterface;
37
38
use function addcslashes;
39
use function app;
40
use function assert;
41
use function fclose;
42
use function fopen;
43
use function pathinfo;
44
use function rewind;
45
use function strtolower;
46
use function sys_get_temp_dir;
47
use function tempnam;
48
use function tmpfile;
49
50
use const PATHINFO_EXTENSION;
51
52
/**
53
 * Download a GEDCOM file to the client.
54
 */
55
class ExportGedcomClient implements RequestHandlerInterface
56
{
57
    use ViewResponseTrait;
58
59
    /**
60
     * @param ServerRequestInterface $request
61
     *
62
     * @return ResponseInterface
63
     */
64
    public function handle(ServerRequestInterface $request): ResponseInterface
65
    {
66
        $tree = $request->getAttribute('tree');
67
        assert($tree instanceof Tree);
68
69
        $convert          = (bool) ($request->getParsedBody()['convert'] ?? false);
70
        $zip              = (bool) ($request->getParsedBody()['zip'] ?? false);
71
        $media            = (bool) ($request->getParsedBody()['media'] ?? false);
72
        $media_path       = $request->getParsedBody()['media-path'] ?? '';
73
        $privatize_export = $request->getParsedBody()['privatize_export'];
74
75
        $access_levels = [
76
            'gedadmin' => Auth::PRIV_NONE,
77
            'user'     => Auth::PRIV_USER,
78
            'visitor'  => Auth::PRIV_PRIVATE,
79
            'none'     => Auth::PRIV_HIDE,
80
        ];
81
82
        $access_level = $access_levels[$privatize_export];
83
        $encoding     = $convert ? 'ANSI' : 'UTF-8';
84
85
        // What to call the downloaded file
86
        $download_filename = $tree->name();
87
88
        // Force a ".ged" suffix
89
        if (strtolower(pathinfo($download_filename, PATHINFO_EXTENSION)) !== 'ged') {
90
            $download_filename .= '.ged';
91
        }
92
93
        if ($zip || $media) {
94
            // Export the GEDCOM to an in-memory stream.
95
            $tmp_stream = tmpfile();
96
            FunctionsExport::exportGedcom($tree, $tmp_stream, $access_level, $media_path, $encoding);
0 ignored issues
show
Bug introduced by
It seems like $tmp_stream can also be of type false; however, parameter $stream of Fisharebest\Webtrees\Fun...sExport::exportGedcom() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
            FunctionsExport::exportGedcom($tree, /** @scrutinizer ignore-type */ $tmp_stream, $access_level, $media_path, $encoding);
Loading history...
97
            rewind($tmp_stream);
0 ignored issues
show
Bug introduced by
It seems like $tmp_stream can also be of type false; however, parameter $handle of rewind() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
            rewind(/** @scrutinizer ignore-type */ $tmp_stream);
Loading history...
98
99
            $path = $tree->getPreference('MEDIA_DIRECTORY', 'media/');
100
101
            // Create a new/empty .ZIP file
102
            $temp_zip_file  = tempnam(sys_get_temp_dir(), 'webtrees-zip-');
103
            $zip_adapter    = new ZipArchiveAdapter($temp_zip_file);
104
            $zip_filesystem = new Filesystem($zip_adapter);
105
            $zip_filesystem->writeStream($download_filename, $tmp_stream);
106
            fclose($tmp_stream);
107
108
            if ($media) {
109
                $manager = new MountManager([
110
                    'media' => $tree->mediaFilesystem(),
111
                    'zip'   => $zip_filesystem,
112
                ]);
113
114
                $records = DB::table('media')
115
                    ->where('m_file', '=', $tree->id())
116
                    ->get()
117
                    ->map(Media::rowMapper())
118
                    ->filter(GedcomRecord::accessFilter());
119
120
                foreach ($records as $record) {
121
                    foreach ($record->mediaFiles() as $media_file) {
122
                        $from = 'media://' . $media_file->filename();
123
                        $to   = 'zip://' . $path . $media_file->filename();
124
                        if (!$media_file->isExternal() && $manager->has($from)) {
125
                            $manager->copy($from, $to);
126
                        }
127
                    }
128
                }
129
            }
130
131
            // Need to force-close ZipArchive filesystems.
132
            $zip_adapter->getArchive()->close();
133
134
            // Use a stream, so that we do not have to load the entire file into memory.
135
            $stream   = app(StreamFactoryInterface::class)->createStreamFromFile($temp_zip_file);
136
            $filename = addcslashes($download_filename, '"') . '.zip';
137
138
            /** @var ResponseFactoryInterface $response_factory */
139
            $response_factory = app(ResponseFactoryInterface::class);
140
141
            return $response_factory->createResponse()
142
                ->withBody($stream)
143
                ->withHeader('Content-Type', 'application/zip')
144
                ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
145
        }
146
147
        $resource = fopen('php://temp', 'wb+');
148
        FunctionsExport::exportGedcom($tree, $resource, $access_level, $media_path, $encoding);
149
        rewind($resource);
150
151
        $charset = $convert ? 'ISO-8859-1' : 'UTF-8';
152
153
        /** @var StreamFactoryInterface $response_factory */
154
        $stream_factory = app(StreamFactoryInterface::class);
155
156
        $stream = $stream_factory->createStreamFromResource($resource);
0 ignored issues
show
Bug introduced by
It seems like $resource can also be of type false; however, parameter $resource of Psr\Http\Message\StreamF...ateStreamFromResource() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

156
        $stream = $stream_factory->createStreamFromResource(/** @scrutinizer ignore-type */ $resource);
Loading history...
157
158
        /** @var ResponseFactoryInterface $response_factory */
159
        $response_factory = app(ResponseFactoryInterface::class);
160
161
        return $response_factory->createResponse()
162
            ->withBody($stream)
163
            ->withHeader('Content-Type', 'text/x-gedcom; charset=' . $charset)
164
            ->withHeader('Content-Disposition', 'attachment; filename="' . addcslashes($download_filename, '"') . '"');
165
    }
166
}
167