Passed
Push — master ( 7b2840...06a438 )
by Greg
06:41
created

MediaListModule::listUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
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\Module;
21
22
use Aura\Router\RouterContainer;
23
use Fig\Http\Message\RequestMethodInterface;
24
use Fisharebest\Webtrees\Auth;
25
use Fisharebest\Webtrees\Contracts\UserInterface;
26
use Fisharebest\Webtrees\Factory;
27
use Fisharebest\Webtrees\GedcomRecord;
28
use Fisharebest\Webtrees\GedcomTag;
29
use Fisharebest\Webtrees\I18N;
30
use Fisharebest\Webtrees\Media;
31
use Fisharebest\Webtrees\Tree;
32
use Illuminate\Database\Capsule\Manager as DB;
33
use Illuminate\Database\Query\Builder;
34
use Illuminate\Database\Query\JoinClause;
35
use Illuminate\Support\Collection;
36
use League\Flysystem\FilesystemInterface;
37
use Psr\Http\Message\ResponseInterface;
38
use Psr\Http\Message\ServerRequestInterface;
39
use Psr\Http\Server\RequestHandlerInterface;
40
41
use function addcslashes;
42
use function app;
43
use function array_combine;
44
use function array_unshift;
45
use function assert;
46
use function dirname;
47
use function max;
48
use function min;
49
use function redirect;
50
use function route;
51
52
/**
53
 * Class MediaListModule
54
 */
55
class MediaListModule extends AbstractModule implements ModuleListInterface, RequestHandlerInterface
56
{
57
    use ModuleListTrait;
58
59
    protected const ROUTE_URL = '/tree/{tree}/media-list';
60
61
    /**
62
     * Initialization.
63
     *
64
     * @return void
65
     */
66
    public function boot(): void
67
    {
68
        $router_container = app(RouterContainer::class);
69
        assert($router_container instanceof RouterContainer);
70
71
        $router_container->getMap()
72
            ->get(static::class, static::ROUTE_URL, $this)
73
            ->allows(RequestMethodInterface::METHOD_POST);
74
    }
75
76
    /**
77
     * How should this module be identified in the control panel, etc.?
78
     *
79
     * @return string
80
     */
81
    public function title(): string
82
    {
83
        /* I18N: Name of a module/list */
84
        return I18N::translate('Media objects');
85
    }
86
87
    /**
88
     * A sentence describing what this module does.
89
     *
90
     * @return string
91
     */
92
    public function description(): string
93
    {
94
        /* I18N: Description of the “Media objects” module */
95
        return I18N::translate('A list of media objects.');
96
    }
97
98
    /**
99
     * CSS class for the URL.
100
     *
101
     * @return string
102
     */
103
    public function listMenuClass(): string
104
    {
105
        return 'menu-list-obje';
106
    }
107
108
    /**
109
     * @param Tree    $tree
110
     * @param mixed[] $parameters
111
     *
112
     * @return string
113
     */
114
    public function listUrl(Tree $tree, array $parameters = []): string
115
    {
116
        $parameters['tree'] = $tree->name();
117
118
        return route(static::class, $parameters);
119
    }
120
121
    /**
122
     * @return string[]
123
     */
124
    public function listUrlAttributes(): array
125
    {
126
        return [];
127
    }
128
129
    /**
130
     * @param Tree $tree
131
     *
132
     * @return bool
133
     */
134
    public function listIsEmpty(Tree $tree): bool
135
    {
136
        return !DB::table('media')
137
            ->where('m_file', '=', $tree->id())
138
            ->exists();
139
    }
140
141
    /**
142
     * Handle URLs generated by older versions of webtrees
143
     *
144
     * @param ServerRequestInterface $request
145
     *
146
     * @return ResponseInterface
147
     */
148
    public function getListAction(ServerRequestInterface $request): ResponseInterface
149
    {
150
        return redirect($this->listUrl($request->getAttribute('tree'), $request->getQueryParams()));
151
    }
152
153
    /**
154
     * @param ServerRequestInterface $request
155
     *
156
     * @return ResponseInterface
157
     */
158
    public function handle(ServerRequestInterface $request): ResponseInterface
159
    {
160
        $tree = $request->getAttribute('tree');
161
        assert($tree instanceof Tree);
162
163
        $user = $request->getAttribute('user');
164
        assert($user instanceof UserInterface);
165
166
        $data_filesystem = $request->getAttribute('filesystem.data');
167
        assert($data_filesystem instanceof FilesystemInterface);
168
169
        Auth::checkComponentAccess($this, ModuleListInterface::class, $tree, $user);
170
171
        // Convert POST requests into GET requests for pretty URLs.
172
        if ($request->getMethod() === RequestMethodInterface::METHOD_POST) {
173
            return redirect($this->listUrl($tree, (array) $request->getParsedBody()));
174
        }
175
176
        $params  = $request->getQueryParams();
177
        $formats = GedcomTag::getFileFormTypes();
178
        $go      = $params['go'] ?? '';
179
        $page    = (int) ($params['page'] ?? 1);
180
        $max     = (int) ($params['max'] ?? 20);
181
        $folder  = $params['folder'] ?? '';
182
        $filter  = $params['filter'] ?? '';
183
        $subdirs = $params['subdirs'] ?? '';
184
        $format  = $params['format'] ?? '';
185
186
        $folders = $this->allFolders($tree);
187
188
        if ($go === '1') {
189
            $media_objects = $this->allMedia(
190
                $tree,
191
                $folder,
192
                $subdirs === '1' ? 'include' : 'exclude',
193
                'title',
194
                $filter,
195
                $format
196
            );
197
        } else {
198
            $media_objects = new Collection();
199
        }
200
201
        // Pagination
202
        $count = $media_objects->count();
203
        $pages = (int) (($count + $max - 1) / $max);
204
        $page  = max(min($page, $pages), 1);
205
206
        $media_objects = $media_objects->slice(($page - 1) * $max, $max);
207
208
        return $this->viewResponse('modules/media-list/page', [
209
            'count'           => $count,
210
            'filter'          => $filter,
211
            'folder'          => $folder,
212
            'folders'         => $folders,
213
            'format'          => $format,
214
            'formats'         => $formats,
215
            'max'             => $max,
216
            'media_objects'   => $media_objects,
217
            'page'            => $page,
218
            'pages'           => $pages,
219
            'subdirs'         => $subdirs,
220
            'data_filesystem' => $data_filesystem,
221
            'module'          => $this,
222
            'title'           => I18N::translate('Media'),
223
            'tree'            => $tree,
224
        ]);
225
    }
226
227
    /**
228
     * Generate a list of all the folders in a current tree.
229
     *
230
     * @param Tree $tree
231
     *
232
     * @return string[]
233
     */
234
    private function allFolders(Tree $tree): array
235
    {
236
        $folders = DB::table('media_file')
237
            ->where('m_file', '=', $tree->id())
238
            ->where('multimedia_file_refn', 'NOT LIKE', 'http:%')
239
            ->where('multimedia_file_refn', 'NOT LIKE', 'https:%')
240
            ->where('multimedia_file_refn', 'LIKE', '%/%')
241
            ->pluck('multimedia_file_refn', 'multimedia_file_refn')
242
            ->map(static function (string $path): string {
243
                return dirname($path);
244
            })
245
            ->uniqueStrict()
246
            ->sort()
247
            ->all();
248
249
        // Ensure we have an empty (top level) folder.
250
        array_unshift($folders, '');
251
252
        return array_combine($folders, $folders);
253
    }
254
255
    /**
256
     * Generate a list of all the media objects matching the criteria in a current tree.
257
     *
258
     * @param Tree   $tree       find media in this tree
259
     * @param string $folder     folder to search
260
     * @param string $subfolders either "include" or "exclude"
261
     * @param string $sort       either "file" or "title"
262
     * @param string $filter     optional search string
263
     * @param string $format     option OBJE/FILE/FORM/TYPE
264
     *
265
     * @return Collection<Media>
266
     */
267
    private function allMedia(Tree $tree, string $folder, string $subfolders, string $sort, string $filter, string $format): Collection
268
    {
269
        $query = DB::table('media')
270
            ->join('media_file', static function (JoinClause $join): void {
271
                $join
272
                    ->on('media_file.m_file', '=', 'media.m_file')
273
                    ->on('media_file.m_id', '=', 'media.m_id');
274
            })
275
            ->where('media.m_file', '=', $tree->id());
276
277
        if ($folder === '') {
278
            // Include external URLs in the root folder.
279
            if ($subfolders === 'exclude') {
280
                $query->where(static function (Builder $query): void {
281
                    $query
282
                        ->where('multimedia_file_refn', 'NOT LIKE', '%/%')
283
                        ->orWhere('multimedia_file_refn', 'LIKE', 'http:%')
284
                        ->orWhere('multimedia_file_refn', 'LIKE', 'https:%');
285
                });
286
            }
287
        } else {
288
            // Exclude external URLs from the root folder.
289
            $query
290
                ->where('multimedia_file_refn', 'LIKE', $folder . '/%')
291
                ->where('multimedia_file_refn', 'NOT LIKE', 'http:%')
292
                ->where('multimedia_file_refn', 'NOT LIKE', 'https:%');
293
294
            if ($subfolders === 'exclude') {
295
                $query->where('multimedia_file_refn', 'NOT LIKE', $folder . '/%/%');
296
            }
297
        }
298
299
        // Apply search terms
300
        if ($filter !== '') {
301
            $query->where(static function (Builder $query) use ($filter): void {
302
                $like = '%' . addcslashes($filter, '\\%_') . '%';
303
                $query
304
                    ->where('multimedia_file_refn', 'LIKE', $like)
305
                    ->orWhere('descriptive_title', 'LIKE', $like);
306
            });
307
        }
308
309
        if ($format) {
310
            $query->where('source_media_type', '=', $format);
311
        }
312
313
        switch ($sort) {
314
            case 'file':
315
                $query->orderBy('multimedia_file_refn');
316
                break;
317
            case 'title':
318
                $query->orderBy('descriptive_title');
319
                break;
320
        }
321
322
        return $query
323
            ->get()
324
            ->map(Factory::media()->mapper($tree))
325
            ->uniqueStrict()
326
            ->filter(GedcomRecord::accessFilter());
327
    }
328
}
329