xref: /webtrees/app/Http/RequestHandlers/ManageMediaData.php (revision d11be7027e34e3121be11cc025421873364403f9)
18ce3bd73SGreg Roach<?php
28ce3bd73SGreg Roach
38ce3bd73SGreg Roach/**
48ce3bd73SGreg Roach * webtrees: online genealogy
5*d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
68ce3bd73SGreg Roach * This program is free software: you can redistribute it and/or modify
78ce3bd73SGreg Roach * it under the terms of the GNU General Public License as published by
88ce3bd73SGreg Roach * the Free Software Foundation, either version 3 of the License, or
98ce3bd73SGreg Roach * (at your option) any later version.
108ce3bd73SGreg Roach * This program is distributed in the hope that it will be useful,
118ce3bd73SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
128ce3bd73SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138ce3bd73SGreg Roach * GNU General Public License for more details.
148ce3bd73SGreg Roach * 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/>.
168ce3bd73SGreg Roach */
178ce3bd73SGreg Roach
188ce3bd73SGreg Roachdeclare(strict_types=1);
198ce3bd73SGreg Roach
208ce3bd73SGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers;
218ce3bd73SGreg Roach
2281b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException;
238ce3bd73SGreg Roachuse Fisharebest\Webtrees\I18N;
248ce3bd73SGreg Roachuse Fisharebest\Webtrees\Media;
258ce3bd73SGreg Roachuse Fisharebest\Webtrees\Mime;
268ce3bd73SGreg Roachuse Fisharebest\Webtrees\Registry;
278ce3bd73SGreg Roachuse Fisharebest\Webtrees\Services\DatatablesService;
284991f205SGreg Roachuse Fisharebest\Webtrees\Services\LinkedRecordService;
298ce3bd73SGreg Roachuse Fisharebest\Webtrees\Services\MediaFileService;
308ce3bd73SGreg Roachuse Fisharebest\Webtrees\Services\TreeService;
31748dbe15SGreg Roachuse Fisharebest\Webtrees\Validator;
328ce3bd73SGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
338ce3bd73SGreg Roachuse Illuminate\Database\Query\Builder;
348ce3bd73SGreg Roachuse Illuminate\Database\Query\Expression;
358ce3bd73SGreg Roachuse Illuminate\Database\Query\JoinClause;
36f0448b68SGreg Roachuse League\Flysystem\FilesystemException;
37f7cf8a15SGreg Roachuse League\Flysystem\FilesystemOperator;
38f0448b68SGreg Roachuse League\Flysystem\UnableToCheckFileExistence;
39f32d77e6SGreg Roachuse League\Flysystem\UnableToReadFile;
40f7cf8a15SGreg Roachuse League\Flysystem\UnableToRetrieveMetadata;
418ce3bd73SGreg Roachuse Psr\Http\Message\ResponseInterface;
428ce3bd73SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
438ce3bd73SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
448ce3bd73SGreg Roachuse Throwable;
458ce3bd73SGreg Roach
468ce3bd73SGreg Roachuse function assert;
478ce3bd73SGreg Roachuse function e;
48f7cf8a15SGreg Roachuse function getimagesizefromstring;
498ce3bd73SGreg Roachuse function intdiv;
508ce3bd73SGreg Roachuse function route;
518ce3bd73SGreg Roachuse function str_starts_with;
528ce3bd73SGreg Roachuse function strlen;
538ce3bd73SGreg Roachuse function substr;
548ce3bd73SGreg Roachuse function view;
558ce3bd73SGreg Roach
568ce3bd73SGreg Roach/**
578ce3bd73SGreg Roach * Manage media from the control panel.
588ce3bd73SGreg Roach */
598ce3bd73SGreg Roachclass ManageMediaData implements RequestHandlerInterface
608ce3bd73SGreg Roach{
61c4943cffSGreg Roach    private DatatablesService $datatables_service;
628ce3bd73SGreg Roach
634991f205SGreg Roach    private LinkedRecordService $linked_record_service;
644991f205SGreg Roach
65c4943cffSGreg Roach    private MediaFileService $media_file_service;
668ce3bd73SGreg Roach
67c4943cffSGreg Roach    private TreeService $tree_service;
688ce3bd73SGreg Roach
698ce3bd73SGreg Roach    /**
708ce3bd73SGreg Roach     * MediaController constructor.
718ce3bd73SGreg Roach     *
728ce3bd73SGreg Roach     * @param DatatablesService   $datatables_service
734991f205SGreg Roach     * @param LinkedRecordService $linked_record_service
748ce3bd73SGreg Roach     * @param MediaFileService    $media_file_service
758ce3bd73SGreg Roach     * @param TreeService         $tree_service
768ce3bd73SGreg Roach     */
778ce3bd73SGreg Roach    public function __construct(
788ce3bd73SGreg Roach        DatatablesService $datatables_service,
794991f205SGreg Roach        LinkedRecordService $linked_record_service,
808ce3bd73SGreg Roach        MediaFileService $media_file_service,
818ce3bd73SGreg Roach        TreeService $tree_service
828ce3bd73SGreg Roach    ) {
838ce3bd73SGreg Roach        $this->datatables_service    = $datatables_service;
844991f205SGreg Roach        $this->linked_record_service = $linked_record_service;
858ce3bd73SGreg Roach        $this->media_file_service    = $media_file_service;
868ce3bd73SGreg Roach        $this->tree_service          = $tree_service;
878ce3bd73SGreg Roach    }
888ce3bd73SGreg Roach
898ce3bd73SGreg Roach    /**
908ce3bd73SGreg Roach     * @param ServerRequestInterface $request
918ce3bd73SGreg Roach     *
928ce3bd73SGreg Roach     * @return ResponseInterface
938ce3bd73SGreg Roach     */
948ce3bd73SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
958ce3bd73SGreg Roach    {
968ce3bd73SGreg Roach        $data_filesystem = Registry::filesystem()->data();
978ce3bd73SGreg Roach
98748dbe15SGreg Roach        $files = Validator::queryParams($request)->isInArray(['local', 'external', 'unused'])->string('files');
998ce3bd73SGreg Roach
1008ce3bd73SGreg Roach        // Files within this folder
101748dbe15SGreg Roach        $media_folder = Validator::queryParams($request)->string('media_folder');
1028ce3bd73SGreg Roach
1038ce3bd73SGreg Roach        // Show sub-folders within $media_folder
104748dbe15SGreg Roach        $subfolders = Validator::queryParams($request)->isInArray(['include', 'exclude'])->string('subfolders');
1058ce3bd73SGreg Roach
1068ce3bd73SGreg Roach        $search_columns = ['multimedia_file_refn', 'descriptive_title'];
1078ce3bd73SGreg Roach
1088ce3bd73SGreg Roach        $sort_columns = [
1098ce3bd73SGreg Roach            0 => 'multimedia_file_refn',
11096c9e653SGreg Roach            2 => new Expression('descriptive_title || multimedia_file_refn'),
1118ce3bd73SGreg Roach        ];
1128ce3bd73SGreg Roach
1138ce3bd73SGreg Roach        // Convert a row from the database into a row for datatables
114f70bcff5SGreg Roach        $callback = function (object $row): array {
1158ce3bd73SGreg Roach            $tree  = $this->tree_service->find((int) $row->m_file);
1168ce3bd73SGreg Roach            $media = Registry::mediaFactory()->make($row->m_id, $tree, $row->m_gedcom);
1178ce3bd73SGreg Roach            assert($media instanceof Media);
1188ce3bd73SGreg Roach
119f41d9852SGreg Roach            $is_http  = str_starts_with($row->multimedia_file_refn, 'http://');
120f41d9852SGreg Roach            $is_https = str_starts_with($row->multimedia_file_refn, 'https://');
121f41d9852SGreg Roach
122f41d9852SGreg Roach            if ($is_http || $is_https) {
1232fad1159SGreg Roach                return [
1242fad1159SGreg Roach                    '<a href="' . e($row->multimedia_file_refn) . '">' . e($row->multimedia_file_refn) . '</a>',
1252fad1159SGreg Roach                    view('icons/mime', ['type' => Mime::DEFAULT_TYPE]),
1262fad1159SGreg Roach                    $this->mediaObjectInfo($media),
1272fad1159SGreg Roach                ];
1282fad1159SGreg Roach            }
1292fad1159SGreg Roach
130f41d9852SGreg Roach            try {
1318ce3bd73SGreg Roach                $path = $row->media_folder . $row->multimedia_file_refn;
1328ce3bd73SGreg Roach
1338ce3bd73SGreg Roach                try {
134f7cf8a15SGreg Roach                    $mime_type = Registry::filesystem()->data()->mimeType($path);
13528d026adSGreg Roach                } catch (UnableToRetrieveMetadata) {
136f7cf8a15SGreg Roach                    $mime_type = Mime::DEFAULT_TYPE;
137f7cf8a15SGreg Roach                }
1388ce3bd73SGreg Roach
1398ce3bd73SGreg Roach                if (str_starts_with($mime_type, 'image/')) {
1408ce3bd73SGreg Roach                    $url = route(AdminMediaFileThumbnail::class, ['path' => $path]);
1418ce3bd73SGreg Roach                    $img = '<img src="' . e($url) . '">';
1428ce3bd73SGreg Roach                } else {
1438ce3bd73SGreg Roach                    $img = view('icons/mime', ['type' => $mime_type]);
1448ce3bd73SGreg Roach                }
1458ce3bd73SGreg Roach
1468ce3bd73SGreg Roach                $url = route(AdminMediaFileDownload::class, ['path' => $path]);
1478ce3bd73SGreg Roach                $img = '<a href="' . e($url) . '" type="' . $mime_type . '" class="gallery">' . $img . '</a>';
14828d026adSGreg Roach            } catch (UnableToReadFile) {
1498ce3bd73SGreg Roach                $url = route(AdminMediaFileThumbnail::class, ['path' => $path]);
1508ce3bd73SGreg Roach                $img = '<img src="' . e($url) . '">';
1518ce3bd73SGreg Roach            }
1528ce3bd73SGreg Roach
1538ce3bd73SGreg Roach            return [
1542fad1159SGreg Roach                e($row->multimedia_file_refn),
1558ce3bd73SGreg Roach                $img,
1568ce3bd73SGreg Roach                $this->mediaObjectInfo($media),
1578ce3bd73SGreg Roach            ];
1588ce3bd73SGreg Roach        };
1598ce3bd73SGreg Roach
1608ce3bd73SGreg Roach        switch ($files) {
1618ce3bd73SGreg Roach            case 'local':
1628ce3bd73SGreg Roach                $query = DB::table('media_file')
1638ce3bd73SGreg Roach                    ->join('media', static function (JoinClause $join): void {
1648ce3bd73SGreg Roach                        $join
1658ce3bd73SGreg Roach                            ->on('media.m_file', '=', 'media_file.m_file')
1668ce3bd73SGreg Roach                            ->on('media.m_id', '=', 'media_file.m_id');
1678ce3bd73SGreg Roach                    })
1689ba2a180SGreg Roach                    ->leftJoin('gedcom_setting', static function (JoinClause $join): void {
1699ba2a180SGreg Roach                        $join
1709ba2a180SGreg Roach                            ->on('gedcom_setting.gedcom_id', '=', 'media.m_file')
1719ba2a180SGreg Roach                            ->where('setting_name', '=', 'MEDIA_DIRECTORY');
1729ba2a180SGreg Roach                    })
1738ce3bd73SGreg Roach                    ->where('multimedia_file_refn', 'NOT LIKE', 'http://%')
1748ce3bd73SGreg Roach                    ->where('multimedia_file_refn', 'NOT LIKE', 'https://%')
1758ce3bd73SGreg Roach                    ->select([
1768ce3bd73SGreg Roach                        'media.*',
1778ce3bd73SGreg Roach                        'multimedia_file_refn',
1788ce3bd73SGreg Roach                        'descriptive_title',
1799ba2a180SGreg Roach                        new Expression("COALESCE(setting_value, 'media/') AS media_folder"),
1808ce3bd73SGreg Roach                    ]);
1818ce3bd73SGreg Roach
1828ce3bd73SGreg Roach                $query->where(new Expression('setting_value || multimedia_file_refn'), 'LIKE', $media_folder . '%');
1838ce3bd73SGreg Roach
1848ce3bd73SGreg Roach                if ($subfolders === 'exclude') {
1858ce3bd73SGreg Roach                    $query->where(new Expression('setting_value || multimedia_file_refn'), 'NOT LIKE', $media_folder . '%/%');
1868ce3bd73SGreg Roach                }
1878ce3bd73SGreg Roach
1888ce3bd73SGreg Roach                return $this->datatables_service->handleQuery($request, $query, $search_columns, $sort_columns, $callback);
1898ce3bd73SGreg Roach
1908ce3bd73SGreg Roach            case 'external':
1918ce3bd73SGreg Roach                $query = DB::table('media_file')
1928ce3bd73SGreg Roach                    ->join('media', static function (JoinClause $join): void {
1938ce3bd73SGreg Roach                        $join
1948ce3bd73SGreg Roach                            ->on('media.m_file', '=', 'media_file.m_file')
1958ce3bd73SGreg Roach                            ->on('media.m_id', '=', 'media_file.m_id');
1968ce3bd73SGreg Roach                    })
1978ce3bd73SGreg Roach                    ->where(static function (Builder $query): void {
1988ce3bd73SGreg Roach                        $query
1998ce3bd73SGreg Roach                            ->where('multimedia_file_refn', 'LIKE', 'http://%')
2008ce3bd73SGreg Roach                            ->orWhere('multimedia_file_refn', 'LIKE', 'https://%');
2018ce3bd73SGreg Roach                    })
2028ce3bd73SGreg Roach                    ->select([
2038ce3bd73SGreg Roach                        'media.*',
2048ce3bd73SGreg Roach                        'multimedia_file_refn',
2058ce3bd73SGreg Roach                        'descriptive_title',
20696c9e653SGreg Roach                        new Expression("'' AS media_folder"),
2078ce3bd73SGreg Roach                    ]);
2088ce3bd73SGreg Roach
2098ce3bd73SGreg Roach                return $this->datatables_service->handleQuery($request, $query, $search_columns, $sort_columns, $callback);
2108ce3bd73SGreg Roach
2118ce3bd73SGreg Roach            case 'unused':
2128ce3bd73SGreg Roach                // Which trees use which media folder?
2138ce3bd73SGreg Roach                $media_trees = DB::table('gedcom')
2148ce3bd73SGreg Roach                    ->join('gedcom_setting', 'gedcom_setting.gedcom_id', '=', 'gedcom.gedcom_id')
2158ce3bd73SGreg Roach                    ->where('setting_name', '=', 'MEDIA_DIRECTORY')
2168ce3bd73SGreg Roach                    ->where('gedcom.gedcom_id', '>', 0)
2178ce3bd73SGreg Roach                    ->pluck('setting_value', 'gedcom_name');
2188ce3bd73SGreg Roach
2198ce3bd73SGreg Roach                $disk_files = $this->media_file_service->allFilesOnDisk($data_filesystem, $media_folder, $subfolders === 'include');
2208ce3bd73SGreg Roach                $db_files   = $this->media_file_service->allFilesInDatabase($media_folder, $subfolders === 'include');
2218ce3bd73SGreg Roach
2228ce3bd73SGreg Roach                // All unused files
2238ce3bd73SGreg Roach                $unused_files = $disk_files->diff($db_files)
2248ce3bd73SGreg Roach                    ->map(static function (string $file): array {
2258ce3bd73SGreg Roach                        return (array) $file;
2268ce3bd73SGreg Roach                    });
2278ce3bd73SGreg Roach
2288ce3bd73SGreg Roach                $search_columns = [0];
2298ce3bd73SGreg Roach                $sort_columns   = [0 => 0];
2308ce3bd73SGreg Roach
2318ce3bd73SGreg Roach                $callback = function (array $row) use ($data_filesystem, $media_trees): array {
232f7cf8a15SGreg Roach                    try {
233f0448b68SGreg Roach                        $mime_type = $data_filesystem->mimeType($row[0]) ?: Mime::DEFAULT_TYPE;
23428d026adSGreg Roach                    } catch (FilesystemException | UnableToRetrieveMetadata) {
235f7cf8a15SGreg Roach                        $mime_type = Mime::DEFAULT_TYPE;
236f7cf8a15SGreg Roach                    }
237f7cf8a15SGreg Roach
2388ce3bd73SGreg Roach
2398ce3bd73SGreg Roach                    if (str_starts_with($mime_type, 'image/')) {
2408ce3bd73SGreg Roach                        $url = route(AdminMediaFileThumbnail::class, ['path' => $row[0]]);
2418ce3bd73SGreg Roach                        $img = '<img src="' . e($url) . '">';
2428ce3bd73SGreg Roach                    } else {
2438ce3bd73SGreg Roach                        $img = view('icons/mime', ['type' => $mime_type]);
2448ce3bd73SGreg Roach                    }
2458ce3bd73SGreg Roach
2468ce3bd73SGreg Roach                    $url = route(AdminMediaFileDownload::class, ['path' => $row[0]]);
2478ce3bd73SGreg Roach                    $img = '<a href="' . e($url) . '">' . $img . '</a>';
2488ce3bd73SGreg Roach
2498ce3bd73SGreg Roach                    // Form to create new media object in each tree
2508ce3bd73SGreg Roach                    $create_form = '';
2518ce3bd73SGreg Roach                    foreach ($media_trees as $media_tree => $media_directory) {
2528ce3bd73SGreg Roach                        if (str_starts_with($row[0], $media_directory)) {
2538ce3bd73SGreg Roach                            $tmp = substr($row[0], strlen($media_directory));
2548ce3bd73SGreg Roach                            $create_form .=
255315eb316SGreg Roach                                '<p><a href="#" data-bs-toggle="modal" data-bs-backdrop="static" data-bs-target="#modal-create-media-from-file" data-file="' . e($tmp) . '" data-url="' . e(route(CreateMediaObjectFromFile::class, ['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>';
2568ce3bd73SGreg Roach                        }
2578ce3bd73SGreg Roach                    }
2588ce3bd73SGreg Roach
259d4786c66SGreg Roach                    $delete_link = '<p><a data-wt-confirm="' . I18N::translate('Are you sure you want to delete “%s”?', e($row[0])) . '" data-wt-post-url="' . e(route(DeletePath::class, [
2608ce3bd73SGreg Roach                            'path' => $row[0],
2618ce3bd73SGreg Roach                        ])) . '" href="#">' . I18N::translate('Delete') . '</a></p>';
2628ce3bd73SGreg Roach
2638ce3bd73SGreg Roach                    return [
2648ce3bd73SGreg Roach                        $this->mediaFileInfo($data_filesystem, $row[0]) . $delete_link,
2658ce3bd73SGreg Roach                        $img,
2668ce3bd73SGreg Roach                        $create_form,
2678ce3bd73SGreg Roach                    ];
2688ce3bd73SGreg Roach                };
2698ce3bd73SGreg Roach
2708ce3bd73SGreg Roach                return $this->datatables_service->handleCollection($request, $unused_files, $search_columns, $sort_columns, $callback);
2718ce3bd73SGreg Roach
2728ce3bd73SGreg Roach            default:
2738ce3bd73SGreg Roach                throw new HttpNotFoundException();
2748ce3bd73SGreg Roach        }
2758ce3bd73SGreg Roach    }
2768ce3bd73SGreg Roach
2778ce3bd73SGreg Roach    /**
2788ce3bd73SGreg Roach     * Generate some useful information and links about a media object.
2798ce3bd73SGreg Roach     *
2808ce3bd73SGreg Roach     * @param Media $media
2818ce3bd73SGreg Roach     *
2828ce3bd73SGreg Roach     * @return string HTML
2838ce3bd73SGreg Roach     */
2848ce3bd73SGreg Roach    private function mediaObjectInfo(Media $media): string
2858ce3bd73SGreg Roach    {
286d72643a1SDavid Drury        $element = Registry::elementFactory()->make('NOTE:CONC');
287c88ea636SGreg Roach        $html    = '<a href="' . e($media->url()) . '" title="' . e($media->tree()->title()) . '">' . $media->fullName() . '</a>';
288c88ea636SGreg Roach
289c88ea636SGreg Roach        if ($this->tree_service->all()->count() > 1) {
290c88ea636SGreg Roach            $html .= ' — ' . e($media->tree()->title());
291c88ea636SGreg Roach        }
292c88ea636SGreg Roach
293c88ea636SGreg Roach        $html .= $element->value($media->getNote(), $media->tree());
2948ce3bd73SGreg Roach
2958ce3bd73SGreg Roach        $linked = [];
2964991f205SGreg Roach
2974991f205SGreg Roach        foreach ($this->linked_record_service->linkedIndividuals($media) as $link) {
298c88ea636SGreg Roach            $linked[] = view('icons/individual') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
2998ce3bd73SGreg Roach        }
3004991f205SGreg Roach
3014991f205SGreg Roach        foreach ($this->linked_record_service->linkedFamilies($media) as $link) {
302c88ea636SGreg Roach            $linked[] = view('icons/family') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
3038ce3bd73SGreg Roach        }
3044991f205SGreg Roach
3054991f205SGreg Roach        foreach ($this->linked_record_service->linkedSources($media) as $link) {
306c88ea636SGreg Roach            $linked[] = view('icons/source') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
3078ce3bd73SGreg Roach        }
3084991f205SGreg Roach
3094991f205SGreg Roach        foreach ($this->linked_record_service->linkedNotes($media) as $link) {
310c88ea636SGreg Roach            $linked[] = view('icons/note') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
3118ce3bd73SGreg Roach        }
3124991f205SGreg Roach
3134991f205SGreg Roach        foreach ($this->linked_record_service->linkedRepositories($media) as $link) {
314c88ea636SGreg Roach            $linked[] = view('icons/media') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
3158ce3bd73SGreg Roach        }
3164991f205SGreg Roach
3174991f205SGreg Roach        foreach ($this->linked_record_service->linkedMedia($media) as $link) {
318c88ea636SGreg Roach            $linked[] = view('icons/location') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>';
3198ce3bd73SGreg Roach        }
320c88ea636SGreg Roach
3218ce3bd73SGreg Roach        if ($linked !== []) {
322c88ea636SGreg Roach            $html .= '<ul class="list-unstyled">';
3238ce3bd73SGreg Roach            foreach ($linked as $link) {
3248ce3bd73SGreg Roach                $html .= '<li>' . $link . '</li>';
3258ce3bd73SGreg Roach            }
3268ce3bd73SGreg Roach            $html .= '</ul>';
3278ce3bd73SGreg Roach        } else {
3288ce3bd73SGreg Roach            $html .= '<div class="alert alert-danger">' . I18N::translate('There are no links to this media object.') . '</div>';
3298ce3bd73SGreg Roach        }
3308ce3bd73SGreg Roach
3318ce3bd73SGreg Roach        return $html;
3328ce3bd73SGreg Roach    }
3338ce3bd73SGreg Roach
3348ce3bd73SGreg Roach    /**
3358ce3bd73SGreg Roach     * Generate some useful information and links about a media file.
3368ce3bd73SGreg Roach     *
337f7cf8a15SGreg Roach     * @param FilesystemOperator $data_filesystem
3388ce3bd73SGreg Roach     * @param string             $file
3398ce3bd73SGreg Roach     *
3408ce3bd73SGreg Roach     * @return string
3418ce3bd73SGreg Roach     */
342f7cf8a15SGreg Roach    private function mediaFileInfo(FilesystemOperator $data_filesystem, string $file): string
3438ce3bd73SGreg Roach    {
3448ce3bd73SGreg Roach        $html = '<dl>';
3458ce3bd73SGreg Roach        $html .= '<dt>' . I18N::translate('Filename') . '</dt>';
3468ce3bd73SGreg Roach        $html .= '<dd>' . e($file) . '</dd>';
3478ce3bd73SGreg Roach
348f0448b68SGreg Roach        try {
349f0448b68SGreg Roach            $file_exists = $data_filesystem->fileExists($file);
35028d026adSGreg Roach        } catch (FilesystemException | UnableToCheckFileExistence) {
351f0448b68SGreg Roach            $file_exists = false;
352f0448b68SGreg Roach        }
353f0448b68SGreg Roach
354f0448b68SGreg Roach        if ($file_exists) {
355f0448b68SGreg Roach            try {
356f7cf8a15SGreg Roach                $size = $data_filesystem->fileSize($file);
35728d026adSGreg Roach            } catch (FilesystemException | UnableToRetrieveMetadata) {
358f0448b68SGreg Roach                $size = 0;
359f0448b68SGreg Roach            }
3608ce3bd73SGreg Roach            $size = intdiv($size + 1023, 1024); // Round up to next KB
3618ce3bd73SGreg Roach            /* I18N: size of file in KB */
3628ce3bd73SGreg Roach            $size = I18N::translate('%s KB', I18N::number($size));
3638ce3bd73SGreg Roach            $html .= '<dt>' . I18N::translate('File size') . '</dt>';
3648ce3bd73SGreg Roach            $html .= '<dd>' . $size . '</dd>';
3658ce3bd73SGreg Roach
3668ce3bd73SGreg Roach            try {
3678ce3bd73SGreg Roach                // This will work for local filesystems.  For remote filesystems, we will
3688ce3bd73SGreg Roach                // need to copy the file locally to work out the image size.
369f7cf8a15SGreg Roach                $imgsize = getimagesizefromstring($data_filesystem->read($file));
3708ce3bd73SGreg Roach                $html .= '<dt>' . I18N::translate('Image dimensions') . '</dt>';
3718ce3bd73SGreg Roach                /* I18N: image dimensions, width × height */
3728ce3bd73SGreg Roach                $html .= '<dd>' . I18N::translate('%1$s × %2$s pixels', I18N::number($imgsize['0']), I18N::number($imgsize['1'])) . '</dd>';
37328d026adSGreg Roach            } catch (FilesystemException | UnableToReadFile | Throwable) {
3748ce3bd73SGreg Roach                // Not an image, or not a valid image?
3758ce3bd73SGreg Roach            }
3768ce3bd73SGreg Roach        }
3778ce3bd73SGreg Roach
3788ce3bd73SGreg Roach        $html .= '</dl>';
3798ce3bd73SGreg Roach
3808ce3bd73SGreg Roach        return $html;
3818ce3bd73SGreg Roach    }
3828ce3bd73SGreg Roach}
383