Completed
Push — master ( 8091bf...8ce3bd )
by Greg
06:22
created

ManageMediaData   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 275
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 147
c 0
b 0
f 0
dl 0
loc 275
rs 10
wmc 25

4 Methods

Rating   Name   Duplication   Size   Complexity  
C handle() 0 157 12
A mediaFileInfo() 0 29 3
A __construct() 0 8 1
B mediaObjectInfo() 0 36 9
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2020 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\Exceptions\HttpNotFoundException;
23
use Fisharebest\Webtrees\I18N;
24
use Fisharebest\Webtrees\Media;
25
use Fisharebest\Webtrees\Mime;
26
use Fisharebest\Webtrees\Registry;
27
use Fisharebest\Webtrees\Services\DatatablesService;
28
use Fisharebest\Webtrees\Services\MediaFileService;
29
use Fisharebest\Webtrees\Services\TreeService;
30
use Fisharebest\Webtrees\Webtrees;
31
use Illuminate\Database\Capsule\Manager as DB;
32
use Illuminate\Database\Query\Builder;
33
use Illuminate\Database\Query\Expression;
34
use Illuminate\Database\Query\JoinClause;
35
use League\Flysystem\FileNotFoundException;
36
use League\Flysystem\FilesystemInterface;
37
use Psr\Http\Message\ResponseInterface;
38
use Psr\Http\Message\ServerRequestInterface;
39
use Psr\Http\Server\RequestHandlerInterface;
40
use stdClass;
41
use Throwable;
42
43
use function assert;
44
use function e;
45
use function getimagesize;
46
use function intdiv;
47
use function route;
48
use function str_starts_with;
49
use function strlen;
50
use function substr;
51
use function view;
52
53
/**
54
 * Manage media from the control panel.
55
 */
56
class ManageMediaData implements RequestHandlerInterface
57
{
58
    /** @var DatatablesService */
59
    private $datatables_service;
60
61
    /** @var MediaFileService */
62
    private $media_file_service;
63
64
    /** @var TreeService */
65
    private $tree_service;
66
67
    /**
68
     * MediaController constructor.
69
     *
70
     * @param DatatablesService $datatables_service
71
     * @param MediaFileService  $media_file_service
72
     * @param TreeService       $tree_service
73
     */
74
    public function __construct(
75
        DatatablesService $datatables_service,
76
        MediaFileService $media_file_service,
77
        TreeService $tree_service
78
    ) {
79
        $this->datatables_service = $datatables_service;
80
        $this->media_file_service = $media_file_service;
81
        $this->tree_service       = $tree_service;
82
    }
83
84
    /**
85
     * @param ServerRequestInterface $request
86
     *
87
     * @return ResponseInterface
88
     */
89
    public function handle(ServerRequestInterface $request): ResponseInterface
90
    {
91
        $data_filesystem = Registry::filesystem()->data();
92
93
        $files = $request->getQueryParams()['files']; // local|external|unused
94
95
        // Files within this folder
96
        $media_folder = $request->getQueryParams()['media_folder'];
97
98
        // Show sub-folders within $media_folder
99
        $subfolders = $request->getQueryParams()['subfolders']; // include|exclude
100
101
        $search_columns = ['multimedia_file_refn', 'descriptive_title'];
102
103
        $sort_columns = [
104
            0 => 'multimedia_file_refn',
105
            2 => (string) new Expression('descriptive_title || multimedia_file_refn'),
106
        ];
107
108
        // Convert a row from the database into a row for datatables
109
        $callback = function (stdClass $row): array {
110
            $tree  = $this->tree_service->find((int) $row->m_file);
111
            $media = Registry::mediaFactory()->make($row->m_id, $tree, $row->m_gedcom);
112
            assert($media instanceof Media);
113
114
            $path = $row->media_folder . $row->multimedia_file_refn;
115
116
            try {
117
                $mime_type = Registry::filesystem()->data()->getMimeType($path) ?: Mime::DEFAULT_TYPE;
118
119
                if (str_starts_with($mime_type, 'image/')) {
120
                    $url = route(AdminMediaFileThumbnail::class, ['path' => $path]);
121
                    $img = '<img src="' . e($url) . '">';
122
                } else {
123
                    $img = view('icons/mime', ['type' => $mime_type]);
124
                }
125
126
                $url = route(AdminMediaFileDownload::class, ['path' => $path]);
127
                $img = '<a href="' . e($url) . '" type="' . $mime_type . '" class="gallery">' . $img . '</a>';
128
            } catch (FileNotFoundException $ex) {
129
                $url = route(AdminMediaFileThumbnail::class, ['path' => $path]);
130
                $img = '<img src="' . e($url) . '">';
131
            }
132
133
            return [
134
                $row->multimedia_file_refn,
135
                $img,
136
                $this->mediaObjectInfo($media),
137
            ];
138
        };
139
140
        switch ($files) {
141
            case 'local':
142
                $query = DB::table('media_file')
143
                    ->join('media', static function (JoinClause $join): void {
144
                        $join
145
                            ->on('media.m_file', '=', 'media_file.m_file')
146
                            ->on('media.m_id', '=', 'media_file.m_id');
147
                    })
148
                    ->join('gedcom_setting', 'gedcom_id', '=', 'media.m_file')
149
                    ->where('setting_name', '=', 'MEDIA_DIRECTORY')
150
                    ->where('multimedia_file_refn', 'NOT LIKE', 'http://%')
151
                    ->where('multimedia_file_refn', 'NOT LIKE', 'https://%')
152
                    ->select([
153
                        'media.*',
154
                        'multimedia_file_refn',
155
                        'descriptive_title',
156
                        'setting_value AS media_folder',
157
                    ]);
158
159
                $query->where(new Expression('setting_value || multimedia_file_refn'), 'LIKE', $media_folder . '%');
160
161
                if ($subfolders === 'exclude') {
162
                    $query->where(new Expression('setting_value || multimedia_file_refn'), 'NOT LIKE', $media_folder . '%/%');
163
                }
164
165
                return $this->datatables_service->handleQuery($request, $query, $search_columns, $sort_columns, $callback);
166
167
            case 'external':
168
                $query = DB::table('media_file')
169
                    ->join('media', static function (JoinClause $join): void {
170
                        $join
171
                            ->on('media.m_file', '=', 'media_file.m_file')
172
                            ->on('media.m_id', '=', 'media_file.m_id');
173
                    })
174
                    ->where(static function (Builder $query): void {
175
                        $query
176
                            ->where('multimedia_file_refn', 'LIKE', 'http://%')
177
                            ->orWhere('multimedia_file_refn', 'LIKE', 'https://%');
178
                    })
179
                    ->select([
180
                        'media.*',
181
                        'multimedia_file_refn',
182
                        'descriptive_title',
183
                        (string) new Expression("'' AS media_folder"),
184
                    ]);
185
186
                return $this->datatables_service->handleQuery($request, $query, $search_columns, $sort_columns, $callback);
187
188
            case 'unused':
189
                // Which trees use which media folder?
190
                $media_trees = DB::table('gedcom')
191
                    ->join('gedcom_setting', 'gedcom_setting.gedcom_id', '=', 'gedcom.gedcom_id')
192
                    ->where('setting_name', '=', 'MEDIA_DIRECTORY')
193
                    ->where('gedcom.gedcom_id', '>', 0)
194
                    ->pluck('setting_value', 'gedcom_name');
195
196
                $disk_files = $this->media_file_service->allFilesOnDisk($data_filesystem, $media_folder, $subfolders === 'include');
197
                $db_files   = $this->media_file_service->allFilesInDatabase($media_folder, $subfolders === 'include');
198
199
                // All unused files
200
                $unused_files = $disk_files->diff($db_files)
201
                    ->map(static function (string $file): array {
202
                        return (array) $file;
203
                    });
204
205
                $search_columns = [0];
206
                $sort_columns   = [0 => 0];
207
208
                $callback = function (array $row) use ($data_filesystem, $media_trees): array {
209
                    $mime_type = $data_filesystem->getMimeType($row[0]) ?: Mime::DEFAULT_TYPE;
210
211
                    if (str_starts_with($mime_type, 'image/')) {
212
                        $url = route(AdminMediaFileThumbnail::class, ['path' => $row[0]]);
213
                        $img = '<img src="' . e($url) . '">';
214
                    } else {
215
                        $img = view('icons/mime', ['type' => $mime_type]);
216
                    }
217
218
                    $url = route(AdminMediaFileDownload::class, ['path' => $row[0]]);
219
                    $img = '<a href="' . e($url) . '">' . $img . '</a>';
220
221
                    // Form to create new media object in each tree
222
                    $create_form = '';
223
                    foreach ($media_trees as $media_tree => $media_directory) {
224
                        if (str_starts_with($row[0], $media_directory)) {
225
                            $tmp         = substr($row[0], strlen($media_directory));
226
                            $create_form .=
227
                                '<p><a href="#" data-toggle="modal" data-backdrop="static" data-target="#modal-create-media-from-file" data-file="' . e($tmp) . '" data-url="' . e(route('create-media-from-file', ['tree' => $media_tree])) . '" onclick="document.getElementById(\'modal-create-media-from-file-form\').action=this.dataset.url; document.getElementById(\'file\').value=this.dataset.file;">' . I18N::translate('Create') . '</a> — ' . e($media_tree) . '<p>';
228
                        }
229
                    }
230
231
                    $delete_link = '<p><a data-confirm="' . I18N::translate('Are you sure you want to delete “%s”?', e($row[0])) . '" data-post-url="' . e(route(DeletePath::class, [
232
                            'path' => $row[0],
233
                        ])) . '" href="#">' . I18N::translate('Delete') . '</a></p>';
234
235
                    return [
236
                        $this->mediaFileInfo($data_filesystem, $row[0]) . $delete_link,
237
                        $img,
238
                        $create_form,
239
                    ];
240
                };
241
242
                return $this->datatables_service->handleCollection($request, $unused_files, $search_columns, $sort_columns, $callback);
243
244
            default:
245
                throw new HttpNotFoundException();
246
        }
247
    }
248
249
    /**
250
     * Generate some useful information and links about a media object.
251
     *
252
     * @param Media $media
253
     *
254
     * @return string HTML
255
     */
256
    private function mediaObjectInfo(Media $media): string
257
    {
258
        $html = '<b><a href="' . e($media->url()) . '">' . $media->fullName() . '</a></b>' . '<br><i>' . e($media->getNote()) . '</i></br><br>';
259
260
        $linked = [];
261
        foreach ($media->linkedIndividuals('OBJE') as $link) {
262
            $linked[] = '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
263
        }
264
        foreach ($media->linkedFamilies('OBJE') as $link) {
265
            $linked[] = '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
266
        }
267
        foreach ($media->linkedSources('OBJE') as $link) {
268
            $linked[] = '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
269
        }
270
        foreach ($media->linkedNotes('OBJE') as $link) {
271
            // Invalid GEDCOM - you cannot link a NOTE to an OBJE
272
            $linked[] = '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
273
        }
274
        foreach ($media->linkedRepositories('OBJE') as $link) {
275
            // Invalid GEDCOM - you cannot link a REPO to an OBJE
276
            $linked[] = '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
277
        }
278
        foreach ($media->linkedLocations('OBJE') as $link) {
279
            $linked[] = '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
280
        }
281
        if ($linked !== []) {
282
            $html .= '<ul>';
283
            foreach ($linked as $link) {
284
                $html .= '<li>' . $link . '</li>';
285
            }
286
            $html .= '</ul>';
287
        } else {
288
            $html .= '<div class="alert alert-danger">' . I18N::translate('There are no links to this media object.') . '</div>';
289
        }
290
291
        return $html;
292
    }
293
294
    /**
295
     * Generate some useful information and links about a media file.
296
     *
297
     * @param FilesystemInterface $data_filesystem
298
     * @param string              $file
299
     *
300
     * @return string
301
     */
302
    private function mediaFileInfo(FilesystemInterface $data_filesystem, string $file): string
303
    {
304
        $html = '<dl>';
305
        $html .= '<dt>' . I18N::translate('Filename') . '</dt>';
306
        $html .= '<dd>' . e($file) . '</dd>';
307
308
        if ($data_filesystem->has($file)) {
309
            $size = $data_filesystem->getSize($file);
310
            $size = intdiv($size + 1023, 1024); // Round up to next KB
311
            /* I18N: size of file in KB */
312
            $size = I18N::translate('%s KB', I18N::number($size));
313
            $html .= '<dt>' . I18N::translate('File size') . '</dt>';
314
            $html .= '<dd>' . $size . '</dd>';
315
316
            try {
317
                // This will work for local filesystems.  For remote filesystems, we will
318
                // need to copy the file locally to work out the image size.
319
                $imgsize = getimagesize(Webtrees::DATA_DIR . $file);
320
                $html    .= '<dt>' . I18N::translate('Image dimensions') . '</dt>';
321
                /* I18N: image dimensions, width × height */
322
                $html .= '<dd>' . I18N::translate('%1$s × %2$s pixels', I18N::number($imgsize['0']), I18N::number($imgsize['1'])) . '</dd>';
323
            } catch (Throwable $ex) {
324
                // Not an image, or not a valid image?
325
            }
326
        }
327
328
        $html .= '</dl>';
329
330
        return $html;
331
    }
332
}
333