1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2019 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 <http://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Services; 21 22use Fisharebest\Webtrees\FlashMessages; 23use Fisharebest\Webtrees\GedcomTag; 24use Fisharebest\Webtrees\I18N; 25use Fisharebest\Webtrees\Tree; 26use Illuminate\Database\Capsule\Manager as DB; 27use InvalidArgumentException; 28use Psr\Http\Message\ServerRequestInterface; 29use Psr\Http\Message\UploadedFileInterface; 30use RuntimeException; 31use Symfony\Component\HttpFoundation\File\UploadedFile; 32 33use function array_combine; 34use function array_diff; 35use function array_filter; 36use function array_map; 37use function assert; 38use function intdiv; 39use function pathinfo; 40use function preg_match; 41use function sha1; 42use function sort; 43use function str_replace; 44use function strpos; 45use function strtolower; 46use function trim; 47 48use const PATHINFO_EXTENSION; 49use const UPLOAD_ERR_OK; 50 51/** 52 * Managing media files. 53 */ 54class MediaFileService 55{ 56 public const EDIT_RESTRICTIONS = [ 57 'locked', 58 ]; 59 60 public const PRIVACY_RESTRICTIONS = [ 61 'none', 62 'privacy', 63 'confidential', 64 ]; 65 66 /** 67 * What is the largest file a user may upload? 68 */ 69 public function maxUploadFilesize(): string 70 { 71 $bytes = UploadedFile::getMaxFilesize(); 72 $kb = intdiv($bytes + 1023, 1024); 73 74 return I18N::translate('%s KB', I18N::number($kb)); 75 } 76 77 /** 78 * A list of key/value options for media types. 79 * 80 * @param string $current 81 * 82 * @return array 83 */ 84 public function mediaTypes($current = ''): array 85 { 86 $media_types = GedcomTag::getFileFormTypes(); 87 88 $media_types = ['' => ''] + [$current => $current] + $media_types; 89 90 return $media_types; 91 } 92 93 /** 94 * A list of media files not already linked to a media object. 95 * 96 * @param Tree $tree 97 * 98 * @return array 99 */ 100 public function unusedFiles(Tree $tree): array 101 { 102 $used_files = DB::table('media_file') 103 ->where('m_file', '=', $tree->id()) 104 ->where('multimedia_file_refn', 'NOT LIKE', 'http://%') 105 ->where('multimedia_file_refn', 'NOT LIKE', 'https://%') 106 ->pluck('multimedia_file_refn') 107 ->all(); 108 109 $disk_files = $tree->mediaFilesystem()->listContents('', true); 110 111 $disk_files = array_filter($disk_files, static function (array $item) { 112 // Older versions of webtrees used a couple of special folders. 113 return 114 $item['type'] === 'file' && 115 strpos($item['path'], '/thumbs/') === false && 116 strpos($item['path'], '/watermarks/') === false; 117 }); 118 119 $disk_files = array_map(static function (array $item): string { 120 return $item['path']; 121 }, $disk_files); 122 123 $unused_files = array_diff($disk_files, $used_files); 124 125 sort($unused_files); 126 127 return array_combine($unused_files, $unused_files); 128 } 129 130 /** 131 * Store an uploaded file (or URL), either to be added to a media object 132 * or to create a media object. 133 * 134 * @param ServerRequestInterface $request 135 * 136 * @return string The value to be stored in the 'FILE' field of the media object. 137 */ 138 public function uploadFile(ServerRequestInterface $request): string 139 { 140 $tree = $request->getAttribute('tree'); 141 assert($tree instanceof Tree); 142 143 $params = $request->getParsedBody(); 144 $file_location = $params['file_location']; 145 146 switch ($file_location) { 147 case 'url': 148 $remote = $params['remote']; 149 150 if (strpos($remote, '://') !== false) { 151 return $remote; 152 } 153 154 return ''; 155 156 case 'unused': 157 $unused = $params['unused']; 158 159 if ($tree->mediaFilesystem()->has($unused)) { 160 return $unused; 161 } 162 163 return ''; 164 165 case 'upload': 166 default: 167 $folder = $params['folder']; 168 $auto = $params['auto']; 169 $new_file = $params['new_file']; 170 171 /** @var UploadedFileInterface|null $uploaded_file */ 172 $uploaded_file = $request->getUploadedFiles()['file']; 173 if ($uploaded_file === null || $uploaded_file->getError() !== UPLOAD_ERR_OK) { 174 return ''; 175 } 176 177 // The filename 178 $new_file = str_replace('\\', '/', $new_file); 179 if ($new_file !== '' && strpos($new_file, '/') === false) { 180 $file = $new_file; 181 } else { 182 $file = $uploaded_file->getClientFilename(); 183 } 184 185 // The folder 186 $folder = str_replace('\\', '/', $folder); 187 $folder = trim($folder, '/'); 188 if ($folder !== '') { 189 $folder .= '/'; 190 } 191 192 // Generate a unique name for the file? 193 if ($auto === '1' || $tree->mediaFilesystem()->has($folder . $file)) { 194 $folder = ''; 195 $extension = pathinfo($uploaded_file->getClientFilename(), PATHINFO_EXTENSION); 196 $file = sha1((string) $uploaded_file->getStream()) . '.' . $extension; 197 } 198 199 try { 200 $tree->mediaFilesystem()->writeStream($folder . $file, $uploaded_file->getStream()->detach()); 201 202 return $folder . $file; 203 } catch (RuntimeException | InvalidArgumentException $ex) { 204 FlashMessages::addMessage(I18N::translate('There was an error uploading your file.')); 205 206 return ''; 207 } 208 } 209 } 210 211 /** 212 * Convert the media file attributes into GEDCOM format. 213 * 214 * @param string $file 215 * @param string $type 216 * @param string $title 217 * 218 * @return string 219 */ 220 public function createMediaFileGedcom(string $file, string $type, string $title): string 221 { 222 if (preg_match('/\.([a-z0-9]+)/i', $file, $match)) { 223 $extension = strtolower($match[1]); 224 $extension = str_replace('jpg', 'jpeg', $extension); 225 $extension = ' ' . $extension; 226 } else { 227 $extension = ''; 228 } 229 230 $gedcom = '1 FILE ' . $file; 231 if ($type !== '') { 232 $gedcom .= "\n2 FORM" . $extension . "\n3 TYPE " . $type; 233 } 234 if ($title !== '') { 235 $gedcom .= "\n2 TITL " . $title; 236 } 237 238 return $gedcom; 239 } 240} 241