diff options
Diffstat (limited to 'app/Http/Controllers/AdminMediaController.php')
| -rw-r--r-- | app/Http/Controllers/AdminMediaController.php | 1202 |
1 files changed, 612 insertions, 590 deletions
diff --git a/app/Http/Controllers/AdminMediaController.php b/app/Http/Controllers/AdminMediaController.php index c74b4ad73c..15974cbcdd 100644 --- a/app/Http/Controllers/AdminMediaController.php +++ b/app/Http/Controllers/AdminMediaController.php @@ -39,671 +39,693 @@ use Throwable; /** * Controller for media administration. */ -class AdminMediaController extends AbstractBaseController { - // How many files to upload on one form. - const MAX_UPLOAD_FILES = 10; +class AdminMediaController extends AbstractBaseController +{ + // How many files to upload on one form. + const MAX_UPLOAD_FILES = 10; - protected $layout = 'layouts/administration'; + protected $layout = 'layouts/administration'; - /** - * @param Request $request - * - * @return Response - */ - public function index(Request $request): Response { - $files = $request->get('files', 'local'); // local|unused|external - $media_folder = $request->get('media_folder', ''); - $media_path = $request->get('media_path', ''); - $subfolders = $request->get('subfolders', 'include'); // include/exclude + /** + * @param Request $request + * + * @return Response + */ + public function index(Request $request): Response + { + $files = $request->get('files', 'local'); // local|unused|external + $media_folder = $request->get('media_folder', ''); + $media_path = $request->get('media_path', ''); + $subfolders = $request->get('subfolders', 'include'); // include/exclude - $media_folders = $this->allMediaFolders(); - $media_paths = $this->mediaPaths($media_folder); + $media_folders = $this->allMediaFolders(); + $media_paths = $this->mediaPaths($media_folder); - // Preserve the pagination/filtering/sorting between requests, so that the - // browser’s back button works. Pagination is dependent on the currently - // selected folder. - $table_id = md5($files . $media_folder . $media_path . $subfolders); + // Preserve the pagination/filtering/sorting between requests, so that the + // browser’s back button works. Pagination is dependent on the currently + // selected folder. + $table_id = md5($files . $media_folder . $media_path . $subfolders); - $title = I18N::translate('Manage media'); + $title = I18N::translate('Manage media'); - return $this->viewResponse('admin/media', [ - 'files' => $files, - 'media_folder' => $media_folder, - 'media_folders' => $media_folders, - 'media_path' => $media_path, - 'media_paths' => $media_paths, - 'subfolders' => $subfolders, - 'table_id' => $table_id, - 'title' => $title, - ]); - } + return $this->viewResponse('admin/media', [ + 'files' => $files, + 'media_folder' => $media_folder, + 'media_folders' => $media_folders, + 'media_path' => $media_path, + 'media_paths' => $media_paths, + 'subfolders' => $subfolders, + 'table_id' => $table_id, + 'title' => $title, + ]); + } - /** - * @param Request $request - * - * @return Response - */ - public function delete(Request $request): Response { - $delete_file = $request->get('file', ''); - $media_folder = $request->get('folder', ''); + /** + * @param Request $request + * + * @return Response + */ + public function delete(Request $request): Response + { + $delete_file = $request->get('file', ''); + $media_folder = $request->get('folder', ''); - // Only delete valid (i.e. unused) media files - $disk_files = $this->allDiskFiles($media_folder, '', 'include', ''); + // Only delete valid (i.e. unused) media files + $disk_files = $this->allDiskFiles($media_folder, '', 'include', ''); - // Check file exists? Maybe it was already deleted or renamed. - if (in_array($delete_file, $disk_files)) { - $tmp = WT_DATA_DIR . $media_folder . $delete_file; - try { - unlink($tmp); - FlashMessages::addMessage(I18N::translate('The file %s has been deleted.', Html::filename($tmp)), 'info'); - } catch (Throwable $ex) { - DebugBar::addThrowable($ex); + // Check file exists? Maybe it was already deleted or renamed. + if (in_array($delete_file, $disk_files)) { + $tmp = WT_DATA_DIR . $media_folder . $delete_file; + try { + unlink($tmp); + FlashMessages::addMessage(I18N::translate('The file %s has been deleted.', Html::filename($tmp)), 'info'); + } catch (Throwable $ex) { + DebugBar::addThrowable($ex); - FlashMessages::addMessage(I18N::translate('The file %s could not be deleted.', Html::filename($tmp)) . '<hr><samp dir="ltr">' . $ex->getMessage() . '</samp>', 'danger'); - } - } + FlashMessages::addMessage(I18N::translate('The file %s could not be deleted.', Html::filename($tmp)) . '<hr><samp dir="ltr">' . $ex->getMessage() . '</samp>', 'danger'); + } + } - return new Response; - } + return new Response; + } - /** - * @param Request $request - * - * @return JsonResponse - */ - public function data(Request $request): JsonResponse { - $files = $request->get('files'); // local|external|unused - $search = $request->get('search'); - $search = $search['value']; - $start = (int) $request->get('start'); - $length = (int) $request->get('length'); + /** + * @param Request $request + * + * @return JsonResponse + */ + public function data(Request $request): JsonResponse + { + $files = $request->get('files'); // local|external|unused + $search = $request->get('search'); + $search = $search['value']; + $start = (int)$request->get('start'); + $length = (int)$request->get('length'); - // family tree setting MEDIA_DIRECTORY - $media_folders = $this->allMediaFolders(); - $media_folder = $request->get('media_folder', ''); - // User folders may contain special characters. Restrict to actual folders. - if (!array_key_exists($media_folder, $media_folders)) { - $media_folder = reset($media_folders); - } + // family tree setting MEDIA_DIRECTORY + $media_folders = $this->allMediaFolders(); + $media_folder = $request->get('media_folder', ''); + // User folders may contain special characters. Restrict to actual folders. + if (!array_key_exists($media_folder, $media_folders)) { + $media_folder = reset($media_folders); + } - // prefix to filename - $media_paths = $this->mediaPaths($media_folder); - $media_path = $request->get('media_path', ''); - // User paths may contain special characters. Restrict to actual paths. - if (!array_key_exists($media_path, $media_paths)) { - $media_path = reset($media_paths); - } + // prefix to filename + $media_paths = $this->mediaPaths($media_folder); + $media_path = $request->get('media_path', ''); + // User paths may contain special characters. Restrict to actual paths. + if (!array_key_exists($media_path, $media_paths)) { + $media_path = reset($media_paths); + } - // subfolders within $media_path - $subfolders = $request->get('subfolders'); // include|exclude + // subfolders within $media_path + $subfolders = $request->get('subfolders'); // include|exclude - switch ($files) { - case 'local': - // Filtered rows - $SELECT1 = - "SELECT SQL_CALC_FOUND_ROWS TRIM(LEADING :media_path_1 FROM multimedia_file_refn) AS media_path, m_id AS xref, descriptive_title, m_file AS gedcom_id, m_gedcom AS gedcom" . - " FROM `##media`" . - " JOIN `##media_file` USING (m_file, m_id)" . - " JOIN `##gedcom_setting` ON (m_file = gedcom_id AND setting_name = 'MEDIA_DIRECTORY')" . - " JOIN `##gedcom` USING (gedcom_id)" . - " WHERE setting_value = :media_folder" . - " AND multimedia_file_refn LIKE CONCAT(:media_path_2, '%')" . - " AND (SUBSTRING_INDEX(multimedia_file_refn, '/', -1) LIKE CONCAT('%', :search_1, '%')" . - " OR descriptive_title LIKE CONCAT('%', :search_2, '%'))" . - " AND multimedia_file_refn NOT LIKE 'http://%'" . - " AND multimedia_file_refn NOT LIKE 'https://%'"; - $ARGS1 = [ - 'media_path_1' => $media_path, - 'media_folder' => $media_folder, - 'media_path_2' => Database::escapeLike($media_path), - 'search_1' => Database::escapeLike($search), - 'search_2' => Database::escapeLike($search), - ]; - // Unfiltered rows - $SELECT2 = - "SELECT COUNT(*)" . - " FROM `##media`" . - " JOIN `##media_file` USING (m_file, m_id)" . - " JOIN `##gedcom_setting` ON (m_file = gedcom_id AND setting_name = 'MEDIA_DIRECTORY')" . - " WHERE setting_value = :media_folder" . - " AND multimedia_file_refn LIKE CONCAT(:media_path_3, '%')" . - " AND multimedia_file_refn NOT LIKE 'http://%'" . - " AND multimedia_file_refn NOT LIKE 'https://%'"; - $ARGS2 = [ - 'media_folder' => $media_folder, - 'media_path_3' => $media_path, - ]; + switch ($files) { + case 'local': + // Filtered rows + $SELECT1 = + "SELECT SQL_CALC_FOUND_ROWS TRIM(LEADING :media_path_1 FROM multimedia_file_refn) AS media_path, m_id AS xref, descriptive_title, m_file AS gedcom_id, m_gedcom AS gedcom" . + " FROM `##media`" . + " JOIN `##media_file` USING (m_file, m_id)" . + " JOIN `##gedcom_setting` ON (m_file = gedcom_id AND setting_name = 'MEDIA_DIRECTORY')" . + " JOIN `##gedcom` USING (gedcom_id)" . + " WHERE setting_value = :media_folder" . + " AND multimedia_file_refn LIKE CONCAT(:media_path_2, '%')" . + " AND (SUBSTRING_INDEX(multimedia_file_refn, '/', -1) LIKE CONCAT('%', :search_1, '%')" . + " OR descriptive_title LIKE CONCAT('%', :search_2, '%'))" . + " AND multimedia_file_refn NOT LIKE 'http://%'" . + " AND multimedia_file_refn NOT LIKE 'https://%'"; + $ARGS1 = [ + 'media_path_1' => $media_path, + 'media_folder' => $media_folder, + 'media_path_2' => Database::escapeLike($media_path), + 'search_1' => Database::escapeLike($search), + 'search_2' => Database::escapeLike($search), + ]; + // Unfiltered rows + $SELECT2 = + "SELECT COUNT(*)" . + " FROM `##media`" . + " JOIN `##media_file` USING (m_file, m_id)" . + " JOIN `##gedcom_setting` ON (m_file = gedcom_id AND setting_name = 'MEDIA_DIRECTORY')" . + " WHERE setting_value = :media_folder" . + " AND multimedia_file_refn LIKE CONCAT(:media_path_3, '%')" . + " AND multimedia_file_refn NOT LIKE 'http://%'" . + " AND multimedia_file_refn NOT LIKE 'https://%'"; + $ARGS2 = [ + 'media_folder' => $media_folder, + 'media_path_3' => $media_path, + ]; - if ($subfolders == 'exclude') { - $SELECT1 .= " AND multimedia_file_refn NOT LIKE CONCAT(:media_path_4, '%/%')"; - $ARGS1['media_path_4'] = Database::escapeLike($media_path); - $SELECT2 .= " AND multimedia_file_refn NOT LIKE CONCAT(:media_path_4, '%/%')"; - $ARGS2['media_path_4'] = Database::escapeLike($media_path); - } + if ($subfolders == 'exclude') { + $SELECT1 .= " AND multimedia_file_refn NOT LIKE CONCAT(:media_path_4, '%/%')"; + $ARGS1['media_path_4'] = Database::escapeLike($media_path); + $SELECT2 .= " AND multimedia_file_refn NOT LIKE CONCAT(:media_path_4, '%/%')"; + $ARGS2['media_path_4'] = Database::escapeLike($media_path); + } - $order = $request->get('order', []); - $SELECT1 .= " ORDER BY "; - if ($order) { - foreach ($order as $key => $value) { - if ($key > 0) { - $SELECT1 .= ','; - } - // Columns in datatables are numbered from zero. - // Columns in MySQL are numbered starting with one. - switch ($value['dir']) { - case 'asc': - $SELECT1 .= ":col_" . $key . " ASC"; - break; - case 'desc': - $SELECT1 .= ":col_" . $key . " DESC"; - break; - } - $ARGS1['col_' . $key] = 1 + $value['column']; - } - } else { - $SELECT1 = " 1 ASC"; - } + $order = $request->get('order', []); + $SELECT1 .= " ORDER BY "; + if ($order) { + foreach ($order as $key => $value) { + if ($key > 0) { + $SELECT1 .= ','; + } + // Columns in datatables are numbered from zero. + // Columns in MySQL are numbered starting with one. + switch ($value['dir']) { + case 'asc': + $SELECT1 .= ":col_" . $key . " ASC"; + break; + case 'desc': + $SELECT1 .= ":col_" . $key . " DESC"; + break; + } + $ARGS1['col_' . $key] = 1 + $value['column']; + } + } else { + $SELECT1 = " 1 ASC"; + } - if ($length > 0) { - $SELECT1 .= " LIMIT :length OFFSET :start"; - $ARGS1['length'] = $length; - $ARGS1['start'] = $start; - } + if ($length > 0) { + $SELECT1 .= " LIMIT :length OFFSET :start"; + $ARGS1['length'] = $length; + $ARGS1['start'] = $start; + } - $rows = Database::prepare($SELECT1)->execute($ARGS1)->fetchAll(); - // Total filtered/unfiltered rows - $recordsFiltered = Database::prepare("SELECT FOUND_ROWS()")->fetchOne(); - $recordsTotal = Database::prepare($SELECT2)->execute($ARGS2)->fetchOne(); + $rows = Database::prepare($SELECT1)->execute($ARGS1)->fetchAll(); + // Total filtered/unfiltered rows + $recordsFiltered = Database::prepare("SELECT FOUND_ROWS()")->fetchOne(); + $recordsTotal = Database::prepare($SELECT2)->execute($ARGS2)->fetchOne(); - $data = []; - foreach ($rows as $row) { - $media = Media::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom); - $media_files = $media->mediaFiles(); - $media_files = array_map(function (MediaFile $media_file) { - return $media_file->displayImage(150, 150, '', []); - }, $media_files); - $data[] = [ - $this->mediaFileInfo($media_folder, $media_path, $row->media_path), - implode('', $media_files), - $this->mediaObjectInfo($media), - ]; - } - break; + $data = []; + foreach ($rows as $row) { + $media = Media::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom); + $media_files = $media->mediaFiles(); + $media_files = array_map(function (MediaFile $media_file) { + return $media_file->displayImage(150, 150, '', []); + }, $media_files); + $data[] = [ + $this->mediaFileInfo($media_folder, $media_path, $row->media_path), + implode('', $media_files), + $this->mediaObjectInfo($media), + ]; + } + break; - case 'external': - // Filtered rows - $SELECT1 = - "SELECT SQL_CALC_FOUND_ROWS multimedia_file_refn, m_id AS xref, descriptive_title, m_file AS gedcom_id, m_gedcom AS gedcom" . - " FROM `##media`" . - " JOIN `##media_file` USING (m_id, m_file)" . - " WHERE (multimedia_file_refn LIKE 'http://%' OR multimedia_file_refn LIKE 'https://%')" . - " AND (multimedia_file_refn LIKE CONCAT('%', :search_1, '%') OR descriptive_title LIKE CONCAT('%', :search_2, '%'))"; - $ARGS1 = [ - 'search_1' => Database::escapeLike($search), - 'search_2' => Database::escapeLike($search), - ]; - // Unfiltered rows - $SELECT2 = - "SELECT COUNT(*)" . - " FROM `##media`" . - " JOIN `##media_file` USING (m_id, m_file)" . - " WHERE (multimedia_file_refn LIKE 'http://%' OR multimedia_file_refn LIKE 'https://%')"; - $ARGS2 = []; + case 'external': + // Filtered rows + $SELECT1 = + "SELECT SQL_CALC_FOUND_ROWS multimedia_file_refn, m_id AS xref, descriptive_title, m_file AS gedcom_id, m_gedcom AS gedcom" . + " FROM `##media`" . + " JOIN `##media_file` USING (m_id, m_file)" . + " WHERE (multimedia_file_refn LIKE 'http://%' OR multimedia_file_refn LIKE 'https://%')" . + " AND (multimedia_file_refn LIKE CONCAT('%', :search_1, '%') OR descriptive_title LIKE CONCAT('%', :search_2, '%'))"; + $ARGS1 = [ + 'search_1' => Database::escapeLike($search), + 'search_2' => Database::escapeLike($search), + ]; + // Unfiltered rows + $SELECT2 = + "SELECT COUNT(*)" . + " FROM `##media`" . + " JOIN `##media_file` USING (m_id, m_file)" . + " WHERE (multimedia_file_refn LIKE 'http://%' OR multimedia_file_refn LIKE 'https://%')"; + $ARGS2 = []; - $order = $request->get('order', []); - $SELECT1 .= " ORDER BY "; - if ($order) { - foreach ($order as $key => $value) { - if ($key > 0) { - $SELECT1 .= ','; - } - // Columns in datatables are numbered from zero. - // Columns in MySQL are numbered starting with one. - switch ($value['dir']) { - case 'asc': - $SELECT1 .= ":col_" . $key . " ASC"; - break; - case 'desc': - $SELECT1 .= ":col_" . $key . " DESC"; - break; - } - $ARGS1['col_' . $key] = 1 + $value['column']; - } - } else { - $SELECT1 = " 1 ASC"; - } + $order = $request->get('order', []); + $SELECT1 .= " ORDER BY "; + if ($order) { + foreach ($order as $key => $value) { + if ($key > 0) { + $SELECT1 .= ','; + } + // Columns in datatables are numbered from zero. + // Columns in MySQL are numbered starting with one. + switch ($value['dir']) { + case 'asc': + $SELECT1 .= ":col_" . $key . " ASC"; + break; + case 'desc': + $SELECT1 .= ":col_" . $key . " DESC"; + break; + } + $ARGS1['col_' . $key] = 1 + $value['column']; + } + } else { + $SELECT1 = " 1 ASC"; + } - if ($length > 0) { - $SELECT1 .= " LIMIT :length OFFSET :start"; - $ARGS1['length'] = $length; - $ARGS1['start'] = $start; - } + if ($length > 0) { + $SELECT1 .= " LIMIT :length OFFSET :start"; + $ARGS1['length'] = $length; + $ARGS1['start'] = $start; + } - $rows = Database::prepare($SELECT1)->execute($ARGS1)->fetchAll(); + $rows = Database::prepare($SELECT1)->execute($ARGS1)->fetchAll(); - // Total filtered/unfiltered rows - $recordsFiltered = Database::prepare("SELECT FOUND_ROWS()")->fetchOne(); - $recordsTotal = Database::prepare($SELECT2)->execute($ARGS2)->fetchOne(); + // Total filtered/unfiltered rows + $recordsFiltered = Database::prepare("SELECT FOUND_ROWS()")->fetchOne(); + $recordsTotal = Database::prepare($SELECT2)->execute($ARGS2)->fetchOne(); - $data = []; - foreach ($rows as $row) { - $media = Media::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom); - $data[] = [ - GedcomTag::getLabelValue('URL', $row->multimedia_file_refn), - $media->displayImage(150, 150, '', []), - $this->mediaObjectInfo($media), - ]; - } - break; + $data = []; + foreach ($rows as $row) { + $media = Media::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom); + $data[] = [ + GedcomTag::getLabelValue('URL', $row->multimedia_file_refn), + $media->displayImage(150, 150, '', []), + $this->mediaObjectInfo($media), + ]; + } + break; - case 'unused': - // Which trees use this media folder? - $media_trees = Database::prepare( - "SELECT gedcom_name, gedcom_name" . - " FROM `##gedcom`" . - " JOIN `##gedcom_setting` USING (gedcom_id)" . - " WHERE setting_name='MEDIA_DIRECTORY' AND setting_value = :media_folder AND gedcom_id > 0" - )->execute([ - 'media_folder' => $media_folder, - ])->fetchAssoc(); + case 'unused': + // Which trees use this media folder? + $media_trees = Database::prepare( + "SELECT gedcom_name, gedcom_name" . + " FROM `##gedcom`" . + " JOIN `##gedcom_setting` USING (gedcom_id)" . + " WHERE setting_name='MEDIA_DIRECTORY' AND setting_value = :media_folder AND gedcom_id > 0" + )->execute([ + 'media_folder' => $media_folder, + ])->fetchAssoc(); - $disk_files = $this->allDiskFiles($media_folder, $media_path, $subfolders, $search); - $db_files = $this->allMediaFiles($media_folder, $media_path, $search); + $disk_files = $this->allDiskFiles($media_folder, $media_path, $subfolders, $search); + $db_files = $this->allMediaFiles($media_folder, $media_path, $search); - // All unused files - $unused_files = array_diff($disk_files, $db_files); - $recordsTotal = count($unused_files); + // All unused files + $unused_files = array_diff($disk_files, $db_files); + $recordsTotal = count($unused_files); - // Filter unused files - if ($search) { - $unused_files = array_filter($unused_files, function ($x) use ($search) { - return strpos($x, $search) !== false; - }); - } - $recordsFiltered = count($unused_files); + // Filter unused files + if ($search) { + $unused_files = array_filter($unused_files, function ($x) use ($search) { + return strpos($x, $search) !== false; + }); + } + $recordsFiltered = count($unused_files); - // Sort files - only option is column 0 - sort($unused_files); - $order = $request->get('order', []); - if ($order && $order[0]['dir'] === 'desc') { - $unused_files = array_reverse($unused_files); - } + // Sort files - only option is column 0 + sort($unused_files); + $order = $request->get('order', []); + if ($order && $order[0]['dir'] === 'desc') { + $unused_files = array_reverse($unused_files); + } - // Paginate unused files - $unused_files = array_slice($unused_files, $start, $length); + // Paginate unused files + $unused_files = array_slice($unused_files, $start, $length); - $data = []; - foreach ($unused_files as $unused_file) { - $imgsize = getimagesize(WT_DATA_DIR . $media_folder . $media_path . $unused_file); - // We can’t create a URL (not in public_html) or use the media firewall (no such object) - if ($imgsize === false) { - $img = '-'; - } else { - $url = route('unused-media-thumbnail', ['folder' => $media_folder, 'file' => $media_path . $unused_file, 'w' => 100, 'h' => 100]); - $img = '<img src="' . e($url) . '">'; - } + $data = []; + foreach ($unused_files as $unused_file) { + $imgsize = getimagesize(WT_DATA_DIR . $media_folder . $media_path . $unused_file); + // We can’t create a URL (not in public_html) or use the media firewall (no such object) + if ($imgsize === false) { + $img = '-'; + } else { + $url = route('unused-media-thumbnail', [ + 'folder' => $media_folder, + 'file' => $media_path . $unused_file, + 'w' => 100, + 'h' => 100, + ]); + $img = '<img src="' . e($url) . '">'; + } - // Is there a pending record for this file? - $exists_pending = Database::prepare( - "SELECT 1 FROM `##change` WHERE status='pending' AND new_gedcom LIKE CONCAT('%\n1 FILE ', :unused_file, '\n%')" - )->execute([ - 'unused_file' => Database::escapeLike($unused_file), - ])->fetchOne(); + // Is there a pending record for this file? + $exists_pending = Database::prepare( + "SELECT 1 FROM `##change` WHERE status='pending' AND new_gedcom LIKE CONCAT('%\n1 FILE ', :unused_file, '\n%')" + )->execute([ + 'unused_file' => Database::escapeLike($unused_file), + ])->fetchOne(); - // Form to create new media object in each tree - $create_form = ''; - if (!$exists_pending) { - foreach ($media_trees as $media_tree) { - $create_form .= - '<p><a href="#" data-toggle="modal" data-target="#modal-create-media-from-file" data-file="' . e($unused_file) . '" data-tree="' . e($media_tree) . '" onclick="document.getElementById(\'file\').value=this.dataset.file; document.getElementById(\'ged\').value=this.dataset.tree;">' . I18N::translate('Create') . '</a> — ' . e($media_tree) . '<p>'; - } - } + // Form to create new media object in each tree + $create_form = ''; + if (!$exists_pending) { + foreach ($media_trees as $media_tree) { + $create_form .= + '<p><a href="#" data-toggle="modal" data-target="#modal-create-media-from-file" data-file="' . e($unused_file) . '" data-tree="' . e($media_tree) . '" onclick="document.getElementById(\'file\').value=this.dataset.file; document.getElementById(\'ged\').value=this.dataset.tree;">' . I18N::translate('Create') . '</a> — ' . e($media_tree) . '<p>'; + } + } - $delete_link = '<p><a data-confirm="' . I18N::translate('Are you sure you want to delete “%s”?', e($unused_file)) . '" data-url="' . e(route('admin-media-delete', ['file' => $media_path . $unused_file, 'folder' => $media_folder])) . '" onclick="if (confirm(this.dataset.confirm)) jQuery.post(this.dataset.url, function (){location.reload();})" href="#">' . I18N::translate('Delete') . '</a></p>'; + $delete_link = '<p><a data-confirm="' . I18N::translate('Are you sure you want to delete “%s”?', e($unused_file)) . '" data-url="' . e(route('admin-media-delete', [ + 'file' => $media_path . $unused_file, + 'folder' => $media_folder, + ])) . '" onclick="if (confirm(this.dataset.confirm)) jQuery.post(this.dataset.url, function (){location.reload();})" href="#">' . I18N::translate('Delete') . '</a></p>'; - $data[] = [ - $this->mediaFileInfo($media_folder, $media_path, $unused_file) . $delete_link, - $img, - $create_form, - ]; - } - break; + $data[] = [ + $this->mediaFileInfo($media_folder, $media_path, $unused_file) . $delete_link, + $img, + $create_form, + ]; + } + break; - default: - throw new BadRequestHttpException; - } + default: + throw new BadRequestHttpException; + } - // See http://www.datatables.net/usage/server-side - return new JsonResponse([ - 'draw' => $request->get('draw'), - 'recordsTotal' => $recordsTotal, - 'recordsFiltered' => $recordsFiltered, - 'data' => $data, - ]); - } + // See http://www.datatables.net/usage/server-side + return new JsonResponse([ + 'draw' => $request->get('draw'), + 'recordsTotal' => $recordsTotal, + 'recordsFiltered' => $recordsFiltered, + 'data' => $data, + ]); + } - /** - * @param Request $request - * - * @return Response - */ - public function upload(Request $request): Response { - $media_folders = $this->folderListAll(); + /** + * @param Request $request + * + * @return Response + */ + public function upload(Request $request): Response + { + $media_folders = $this->folderListAll(); - $filesize = ini_get('upload_max_filesize'); - if (empty($filesize)) { - $filesize = '2M'; - } + $filesize = ini_get('upload_max_filesize'); + if (empty($filesize)) { + $filesize = '2M'; + } - $title = I18N::translate('Upload media files'); + $title = I18N::translate('Upload media files'); - return $this->viewResponse('admin/media-upload', [ - 'max_upload_files' => self::MAX_UPLOAD_FILES, - 'filesize' => $filesize, - 'media_folders' => $media_folders, - 'title' => $title, - ]); - } + return $this->viewResponse('admin/media-upload', [ + 'max_upload_files' => self::MAX_UPLOAD_FILES, + 'filesize' => $filesize, + 'media_folders' => $media_folders, + 'title' => $title, + ]); + } - /** - * @param Request $request - * - * @return RedirectResponse - */ - public function uploadAction(Request $request): RedirectResponse { - $all_folders = $this->folderListAll(); + /** + * @param Request $request + * + * @return RedirectResponse + */ + public function uploadAction(Request $request): RedirectResponse + { + $all_folders = $this->folderListAll(); - for ($i = 1; $i < self::MAX_UPLOAD_FILES; $i++) { - if (!empty($_FILES['mediafile' . $i]['name'])) { - $folder = $request->get('folder' . $i, ''); - $filename = $request->get('filename' . $i, ''); + for ($i = 1; $i < self::MAX_UPLOAD_FILES; $i++) { + if (!empty($_FILES['mediafile' . $i]['name'])) { + $folder = $request->get('folder' . $i, ''); + $filename = $request->get('filename' . $i, ''); - // If no filename specified, use the original filename. - if ($filename === '') { - $filename = $_FILES['mediafile' . $i]['name']; - } + // If no filename specified, use the original filename. + if ($filename === '') { + $filename = $_FILES['mediafile' . $i]['name']; + } - // Validate the folder - if (!in_array($folder, $all_folders)) { - break; - } + // Validate the folder + if (!in_array($folder, $all_folders)) { + break; + } - // Validate the filename. - $filename = str_replace('\\', '/', $filename); - $filename = trim($filename, '/'); + // Validate the filename. + $filename = str_replace('\\', '/', $filename); + $filename = trim($filename, '/'); - if (strpos('/' . $filename, '/../') !== false) { - FlashMessages::addMessage('Folder names are not allowed to include “../”'); - continue; - } elseif (preg_match('/([:])/', $filename, $match)) { - // Local media files cannot contain certain special characters, especially on MS Windows - FlashMessages::addMessage(I18N::translate('Filenames are not allowed to contain the character “%s”.', $match[1])); - continue; - } elseif (preg_match('/(\.(php|pl|cgi|bash|sh|bat|exe|com|htm|html|shtml))$/i', $filename, $match)) { - // Do not allow obvious script files. - FlashMessages::addMessage(I18N::translate('Filenames are not allowed to have the extension “%s”.', $match[1])); - continue; - } + if (strpos('/' . $filename, '/../') !== false) { + FlashMessages::addMessage('Folder names are not allowed to include “../”'); + continue; + } elseif (preg_match('/([:])/', $filename, $match)) { + // Local media files cannot contain certain special characters, especially on MS Windows + FlashMessages::addMessage(I18N::translate('Filenames are not allowed to contain the character “%s”.', $match[1])); + continue; + } elseif (preg_match('/(\.(php|pl|cgi|bash|sh|bat|exe|com|htm|html|shtml))$/i', $filename, $match)) { + // Do not allow obvious script files. + FlashMessages::addMessage(I18N::translate('Filenames are not allowed to have the extension “%s”.', $match[1])); + continue; + } - // The new filename may have created a new sub-folder. - $full_path = WT_DATA_DIR . $folder . $filename; - $folder = dirname($full_path); + // The new filename may have created a new sub-folder. + $full_path = WT_DATA_DIR . $folder . $filename; + $folder = dirname($full_path); - // Make sure the media folder exists - if (!is_dir($folder)) { - if (File::mkdir($folder)) { - FlashMessages::addMessage(I18N::translate('The folder %s has been created.', Html::filename($folder)), 'info'); - } else { - FlashMessages::addMessage(I18N::translate('The folder %s does not exist, and it could not be created.', Html::filename($folder)), 'danger'); - continue; - } - } + // Make sure the media folder exists + if (!is_dir($folder)) { + if (File::mkdir($folder)) { + FlashMessages::addMessage(I18N::translate('The folder %s has been created.', Html::filename($folder)), 'info'); + } else { + FlashMessages::addMessage(I18N::translate('The folder %s does not exist, and it could not be created.', Html::filename($folder)), 'danger'); + continue; + } + } - if (file_exists($full_path)) { - FlashMessages::addMessage(I18N::translate('The file %s already exists. Use another filename.', $full_path, 'error')); - continue; - } + if (file_exists($full_path)) { + FlashMessages::addMessage(I18N::translate('The file %s already exists. Use another filename.', $full_path, 'error')); + continue; + } - // Now copy the file to the correct location. - if (move_uploaded_file($_FILES['mediafile' . $i]['tmp_name'], $full_path)) { - FlashMessages::addMessage(I18N::translate('The file %s has been uploaded.', Html::filename($full_path)), 'success'); - Log::addMediaLog('Media file ' . $full_path . ' uploaded'); - } else { - FlashMessages::addMessage(I18N::translate('There was an error uploading your file.') . '<br>' . Functions::fileUploadErrorText($_FILES['mediafile' . $i]['error']), 'danger'); - } - } - } + // Now copy the file to the correct location. + if (move_uploaded_file($_FILES['mediafile' . $i]['tmp_name'], $full_path)) { + FlashMessages::addMessage(I18N::translate('The file %s has been uploaded.', Html::filename($full_path)), 'success'); + Log::addMediaLog('Media file ' . $full_path . ' uploaded'); + } else { + FlashMessages::addMessage(I18N::translate('There was an error uploading your file.') . '<br>' . Functions::fileUploadErrorText($_FILES['mediafile' . $i]['error']), 'danger'); + } + } + } - $url = route('admin-media-upload'); + $url = route('admin-media-upload'); - return new RedirectResponse($url); - } + return new RedirectResponse($url); + } - /** - * Generate a list of all folders from all the trees. - * - * @return string[] - */ - private function folderListAll(): array { - $folders = Database::prepare( - "SELECT CONCAT(setting_value, LEFT(multimedia_file_refn, CHAR_LENGTH(multimedia_file_refn) - CHAR_LENGTH(SUBSTRING_INDEX(multimedia_file_refn, '/', -1))))" . - " FROM `##gedcom_setting` AS gs" . - " JOIN `##media_file` AS m ON m.m_file = gs.gedcom_id AND gs.setting_name = 'MEDIA_DIRECTORY'" . - " WHERE multimedia_file_refn NOT LIKE 'http://%'" . - " AND multimedia_file_refn NOT LIKE 'https://%'" . - " AND gs.gedcom_id > 0" . - " GROUP BY 1" . - " UNION" . - " SELECT setting_value FROM `##gedcom_setting` WHERE setting_name = 'MEDIA_DIRECTORY'" . - " ORDER BY 1" - )->execute()->fetchOneColumn(); + /** + * Generate a list of all folders from all the trees. + * + * @return string[] + */ + private function folderListAll(): array + { + $folders = Database::prepare( + "SELECT CONCAT(setting_value, LEFT(multimedia_file_refn, CHAR_LENGTH(multimedia_file_refn) - CHAR_LENGTH(SUBSTRING_INDEX(multimedia_file_refn, '/', -1))))" . + " FROM `##gedcom_setting` AS gs" . + " JOIN `##media_file` AS m ON m.m_file = gs.gedcom_id AND gs.setting_name = 'MEDIA_DIRECTORY'" . + " WHERE multimedia_file_refn NOT LIKE 'http://%'" . + " AND multimedia_file_refn NOT LIKE 'https://%'" . + " AND gs.gedcom_id > 0" . + " GROUP BY 1" . + " UNION" . + " SELECT setting_value FROM `##gedcom_setting` WHERE setting_name = 'MEDIA_DIRECTORY'" . + " ORDER BY 1" + )->execute()->fetchOneColumn(); - return $folders; - } + return $folders; + } - /** - * A unique list of media folders, from all trees. - * - * @return string[] - */ - private function allMediaFolders(): array { - return Database::prepare( - "SELECT setting_value, setting_value" . - " FROM `##gedcom_setting`" . - " WHERE setting_name='MEDIA_DIRECTORY' AND gedcom_id > 0" . - " GROUP BY 1" . - " ORDER BY 1" - )->execute([])->fetchAssoc(); - } + /** + * A unique list of media folders, from all trees. + * + * @return string[] + */ + private function allMediaFolders(): array + { + return Database::prepare( + "SELECT setting_value, setting_value" . + " FROM `##gedcom_setting`" . + " WHERE setting_name='MEDIA_DIRECTORY' AND gedcom_id > 0" . + " GROUP BY 1" . + " ORDER BY 1" + )->execute([])->fetchAssoc(); + } - /** - * Generate a list of media paths (within a media folder) used by all media objects. - * - * @param string $media_folder - * - * @return string[] - */ - private function mediaPaths(string $media_folder): array { - $media_paths = Database::prepare( - "SELECT LEFT(multimedia_file_refn, CHAR_LENGTH(multimedia_file_refn) - CHAR_LENGTH(SUBSTRING_INDEX(multimedia_file_refn, '/', -1))) AS media_path" . - " FROM `##media`" . - " JOIN `##media_file` USING (m_file, m_id)" . - " JOIN `##gedcom_setting` ON (m_file = gedcom_id AND setting_name = 'MEDIA_DIRECTORY')" . - " WHERE setting_value = :media_folder" . - " AND multimedia_file_refn NOT LIKE 'http://%'" . - " AND multimedia_file_refn NOT LIKE 'https://%'" . - " GROUP BY 1" . - " ORDER BY 1" - )->execute([ - 'media_folder' => $media_folder, - ])->fetchOneColumn(); + /** + * Generate a list of media paths (within a media folder) used by all media objects. + * + * @param string $media_folder + * + * @return string[] + */ + private function mediaPaths(string $media_folder): array + { + $media_paths = Database::prepare( + "SELECT LEFT(multimedia_file_refn, CHAR_LENGTH(multimedia_file_refn) - CHAR_LENGTH(SUBSTRING_INDEX(multimedia_file_refn, '/', -1))) AS media_path" . + " FROM `##media`" . + " JOIN `##media_file` USING (m_file, m_id)" . + " JOIN `##gedcom_setting` ON (m_file = gedcom_id AND setting_name = 'MEDIA_DIRECTORY')" . + " WHERE setting_value = :media_folder" . + " AND multimedia_file_refn NOT LIKE 'http://%'" . + " AND multimedia_file_refn NOT LIKE 'https://%'" . + " GROUP BY 1" . + " ORDER BY 1" + )->execute([ + 'media_folder' => $media_folder, + ])->fetchOneColumn(); - if (empty($media_paths) || $media_paths[0] !== '') { - // Always include a (possibly empty) top-level folder - array_unshift($media_paths, ''); - } + if (empty($media_paths) || $media_paths[0] !== '') { + // Always include a (possibly empty) top-level folder + array_unshift($media_paths, ''); + } - return array_combine($media_paths, $media_paths); - } + return array_combine($media_paths, $media_paths); + } - /** - * Search a folder (and optional subfolders) for filenames that match a search pattern. - * - * @param string $dir - * @param bool $recursive - * @param string $filter - * - * @return string[] - */ - private function scanFolders(string $dir, bool $recursive, string $filter): array { - $files = []; + /** + * Search a folder (and optional subfolders) for filenames that match a search pattern. + * + * @param string $dir + * @param bool $recursive + * @param string $filter + * + * @return string[] + */ + private function scanFolders(string $dir, bool $recursive, string $filter): array + { + $files = []; - // $dir comes from the database. The actual folder may not exist. - if (is_dir($dir)) { - foreach (scandir($dir) as $path) { - if (is_dir($dir . $path)) { - // What if there are user-defined subfolders “thumbs” or “watermarks”? - if ($path != '.' && $path != '..' && $path != 'thumbs' && $path != 'watermark' && $recursive) { - foreach ($this->scanFolders($dir . $path . '/', $recursive, $filter) as $subpath) { - $files[] = $path . '/' . $subpath; - } - } - } elseif (!$filter || stripos($path, $filter) !== false) { - $files[] = $path; - } - } - } + // $dir comes from the database. The actual folder may not exist. + if (is_dir($dir)) { + foreach (scandir($dir) as $path) { + if (is_dir($dir . $path)) { + // What if there are user-defined subfolders “thumbs” or “watermarks”? + if ($path != '.' && $path != '..' && $path != 'thumbs' && $path != 'watermark' && $recursive) { + foreach ($this->scanFolders($dir . $path . '/', $recursive, $filter) as $subpath) { + $files[] = $path . '/' . $subpath; + } + } + } elseif (!$filter || stripos($path, $filter) !== false) { + $files[] = $path; + } + } + } - return $files; - } + return $files; + } - /** - * Fetch a list of all files on disk - * - * @param string $media_folder Location of root folder - * @param string $media_path Any subfolder - * @param string $subfolders Include or exclude subfolders - * @param string $filter Filter files whose name contains this test - * - * @return string[] - */ - private function allDiskFiles(string $media_folder, string $media_path, string $subfolders, string $filter): array { - return $this->scanFolders(WT_DATA_DIR . $media_folder . $media_path, $subfolders == 'include', $filter); - } + /** + * Fetch a list of all files on disk + * + * @param string $media_folder Location of root folder + * @param string $media_path Any subfolder + * @param string $subfolders Include or exclude subfolders + * @param string $filter Filter files whose name contains this test + * + * @return string[] + */ + private function allDiskFiles(string $media_folder, string $media_path, string $subfolders, string $filter): array + { + return $this->scanFolders(WT_DATA_DIR . $media_folder . $media_path, $subfolders == 'include', $filter); + } - /** - * Fetch a list of all files on in the database. - * - * @param string $media_folder - * @param string $media_path - * @param string $filter - * - * @return string[] - */ - private function allMediaFiles(string $media_folder, string $media_path, string $filter): array { - return Database::prepare( - "SELECT SQL_CALC_FOUND_ROWS TRIM(LEADING :media_path_1 FROM multimedia_file_refn) AS media_path, 'OBJE' AS type, descriptive_title, m_id AS xref, m_file AS ged_id, m_gedcom AS gedrec, multimedia_file_refn" . - " FROM `##media`" . - " JOIN `##media_file` USING (m_file, m_id)" . - " JOIN `##gedcom_setting` ON (m_file = gedcom_id AND setting_name = 'MEDIA_DIRECTORY')" . - " JOIN `##gedcom` USING (gedcom_id)" . - " WHERE setting_value = :media_folder" . - " AND multimedia_file_refn LIKE CONCAT(:media_path_2, '%')" . - " AND (SUBSTRING_INDEX(multimedia_file_refn, '/', -1) LIKE CONCAT('%', :filter_1, '%')" . - " OR descriptive_title LIKE CONCAT('%', :filter_2, '%'))" . - " AND multimedia_file_refn NOT LIKE 'http://%'" . - " AND multimedia_file_refn NOT LIKE 'https://%'" - )->execute([ - 'media_path_1' => $media_path, - 'media_folder' => $media_folder, - 'media_path_2' => Database::escapeLike($media_path), - 'filter_1' => Database::escapeLike($filter), - 'filter_2' => Database::escapeLike($filter), - ])->fetchOneColumn(); - } + /** + * Fetch a list of all files on in the database. + * + * @param string $media_folder + * @param string $media_path + * @param string $filter + * + * @return string[] + */ + private function allMediaFiles(string $media_folder, string $media_path, string $filter): array + { + return Database::prepare( + "SELECT SQL_CALC_FOUND_ROWS TRIM(LEADING :media_path_1 FROM multimedia_file_refn) AS media_path, 'OBJE' AS type, descriptive_title, m_id AS xref, m_file AS ged_id, m_gedcom AS gedrec, multimedia_file_refn" . + " FROM `##media`" . + " JOIN `##media_file` USING (m_file, m_id)" . + " JOIN `##gedcom_setting` ON (m_file = gedcom_id AND setting_name = 'MEDIA_DIRECTORY')" . + " JOIN `##gedcom` USING (gedcom_id)" . + " WHERE setting_value = :media_folder" . + " AND multimedia_file_refn LIKE CONCAT(:media_path_2, '%')" . + " AND (SUBSTRING_INDEX(multimedia_file_refn, '/', -1) LIKE CONCAT('%', :filter_1, '%')" . + " OR descriptive_title LIKE CONCAT('%', :filter_2, '%'))" . + " AND multimedia_file_refn NOT LIKE 'http://%'" . + " AND multimedia_file_refn NOT LIKE 'https://%'" + )->execute([ + 'media_path_1' => $media_path, + 'media_folder' => $media_folder, + 'media_path_2' => Database::escapeLike($media_path), + 'filter_1' => Database::escapeLike($filter), + 'filter_2' => Database::escapeLike($filter), + ])->fetchOneColumn(); + } - /** - * Generate some useful information and links about a media file. - * - * @param string $media_folder - * @param string $media_path - * @param string $file - * - * @return string - */ - private function mediaFileInfo(string $media_folder, string $media_path, string $file): string { - $html = '<dl>'; - $html .= '<dt>' . I18N::translate('Filename') . '</dt>'; - $html .= '<dd>' . e($file) . '</dd>'; + /** + * Generate some useful information and links about a media file. + * + * @param string $media_folder + * @param string $media_path + * @param string $file + * + * @return string + */ + private function mediaFileInfo(string $media_folder, string $media_path, string $file): string + { + $html = '<dl>'; + $html .= '<dt>' . I18N::translate('Filename') . '</dt>'; + $html .= '<dd>' . e($file) . '</dd>'; - $full_path = WT_DATA_DIR . $media_folder . $media_path . $file; - try { - $size = filesize($full_path); - $size = (int) (($size + 1023) / 1024); // Round up to next KB - $size = /* I18N: size of file in KB */ - I18N::translate('%s KB', I18N::number($size)); - $html .= '<dt>' . I18N::translate('File size') . '</dt>'; - $html .= '<dd>' . $size . '</dd>'; + $full_path = WT_DATA_DIR . $media_folder . $media_path . $file; + try { + $size = filesize($full_path); + $size = (int)(($size + 1023) / 1024); // Round up to next KB + $size = /* I18N: size of file in KB */ + I18N::translate('%s KB', I18N::number($size)); + $html .= '<dt>' . I18N::translate('File size') . '</dt>'; + $html .= '<dd>' . $size . '</dd>'; - try { - $imgsize = getimagesize($full_path); - $html .= '<dt>' . I18N::translate('Image dimensions') . '</dt>'; - $html .= '<dd>' . /* I18N: image dimensions, width × height */ - I18N::translate('%1$s × %2$s pixels', I18N::number($imgsize['0']), I18N::number($imgsize['1'])) . '</dd>'; - } catch (Throwable $ex) { - DebugBar::addThrowable($ex); + try { + $imgsize = getimagesize($full_path); + $html .= '<dt>' . I18N::translate('Image dimensions') . '</dt>'; + $html .= '<dd>' . /* I18N: image dimensions, width × height */ + I18N::translate('%1$s × %2$s pixels', I18N::number($imgsize['0']), I18N::number($imgsize['1'])) . '</dd>'; + } catch (Throwable $ex) { + DebugBar::addThrowable($ex); - // Not an image, or not a valid image? - } + // Not an image, or not a valid image? + } - $html .= '</dl>'; - } catch (Throwable $ex) { - DebugBar::addThrowable($ex); + $html .= '</dl>'; + } catch (Throwable $ex) { + DebugBar::addThrowable($ex); - // Not a file? Not an image? - } + // Not a file? Not an image? + } - return $html; - } + return $html; + } - /** - * Generate some useful information and links about a media object. - * - * @param Media $media - * - * @return string HTML - */ - private function mediaObjectInfo(Media $media) { - $html = '<b><a href="' . e($media->url()) . '">' . $media->getFullName() . '</a></b>' . '<br><i>' . e($media->getNote()) . '</i></br><br>'; + /** + * Generate some useful information and links about a media object. + * + * @param Media $media + * + * @return string HTML + */ + private function mediaObjectInfo(Media $media) + { + $html = '<b><a href="' . e($media->url()) . '">' . $media->getFullName() . '</a></b>' . '<br><i>' . e($media->getNote()) . '</i></br><br>'; - $linked = []; - foreach ($media->linkedIndividuals('OBJE') as $link) { - $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; - } - foreach ($media->linkedFamilies('OBJE') as $link) { - $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; - } - foreach ($media->linkedSources('OBJE') as $link) { - $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; - } - foreach ($media->linkedNotes('OBJE') as $link) { - // Invalid GEDCOM - you cannot link a NOTE to an OBJE - $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; - } - foreach ($media->linkedRepositories('OBJE') as $link) { - // Invalid GEDCOM - you cannot link a REPO to an OBJE - $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; - } - if (!empty($linked)) { - $html .= '<ul>'; - foreach ($linked as $link) { - $html .= '<li>' . $link . '</li>'; - } - $html .= '</ul>'; - } else { - $html .= '<div class="alert alert-danger">' . I18N::translate('There are no links to this media object.') . '</div>'; - } + $linked = []; + foreach ($media->linkedIndividuals('OBJE') as $link) { + $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; + } + foreach ($media->linkedFamilies('OBJE') as $link) { + $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; + } + foreach ($media->linkedSources('OBJE') as $link) { + $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; + } + foreach ($media->linkedNotes('OBJE') as $link) { + // Invalid GEDCOM - you cannot link a NOTE to an OBJE + $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; + } + foreach ($media->linkedRepositories('OBJE') as $link) { + // Invalid GEDCOM - you cannot link a REPO to an OBJE + $linked[] = '<a href="' . e($link->url()) . '">' . $link->getFullName() . '</a>'; + } + if (!empty($linked)) { + $html .= '<ul>'; + foreach ($linked as $link) { + $html .= '<li>' . $link . '</li>'; + } + $html .= '</ul>'; + } else { + $html .= '<div class="alert alert-danger">' . I18N::translate('There are no links to this media object.') . '</div>'; + } - return $html; - } + return $html; + } } |
