Passed
Push — main ( 036af8...1fe597 )
by Greg
08:29
created

TreeExport::autoCompleteTreeName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
rs 10
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2025 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 <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Cli\Commands;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\DB;
24
use Fisharebest\Webtrees\Services\GedcomExportService;
25
use Fisharebest\Webtrees\Services\TreeService;
26
use Symfony\Component\Console\Completion\CompletionInput;
27
use Symfony\Component\Console\Input\InputArgument;
28
use Symfony\Component\Console\Input\InputInterface;
29
use Symfony\Component\Console\Input\InputOption;
30
use Symfony\Component\Console\Output\OutputInterface;
31
use Symfony\Component\Console\Style\SymfonyStyle;
32
use ZipArchive;
33
34
use function addcslashes;
35
use function filesize;
36
use function stream_get_contents;
37
38
final class TreeExport extends AbstractCommand
39
{
40
    private const array ACCESS_LEVELS = [
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 40 at column 24
Loading history...
41
        'none'    => Auth::PRIV_HIDE,
42
        'manager' => Auth::PRIV_NONE,
43
        'member'  => Auth::PRIV_USER,
44
        'visitor' => Auth::PRIV_PRIVATE,
45
    ];
46
47
    public function __construct(
48
        private readonly GedcomExportService $gedcom_export_service,
49
        private readonly TreeService $tree_service,
50
    ) {
51
        parent::__construct();
52
    }
53
54
    protected function configure(): void
55
    {
56
        $this
57
            ->setName(name: 'tree-export')
58
            ->addArgument(name: 'tree_name', mode: InputArgument::REQUIRED, description: 'The name of the tree', suggestedValues: self::autoCompleteTreeName(...))
59
            ->addOption(name: 'format', mode: InputOption::VALUE_REQUIRED, description: 'Export format: gedcom (default), gedzip, zip or zipmedia')
60
            ->addOption(name: 'privacy', mode: InputOption::VALUE_REQUIRED, description: 'Apply privacy: none (default), manager, member or visitor')
61
            ->setDescription(description: 'Export a tree to a GEDCOM file');
62
    }
63
64
    /**
65
     * @return array<string>
66
     */
67
    private function autoCompleteTreeName(CompletionInput $input): array
68
    {
69
        return DB::table('tree')
70
            ->where('tree_name', 'LIKE', addcslashes($input->getCompletionValue(), '%_\\') . '%')
71
            ->pluck('name')
72
            ->all();
73
    }
74
75
    protected function execute(InputInterface $input, OutputInterface $output): int
76
    {
77
        $io = new SymfonyStyle(input: $input, output: $output);
78
79
        $tree_name = $this->stringArgument(input: $input, name: 'tree_name');
80
        $format    = $this->stringOption(input: $input, name: 'format');
81
        $privacy   = $this->stringOption(input: $input, name: 'privacy');
82
83
        if ($format === '') {
84
            $format = 'gedcom';
85
        }
86
87
        if ($privacy === '') {
88
            $privacy = 'none';
89
        }
90
91
        $access_level = self::ACCESS_LEVELS[$privacy] ?? null;
92
93
        if ($access_level === null) {
94
            $io->error(message: 'privacy option should be none, manager, member or visitor');
95
96
            return self::FAILURE;
97
        }
98
99
        $tree = $this->tree_service->all()[$tree_name] ?? null;
100
101
        if ($tree === null) {
102
            $io->error(message: 'Tree "' . $tree_name . '" not found.');
103
104
            return self::FAILURE;
105
        }
106
107
        $start_time = microtime(true);
108
109
        switch ($format) {
110
            case 'gedcom':
111
                $media_path     = null;
112
                $filename       = $tree_name . '.ged';
113
                $zip_filesystem = null;
114
                break;
115
116
            case 'gedzip':
117
                $media_path     = '';
118
                $filename       = $tree_name . '.gdz';
119
                $zip_filesystem = new ZipArchive();
120
                $zip_filesystem->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
121
                break;
122
123
            case 'zip':
124
                $media_path     = null;
125
                $filename       = $tree_name . '.zip';
126
                $zip_filesystem = new ZipArchive();
127
                $zip_filesystem->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
128
                break;
129
130
            case 'zipmedia':
131
                $media_path     = $tree->getPreference('MEDIA_DIRECTORY');
132
                $filename       = $tree_name . '.zip';
133
                $zip_filesystem = new ZipArchive();
134
                $zip_filesystem->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
135
                break;
136
137
            default:
138
                $io->error(message: 'Format option should be gedcom, gedzip, zip or zipmedia');
139
140
                return self::FAILURE;
141
        }
142
143
        $resource = $this->gedcom_export_service->export(
144
            tree: $tree,
145
            sort_by_xref: true,
146
            access_level: $access_level,
147
            zip_filesystem: $zip_filesystem,
148
            media_path: $media_path,
149
        );
150
151
        $gedcom = stream_get_contents($resource);
152
        fclose($resource);
153
154
        if ($gedcom === false) {
155
            $io->error(message: 'Failed to read GEDCOM');
156
157
            return self::FAILURE;
158
        }
159
160
        switch ($format) {
161
            case 'gedcom':
162
                file_put_contents($filename, $gedcom);
163
                break;
164
165
            case 'gedzip':
166
                $zip_filesystem->addFromString('gedcom.ged', $gedcom);
167
                $zip_filesystem->close();
168
                break;
169
170
            case 'zip':
171
            case 'zipmedia':
172
                $zip_filesystem->addFromString($tree_name . '.ged', $gedcom);
173
                $zip_filesystem->close();
174
                break;
175
        }
176
177
        $bytes = filesize($filename);
178
        $seconds = microtime(true) - $start_time;
179
        $message = sprintf('File exported successfully.  %d bytes written to %s in %.3f seconds', $bytes, $filename, $seconds);
180
181
        $io->success($message);
182
183
        return self::SUCCESS;
184
    }
185
}
186