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