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