xref: /webtrees/app/Services/MediaFileService.php (revision a04bb9a236e92915034f97b1229f5d47c9bc750f)
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
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees\Services;
21
22use Fisharebest\Webtrees\FlashMessages;
23use Fisharebest\Webtrees\GedcomTag;
24use Fisharebest\Webtrees\I18N;
25use Fisharebest\Webtrees\Tree;
26use Illuminate\Database\Capsule\Manager as DB;
27use InvalidArgumentException;
28use League\Flysystem\FilesystemInterface;
29use Psr\Http\Message\ServerRequestInterface;
30use Psr\Http\Message\UploadedFileInterface;
31use RuntimeException;
32use Symfony\Component\HttpFoundation\File\UploadedFile;
33
34use function array_combine;
35use function array_diff;
36use function array_filter;
37use function array_map;
38use function assert;
39use function intdiv;
40use function pathinfo;
41use function preg_match;
42use function sha1;
43use function sort;
44use function str_replace;
45use function strpos;
46use function strtolower;
47use function trim;
48
49use const PATHINFO_EXTENSION;
50use const UPLOAD_ERR_OK;
51
52/**
53 * Managing media files.
54 */
55class MediaFileService
56{
57    public const EDIT_RESTRICTIONS = [
58        'locked',
59    ];
60
61    public const PRIVACY_RESTRICTIONS = [
62        'none',
63        'privacy',
64        'confidential',
65    ];
66
67    /**
68     * What is the largest file a user may upload?
69     */
70    public function maxUploadFilesize(): string
71    {
72        $bytes = UploadedFile::getMaxFilesize();
73        $kb    = intdiv($bytes + 1023, 1024);
74
75        return I18N::translate('%s KB', I18N::number($kb));
76    }
77
78    /**
79     * A list of key/value options for media types.
80     *
81     * @param string $current
82     *
83     * @return array
84     */
85    public function mediaTypes($current = ''): array
86    {
87        $media_types = GedcomTag::getFileFormTypes();
88
89        $media_types = ['' => ''] + [$current => $current] + $media_types;
90
91        return $media_types;
92    }
93
94    /**
95     * A list of media files not already linked to a media object.
96     *
97     * @param Tree                $tree
98     * @param FilesystemInterface $data_filesystem
99     *
100     * @return array
101     */
102    public function unusedFiles(Tree $tree, FilesystemInterface $data_filesystem): array
103    {
104        $used_files = DB::table('media_file')
105            ->where('m_file', '=', $tree->id())
106            ->where('multimedia_file_refn', 'NOT LIKE', 'http://%')
107            ->where('multimedia_file_refn', 'NOT LIKE', 'https://%')
108            ->pluck('multimedia_file_refn')
109            ->all();
110
111        $disk_files = $tree->mediaFilesystem($data_filesystem)->listContents('', true);
112
113        $disk_files = array_filter($disk_files, static function (array $item) {
114            // Older versions of webtrees used a couple of special folders.
115            return
116                $item['type'] === 'file' &&
117                strpos($item['path'], '/thumbs/') === false &&
118                strpos($item['path'], '/watermarks/') === false;
119        });
120
121        $disk_files = array_map(static function (array $item): string {
122            return $item['path'];
123        }, $disk_files);
124
125        $unused_files = array_diff($disk_files, $used_files);
126
127        sort($unused_files);
128
129        return array_combine($unused_files, $unused_files);
130    }
131
132    /**
133     * Store an uploaded file (or URL), either to be added to a media object
134     * or to create a media object.
135     *
136     * @param ServerRequestInterface $request
137     *
138     * @return string The value to be stored in the 'FILE' field of the media object.
139     */
140    public function uploadFile(ServerRequestInterface $request): string
141    {
142        $tree = $request->getAttribute('tree');
143        assert($tree instanceof Tree);
144
145        $data_filesystem = $request->getAttribute('filesystem.data');
146        assert($data_filesystem instanceof FilesystemInterface);
147
148        $params        = $request->getParsedBody();
149        $file_location = $params['file_location'];
150
151        switch ($file_location) {
152            case 'url':
153                $remote = $params['remote'];
154
155                if (strpos($remote, '://') !== false) {
156                    return $remote;
157                }
158
159                return '';
160
161            case 'unused':
162                $unused = $params['unused'];
163
164                if ($tree->mediaFilesystem($data_filesystem)->has($unused)) {
165                    return $unused;
166                }
167
168                return '';
169
170            case 'upload':
171            default:
172                $folder   = $params['folder'];
173                $auto     = $params['auto'];
174                $new_file = $params['new_file'];
175
176                /** @var UploadedFileInterface|null $uploaded_file */
177                $uploaded_file = $request->getUploadedFiles()['file'];
178                if ($uploaded_file === null || $uploaded_file->getError() !== UPLOAD_ERR_OK) {
179                    return '';
180                }
181
182                // The filename
183                $new_file = str_replace('\\', '/', $new_file);
184                if ($new_file !== '' && strpos($new_file, '/') === false) {
185                    $file = $new_file;
186                } else {
187                    $file = $uploaded_file->getClientFilename();
188                }
189
190                // The folder
191                $folder = str_replace('\\', '/', $folder);
192                $folder = trim($folder, '/');
193                if ($folder !== '') {
194                    $folder .= '/';
195                }
196
197                // Generate a unique name for the file?
198                if ($auto === '1' || $tree->mediaFilesystem($data_filesystem)->has($folder . $file)) {
199                    $folder    = '';
200                    $extension = pathinfo($uploaded_file->getClientFilename(), PATHINFO_EXTENSION);
201                    $file      = sha1((string) $uploaded_file->getStream()) . '.' . $extension;
202                }
203
204                try {
205                    $tree->mediaFilesystem($data_filesystem)->writeStream($folder . $file, $uploaded_file->getStream()->detach());
206
207                    return $folder . $file;
208                } catch (RuntimeException | InvalidArgumentException $ex) {
209                    FlashMessages::addMessage(I18N::translate('There was an error uploading your file.'));
210
211                    return '';
212                }
213        }
214    }
215
216    /**
217     * Convert the media file attributes into GEDCOM format.
218     *
219     * @param string $file
220     * @param string $type
221     * @param string $title
222     *
223     * @return string
224     */
225    public function createMediaFileGedcom(string $file, string $type, string $title): string
226    {
227        if (preg_match('/\.([a-z0-9]+)/i', $file, $match)) {
228            $extension = strtolower($match[1]);
229            $extension = str_replace('jpg', 'jpeg', $extension);
230            $extension = ' ' . $extension;
231        } else {
232            $extension = '';
233        }
234
235        $gedcom = '1 FILE ' . $file;
236        if ($type !== '') {
237            $gedcom .= "\n2 FORM" . $extension . "\n3 TYPE " . $type;
238        }
239        if ($title !== '') {
240            $gedcom .= "\n2 TITL " . $title;
241        }
242
243        return $gedcom;
244    }
245}
246