xref: /webtrees/app/Module/MediaListModule.php (revision 24f2a3af38709f9bf0a739b30264240d20ba34e8)
167992b6aSRichard Cissee<?php
23976b470SGreg Roach
367992b6aSRichard Cissee/**
467992b6aSRichard Cissee * webtrees: online genealogy
589f7189bSGreg Roach * Copyright (C) 2021 webtrees development team
667992b6aSRichard Cissee * This program is free software: you can redistribute it and/or modify
767992b6aSRichard Cissee * it under the terms of the GNU General Public License as published by
867992b6aSRichard Cissee * the Free Software Foundation, either version 3 of the License, or
967992b6aSRichard Cissee * (at your option) any later version.
1067992b6aSRichard Cissee * This program is distributed in the hope that it will be useful,
1167992b6aSRichard Cissee * but WITHOUT ANY WARRANTY; without even the implied warranty of
1267992b6aSRichard Cissee * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1367992b6aSRichard Cissee * GNU General Public License for more details.
1467992b6aSRichard Cissee * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
1667992b6aSRichard Cissee */
17fcfa147eSGreg Roach
1867992b6aSRichard Cisseedeclare(strict_types=1);
1967992b6aSRichard Cissee
2067992b6aSRichard Cisseenamespace Fisharebest\Webtrees\Module;
2167992b6aSRichard Cissee
2206a438b4SGreg Roachuse Aura\Router\RouterContainer;
2306a438b4SGreg Roachuse Fig\Http\Message\RequestMethodInterface;
2467992b6aSRichard Cisseeuse Fisharebest\Webtrees\Auth;
2506a438b4SGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface;
266b9cb339SGreg Roachuse Fisharebest\Webtrees\Registry;
2706a438b4SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
2806a438b4SGreg Roachuse Fisharebest\Webtrees\GedcomTag;
2906a438b4SGreg Roachuse Fisharebest\Webtrees\I18N;
3006a438b4SGreg Roachuse Fisharebest\Webtrees\Media;
3106a438b4SGreg Roachuse Fisharebest\Webtrees\Tree;
3267992b6aSRichard Cisseeuse Illuminate\Database\Capsule\Manager as DB;
3306a438b4SGreg Roachuse Illuminate\Database\Query\Builder;
3406a438b4SGreg Roachuse Illuminate\Database\Query\JoinClause;
3506a438b4SGreg Roachuse Illuminate\Support\Collection;
366ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
376ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
3806a438b4SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
39f3874e19SGreg Roach
4006a438b4SGreg Roachuse function addcslashes;
4106a438b4SGreg Roachuse function app;
4206a438b4SGreg Roachuse function array_combine;
4306a438b4SGreg Roachuse function array_unshift;
445229eadeSGreg Roachuse function assert;
4506a438b4SGreg Roachuse function dirname;
4606a438b4SGreg Roachuse function max;
4706a438b4SGreg Roachuse function min;
484fbf5c97SGreg Roachuse function redirect;
494fbf5c97SGreg Roachuse function route;
5067992b6aSRichard Cissee
5167992b6aSRichard Cissee/**
5267992b6aSRichard Cissee * Class MediaListModule
5367992b6aSRichard Cissee */
5406a438b4SGreg Roachclass MediaListModule extends AbstractModule implements ModuleListInterface, RequestHandlerInterface
5567992b6aSRichard Cissee{
5667992b6aSRichard Cissee    use ModuleListTrait;
5767992b6aSRichard Cissee
5806a438b4SGreg Roach    protected const ROUTE_URL = '/tree/{tree}/media-list';
5906a438b4SGreg Roach
6006a438b4SGreg Roach    /**
6106a438b4SGreg Roach     * Initialization.
6206a438b4SGreg Roach     *
6306a438b4SGreg Roach     * @return void
6406a438b4SGreg Roach     */
6506a438b4SGreg Roach    public function boot(): void
6606a438b4SGreg Roach    {
6706a438b4SGreg Roach        $router_container = app(RouterContainer::class);
6806a438b4SGreg Roach        assert($router_container instanceof RouterContainer);
6906a438b4SGreg Roach
7006a438b4SGreg Roach        $router_container->getMap()
7106a438b4SGreg Roach            ->get(static::class, static::ROUTE_URL, $this)
7206a438b4SGreg Roach            ->allows(RequestMethodInterface::METHOD_POST);
7306a438b4SGreg Roach    }
7406a438b4SGreg Roach
7567992b6aSRichard Cissee    /**
760cfd6963SGreg Roach     * How should this module be identified in the control panel, etc.?
7767992b6aSRichard Cissee     *
7867992b6aSRichard Cissee     * @return string
7967992b6aSRichard Cissee     */
8067992b6aSRichard Cissee    public function title(): string
8167992b6aSRichard Cissee    {
8267992b6aSRichard Cissee        /* I18N: Name of a module/list */
8367992b6aSRichard Cissee        return I18N::translate('Media objects');
8467992b6aSRichard Cissee    }
8567992b6aSRichard Cissee
8667992b6aSRichard Cissee    /**
8767992b6aSRichard Cissee     * A sentence describing what this module does.
8867992b6aSRichard Cissee     *
8967992b6aSRichard Cissee     * @return string
9067992b6aSRichard Cissee     */
9167992b6aSRichard Cissee    public function description(): string
9267992b6aSRichard Cissee    {
93b5e8e56bSGreg Roach        /* I18N: Description of the “Media objects” module */
9467992b6aSRichard Cissee        return I18N::translate('A list of media objects.');
9567992b6aSRichard Cissee    }
9667992b6aSRichard Cissee
9767992b6aSRichard Cissee    /**
9867992b6aSRichard Cissee     * CSS class for the URL.
9967992b6aSRichard Cissee     *
10067992b6aSRichard Cissee     * @return string
10167992b6aSRichard Cissee     */
10267992b6aSRichard Cissee    public function listMenuClass(): string
10367992b6aSRichard Cissee    {
10467992b6aSRichard Cissee        return 'menu-list-obje';
10567992b6aSRichard Cissee    }
10667992b6aSRichard Cissee
1074db4b4a9SGreg Roach    /**
10806a438b4SGreg Roach     * @param Tree    $tree
10906a438b4SGreg Roach     * @param mixed[] $parameters
1104db4b4a9SGreg Roach     *
11106a438b4SGreg Roach     * @return string
1124db4b4a9SGreg Roach     */
11306a438b4SGreg Roach    public function listUrl(Tree $tree, array $parameters = []): string
11467992b6aSRichard Cissee    {
11506a438b4SGreg Roach        $parameters['tree'] = $tree->name();
1165229eadeSGreg Roach
11706a438b4SGreg Roach        return route(static::class, $parameters);
1184fbf5c97SGreg Roach    }
1194fbf5c97SGreg Roach
1204fbf5c97SGreg Roach    /**
121*24f2a3afSGreg Roach     * @return array<string>
1224db4b4a9SGreg Roach     */
12367992b6aSRichard Cissee    public function listUrlAttributes(): array
12467992b6aSRichard Cissee    {
12567992b6aSRichard Cissee        return [];
12667992b6aSRichard Cissee    }
12767992b6aSRichard Cissee
1284db4b4a9SGreg Roach    /**
1294db4b4a9SGreg Roach     * @param Tree $tree
1304db4b4a9SGreg Roach     *
1314db4b4a9SGreg Roach     * @return bool
1324db4b4a9SGreg Roach     */
13367992b6aSRichard Cissee    public function listIsEmpty(Tree $tree): bool
13467992b6aSRichard Cissee    {
13567992b6aSRichard Cissee        return !DB::table('media')
13667992b6aSRichard Cissee            ->where('m_file', '=', $tree->id())
13767992b6aSRichard Cissee            ->exists();
13867992b6aSRichard Cissee    }
13906a438b4SGreg Roach
14006a438b4SGreg Roach    /**
14106a438b4SGreg Roach     * Handle URLs generated by older versions of webtrees
14206a438b4SGreg Roach     *
14306a438b4SGreg Roach     * @param ServerRequestInterface $request
14406a438b4SGreg Roach     *
14506a438b4SGreg Roach     * @return ResponseInterface
14606a438b4SGreg Roach     */
14706a438b4SGreg Roach    public function getListAction(ServerRequestInterface $request): ResponseInterface
14806a438b4SGreg Roach    {
14906a438b4SGreg Roach        return redirect($this->listUrl($request->getAttribute('tree'), $request->getQueryParams()));
15006a438b4SGreg Roach    }
15106a438b4SGreg Roach
15206a438b4SGreg Roach    /**
15306a438b4SGreg Roach     * @param ServerRequestInterface $request
15406a438b4SGreg Roach     *
15506a438b4SGreg Roach     * @return ResponseInterface
15606a438b4SGreg Roach     */
15706a438b4SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
15806a438b4SGreg Roach    {
15906a438b4SGreg Roach        $tree = $request->getAttribute('tree');
16006a438b4SGreg Roach        assert($tree instanceof Tree);
16106a438b4SGreg Roach
16206a438b4SGreg Roach        $user = $request->getAttribute('user');
16306a438b4SGreg Roach        assert($user instanceof UserInterface);
16406a438b4SGreg Roach
1656b9cb339SGreg Roach        $data_filesystem = Registry::filesystem()->data();
16606a438b4SGreg Roach
16706a438b4SGreg Roach        Auth::checkComponentAccess($this, ModuleListInterface::class, $tree, $user);
16806a438b4SGreg Roach
16906a438b4SGreg Roach        // Convert POST requests into GET requests for pretty URLs.
17006a438b4SGreg Roach        if ($request->getMethod() === RequestMethodInterface::METHOD_POST) {
17106a438b4SGreg Roach            return redirect($this->listUrl($tree, (array) $request->getParsedBody()));
17206a438b4SGreg Roach        }
17306a438b4SGreg Roach
17406a438b4SGreg Roach        $params  = $request->getQueryParams();
175455a30feSGreg Roach        $formats = Registry::elementFactory()->make('OBJE:FILE:FORM:TYPE')->values();
17606a438b4SGreg Roach        $go      = $params['go'] ?? '';
17706a438b4SGreg Roach        $page    = (int) ($params['page'] ?? 1);
17806a438b4SGreg Roach        $max     = (int) ($params['max'] ?? 20);
17906a438b4SGreg Roach        $folder  = $params['folder'] ?? '';
18006a438b4SGreg Roach        $filter  = $params['filter'] ?? '';
18106a438b4SGreg Roach        $subdirs = $params['subdirs'] ?? '';
18206a438b4SGreg Roach        $format  = $params['format'] ?? '';
18306a438b4SGreg Roach
18406a438b4SGreg Roach        $folders = $this->allFolders($tree);
18506a438b4SGreg Roach
18606a438b4SGreg Roach        if ($go === '1') {
18706a438b4SGreg Roach            $media_objects = $this->allMedia(
18806a438b4SGreg Roach                $tree,
18906a438b4SGreg Roach                $folder,
19006a438b4SGreg Roach                $subdirs === '1' ? 'include' : 'exclude',
19106a438b4SGreg Roach                'title',
19206a438b4SGreg Roach                $filter,
19306a438b4SGreg Roach                $format
19406a438b4SGreg Roach            );
19506a438b4SGreg Roach        } else {
19606a438b4SGreg Roach            $media_objects = new Collection();
19706a438b4SGreg Roach        }
19806a438b4SGreg Roach
19906a438b4SGreg Roach        // Pagination
20006a438b4SGreg Roach        $count = $media_objects->count();
20106a438b4SGreg Roach        $pages = (int) (($count + $max - 1) / $max);
20206a438b4SGreg Roach        $page  = max(min($page, $pages), 1);
20306a438b4SGreg Roach
20406a438b4SGreg Roach        $media_objects = $media_objects->slice(($page - 1) * $max, $max);
20506a438b4SGreg Roach
20606a438b4SGreg Roach        return $this->viewResponse('modules/media-list/page', [
20706a438b4SGreg Roach            'count'           => $count,
20806a438b4SGreg Roach            'filter'          => $filter,
20906a438b4SGreg Roach            'folder'          => $folder,
21006a438b4SGreg Roach            'folders'         => $folders,
21106a438b4SGreg Roach            'format'          => $format,
21206a438b4SGreg Roach            'formats'         => $formats,
21306a438b4SGreg Roach            'max'             => $max,
21406a438b4SGreg Roach            'media_objects'   => $media_objects,
21506a438b4SGreg Roach            'page'            => $page,
21606a438b4SGreg Roach            'pages'           => $pages,
21706a438b4SGreg Roach            'subdirs'         => $subdirs,
21806a438b4SGreg Roach            'data_filesystem' => $data_filesystem,
21906a438b4SGreg Roach            'module'          => $this,
22006a438b4SGreg Roach            'title'           => I18N::translate('Media'),
22106a438b4SGreg Roach            'tree'            => $tree,
22206a438b4SGreg Roach        ]);
22306a438b4SGreg Roach    }
22406a438b4SGreg Roach
22506a438b4SGreg Roach    /**
22606a438b4SGreg Roach     * Generate a list of all the folders in a current tree.
22706a438b4SGreg Roach     *
22806a438b4SGreg Roach     * @param Tree $tree
22906a438b4SGreg Roach     *
230*24f2a3afSGreg Roach     * @return array<string>
23106a438b4SGreg Roach     */
23206a438b4SGreg Roach    private function allFolders(Tree $tree): array
23306a438b4SGreg Roach    {
23406a438b4SGreg Roach        $folders = DB::table('media_file')
23506a438b4SGreg Roach            ->where('m_file', '=', $tree->id())
23606a438b4SGreg Roach            ->where('multimedia_file_refn', 'NOT LIKE', 'http:%')
23706a438b4SGreg Roach            ->where('multimedia_file_refn', 'NOT LIKE', 'https:%')
23806a438b4SGreg Roach            ->where('multimedia_file_refn', 'LIKE', '%/%')
23906a438b4SGreg Roach            ->pluck('multimedia_file_refn', 'multimedia_file_refn')
24006a438b4SGreg Roach            ->map(static function (string $path): string {
24106a438b4SGreg Roach                return dirname($path);
24206a438b4SGreg Roach            })
24306a438b4SGreg Roach            ->uniqueStrict()
24406a438b4SGreg Roach            ->sort()
24506a438b4SGreg Roach            ->all();
24606a438b4SGreg Roach
24706a438b4SGreg Roach        // Ensure we have an empty (top level) folder.
24806a438b4SGreg Roach        array_unshift($folders, '');
24906a438b4SGreg Roach
25006a438b4SGreg Roach        return array_combine($folders, $folders);
25106a438b4SGreg Roach    }
25206a438b4SGreg Roach
25306a438b4SGreg Roach    /**
25406a438b4SGreg Roach     * Generate a list of all the media objects matching the criteria in a current tree.
25506a438b4SGreg Roach     *
25606a438b4SGreg Roach     * @param Tree   $tree       find media in this tree
25706a438b4SGreg Roach     * @param string $folder     folder to search
25806a438b4SGreg Roach     * @param string $subfolders either "include" or "exclude"
25906a438b4SGreg Roach     * @param string $sort       either "file" or "title"
26006a438b4SGreg Roach     * @param string $filter     optional search string
26106a438b4SGreg Roach     * @param string $format     option OBJE/FILE/FORM/TYPE
26206a438b4SGreg Roach     *
26306a438b4SGreg Roach     * @return Collection<Media>
26406a438b4SGreg Roach     */
26506a438b4SGreg Roach    private function allMedia(Tree $tree, string $folder, string $subfolders, string $sort, string $filter, string $format): Collection
26606a438b4SGreg Roach    {
26706a438b4SGreg Roach        $query = DB::table('media')
26806a438b4SGreg Roach            ->join('media_file', static function (JoinClause $join): void {
26906a438b4SGreg Roach                $join
27006a438b4SGreg Roach                    ->on('media_file.m_file', '=', 'media.m_file')
27106a438b4SGreg Roach                    ->on('media_file.m_id', '=', 'media.m_id');
27206a438b4SGreg Roach            })
27306a438b4SGreg Roach            ->where('media.m_file', '=', $tree->id());
27406a438b4SGreg Roach
27506a438b4SGreg Roach        if ($folder === '') {
27606a438b4SGreg Roach            // Include external URLs in the root folder.
27706a438b4SGreg Roach            if ($subfolders === 'exclude') {
27806a438b4SGreg Roach                $query->where(static function (Builder $query): void {
27906a438b4SGreg Roach                    $query
28006a438b4SGreg Roach                        ->where('multimedia_file_refn', 'NOT LIKE', '%/%')
28106a438b4SGreg Roach                        ->orWhere('multimedia_file_refn', 'LIKE', 'http:%')
28206a438b4SGreg Roach                        ->orWhere('multimedia_file_refn', 'LIKE', 'https:%');
28306a438b4SGreg Roach                });
28406a438b4SGreg Roach            }
28506a438b4SGreg Roach        } else {
28606a438b4SGreg Roach            // Exclude external URLs from the root folder.
28706a438b4SGreg Roach            $query
28806a438b4SGreg Roach                ->where('multimedia_file_refn', 'LIKE', $folder . '/%')
28906a438b4SGreg Roach                ->where('multimedia_file_refn', 'NOT LIKE', 'http:%')
29006a438b4SGreg Roach                ->where('multimedia_file_refn', 'NOT LIKE', 'https:%');
29106a438b4SGreg Roach
29206a438b4SGreg Roach            if ($subfolders === 'exclude') {
29306a438b4SGreg Roach                $query->where('multimedia_file_refn', 'NOT LIKE', $folder . '/%/%');
29406a438b4SGreg Roach            }
29506a438b4SGreg Roach        }
29606a438b4SGreg Roach
29706a438b4SGreg Roach        // Apply search terms
29806a438b4SGreg Roach        if ($filter !== '') {
29906a438b4SGreg Roach            $query->where(static function (Builder $query) use ($filter): void {
30006a438b4SGreg Roach                $like = '%' . addcslashes($filter, '\\%_') . '%';
30106a438b4SGreg Roach                $query
30206a438b4SGreg Roach                    ->where('multimedia_file_refn', 'LIKE', $like)
30306a438b4SGreg Roach                    ->orWhere('descriptive_title', 'LIKE', $like);
30406a438b4SGreg Roach            });
30506a438b4SGreg Roach        }
30606a438b4SGreg Roach
30706a438b4SGreg Roach        if ($format) {
30806a438b4SGreg Roach            $query->where('source_media_type', '=', $format);
30906a438b4SGreg Roach        }
31006a438b4SGreg Roach
31106a438b4SGreg Roach        switch ($sort) {
31206a438b4SGreg Roach            case 'file':
31306a438b4SGreg Roach                $query->orderBy('multimedia_file_refn');
31406a438b4SGreg Roach                break;
31506a438b4SGreg Roach            case 'title':
31606a438b4SGreg Roach                $query->orderBy('descriptive_title');
31706a438b4SGreg Roach                break;
31806a438b4SGreg Roach        }
31906a438b4SGreg Roach
32006a438b4SGreg Roach        return $query
32106a438b4SGreg Roach            ->get()
3226b9cb339SGreg Roach            ->map(Registry::mediaFactory()->mapper($tree))
32306a438b4SGreg Roach            ->uniqueStrict()
32406a438b4SGreg Roach            ->filter(GedcomRecord::accessFilter());
32506a438b4SGreg Roach    }
32667992b6aSRichard Cissee}
327