Passed
Push — develop ( 2a2c9f...41115c )
by Greg
16:48 queued 06:17
created

MediaListModule::handle()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 62
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 46
nc 3
nop 1
dl 0
loc 62
rs 9.1781
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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