1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2021 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Http\RequestHandlers; 21 22use Fisharebest\Webtrees\FlashMessages; 23use Fisharebest\Webtrees\Html; 24use Fisharebest\Webtrees\I18N; 25use Fisharebest\Webtrees\MediaFile; 26use Fisharebest\Webtrees\Registry; 27use Fisharebest\Webtrees\Services\MediaFileService; 28use Fisharebest\Webtrees\Services\PendingChangesService; 29use Fisharebest\Webtrees\Tree; 30use League\Flysystem\FileExistsException; 31use League\Flysystem\FileNotFoundException; 32use League\Flysystem\Util; 33use Psr\Http\Message\ResponseInterface; 34use Psr\Http\Message\ServerRequestInterface; 35use Psr\Http\Server\RequestHandlerInterface; 36 37use function assert; 38use function is_string; 39use function preg_replace; 40use function redirect; 41use function route; 42use function str_replace; 43use function trim; 44 45/** 46 * Edit a media file. 47 */ 48class EditMediaFileAction implements RequestHandlerInterface 49{ 50 /** @var MediaFileService */ 51 private $media_file_service; 52 53 /** @var PendingChangesService */ 54 private $pending_changes_service; 55 56 /** 57 * EditMediaFileAction constructor. 58 * 59 * @param MediaFileService $media_file_service 60 * @param PendingChangesService $pending_changes_service 61 */ 62 public function __construct(MediaFileService $media_file_service, PendingChangesService $pending_changes_service) 63 { 64 $this->media_file_service = $media_file_service; 65 $this->pending_changes_service = $pending_changes_service; 66 } 67 68 /** 69 * Save an edited media file. 70 * 71 * @param ServerRequestInterface $request 72 * 73 * @return ResponseInterface 74 */ 75 public function handle(ServerRequestInterface $request): ResponseInterface 76 { 77 $tree = $request->getAttribute('tree'); 78 assert($tree instanceof Tree); 79 80 $xref = $request->getAttribute('xref'); 81 assert(is_string($xref)); 82 83 $fact_id = $request->getAttribute('fact_id'); 84 assert(is_string($fact_id)); 85 86 $data_filesystem = Registry::filesystem()->data(); 87 88 $params = (array) $request->getParsedBody(); 89 $folder = $params['folder'] ?? ''; 90 $new_file = $params['new_file'] ?? ''; 91 $remote = $params['remote'] ?? ''; 92 $title = $params['title'] ?? ''; 93 $type = $params['type'] ?? ''; 94 $media = Registry::mediaFactory()->make($xref, $tree); 95 96 // Tidy non-printing characters 97 $type = trim(preg_replace('/\s+/', ' ', $type)); 98 $title = trim(preg_replace('/\s+/', ' ', $title)); 99 100 // Media object oes not exist? Media object is read-only? 101 if ($media === null || $media->isPendingDeletion() || !$media->canEdit()) { 102 return redirect(route(TreePage::class, ['tree' => $tree->name()])); 103 } 104 105 // Find the fact to edit 106 $media_file = $media->mediaFiles() 107 ->first(static function (MediaFile $media_file) use ($fact_id): bool { 108 return $media_file->factId() === $fact_id; 109 }); 110 111 // Media file does not exist? 112 if ($media_file === null) { 113 return redirect(route(TreePage::class, ['tree' => $tree->name()])); 114 } 115 116 // We can edit the file as either a URL or a folder/file 117 if ($remote !== '') { 118 $file = $remote; 119 } else { 120 $new_file = str_replace('\\', '/', $new_file); 121 $folder = str_replace('\\', '/', $folder); 122 $folder = trim($folder, '/'); 123 124 if ($folder === '') { 125 $file = $new_file; 126 } else { 127 $file = $folder . '/' . $new_file; 128 } 129 } 130 131 // Invalid filename? Do not change it. 132 if ($new_file === '') { 133 $file = $media_file->filename(); 134 } 135 136 $filesystem = $media->tree()->mediaFilesystem($data_filesystem); 137 $old = $media_file->filename(); 138 $new = $file; 139 140 // Update the filesystem, if we can. 141 if ($old !== $new && !$media_file->isExternal()) { 142 try { 143 $new = Util::normalizePath($new); 144 $filesystem->rename($old, $new); 145 FlashMessages::addMessage(I18N::translate('The media file %1$s has been renamed to %2$s.', Html::filename($media_file->filename()), Html::filename($file)), 'info'); 146 } catch (FileNotFoundException $ex) { 147 // The "old" file may not exist. For example, if the file was renamed on disk, 148 // and we are now renaming the GEDCOM data to match. 149 } catch (FileExistsException $ex) { 150 // Don't overwrite existing file 151 FlashMessages::addMessage(I18N::translate('The media file %1$s could not be renamed to %2$s.', Html::filename($media_file->filename()), Html::filename($file)), 'info'); 152 $file = $old; 153 } 154 } 155 156 $gedcom = $this->media_file_service->createMediaFileGedcom($file, $type, $title, ''); 157 158 $media->updateFact($fact_id, $gedcom, true); 159 160 // Accept the changes, to keep the filesystem in sync with the GEDCOM data. 161 if ($old !== $new && !$media_file->isExternal()) { 162 $this->pending_changes_service->acceptRecord($media); 163 } 164 165 return redirect($media->url()); 166 } 167} 168