diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/Functions/FunctionsEdit.php | 3 | ||||
| -rw-r--r-- | app/Http/Controllers/EditMediaController.php | 232 | ||||
| -rw-r--r-- | app/Http/Controllers/EditNoteController.php | 57 | ||||
| -rw-r--r-- | app/Http/RequestHandlers/CreateMediaObjectAction.php | 125 | ||||
| -rw-r--r-- | app/Http/RequestHandlers/CreateMediaObjectModal.php | 65 | ||||
| -rw-r--r-- | app/Http/RequestHandlers/CreateNoteAction.php | 79 | ||||
| -rw-r--r-- | app/Http/RequestHandlers/CreateNoteModal.php | 52 | ||||
| -rw-r--r-- | app/Http/RequestHandlers/CreateRepositoryModal.php | 2 | ||||
| -rw-r--r-- | app/Http/RequestHandlers/CreateSourceModal.php | 90 | ||||
| -rw-r--r-- | app/Services/MediaFileService.php | 240 |
10 files changed, 588 insertions, 357 deletions
diff --git a/app/Functions/FunctionsEdit.php b/app/Functions/FunctionsEdit.php index 68e65076dd..1b50dae045 100644 --- a/app/Functions/FunctionsEdit.php +++ b/app/Functions/FunctionsEdit.php @@ -37,6 +37,7 @@ use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp; use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\Html; +use Fisharebest\Webtrees\Http\RequestHandlers\CreateMediaObjectModal; use Fisharebest\Webtrees\Http\RequestHandlers\CreateRepositoryModal; use Fisharebest\Webtrees\Http\RequestHandlers\CreateSourceModal; use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmitterModal; @@ -488,7 +489,7 @@ class FunctionsEdit } elseif ($fact === 'OBJE') { $html .= '<div class="input-group">' . - '<div class="input-group-prepend"><button class="btn btn-secondary" type="button" data-toggle="modal" data-href="' . e(route('create-media-object', ['tree' => $tree->name()])) . '" data-target="#wt-ajax-modal" data-select-id="' . $id . '" title="' . I18N::translate('Create a media object') . '">' . view('icons/add') . '</button></div>' . + '<div class="input-group-prepend"><button class="btn btn-secondary" type="button" data-toggle="modal" data-href="' . e(route(CreateMediaObjectModal::class, ['tree' => $tree->name()])) . '" data-target="#wt-ajax-modal" data-select-id="' . $id . '" title="' . I18N::translate('Create a media object') . '">' . view('icons/add') . '</button></div>' . view('components/select-media', ['id' => $id, 'name' => $name, 'media' => Media::getInstance($value, $tree), 'tree' => $tree]) . '</div>'; } elseif ($fact === 'PAGE') { diff --git a/app/Http/Controllers/EditMediaController.php b/app/Http/Controllers/EditMediaController.php index 2310f4e325..a087b4a8c6 100644 --- a/app/Http/Controllers/EditMediaController.php +++ b/app/Http/Controllers/EditMediaController.php @@ -24,44 +24,27 @@ use Fig\Http\Message\StatusCodeInterface; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\FlashMessages; use Fisharebest\Webtrees\GedcomRecord; -use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\Html; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Media; +use Fisharebest\Webtrees\Services\MediaFileService; use Fisharebest\Webtrees\Services\PendingChangesService; use Fisharebest\Webtrees\Tree; -use Illuminate\Database\Capsule\Manager as DB; -use InvalidArgumentException; use League\Flysystem\FileExistsException; use League\Flysystem\FileNotFoundException; use League\Flysystem\Util; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\UploadedFileInterface; -use RuntimeException; -use Symfony\Component\HttpFoundation\File\UploadedFile; use function assert; -use function pathinfo; -use function strpos; - -use const PATHINFO_EXTENSION; -use const UPLOAD_ERR_OK; /** * Controller for edit forms and responses. */ class EditMediaController extends AbstractEditController { - private const EDIT_RESTRICTIONS = [ - 'locked', - ]; - - private const PRIVACY_RESTRICTIONS = [ - 'none', - 'privacy', - 'confidential', - ]; + /** @var MediaFileService */ + private $media_file_service; /** @var PendingChangesService */ private $pending_changes_service; @@ -69,10 +52,12 @@ class EditMediaController extends AbstractEditController /** * EditMediaController constructor. * + * @param MediaFileService $media_file_service * @param PendingChangesService $pending_changes_service */ - public function __construct(PendingChangesService $pending_changes_service) + public function __construct(MediaFileService $media_file_service, PendingChangesService $pending_changes_service) { + $this->media_file_service = $media_file_service; $this->pending_changes_service = $pending_changes_service; } @@ -101,10 +86,10 @@ class EditMediaController extends AbstractEditController } return response(view('modals/add-media-file', [ - 'max_upload_size' => $this->maxUploadFilesize(), + 'max_upload_size' => $this->media_file_service->maxUploadFilesize(), 'media' => $media, - 'media_types' => $this->mediaTypes(), - 'unused_files' => $this->unusedFiles($tree), + 'media_types' => $this->media_file_service->mediaTypes(), + 'unused_files' => $this->media_file_service->unusedFiles($tree), ])); } @@ -133,7 +118,7 @@ class EditMediaController extends AbstractEditController return redirect(route('tree-page', ['tree' => $tree->name()])); } - $file = $this->uploadFile($request); + $file = $this->media_file_service->uploadFile($request); if ($file === '') { FlashMessages::addMessage(I18N::translate('There was an error uploading your file.')); @@ -187,10 +172,10 @@ class EditMediaController extends AbstractEditController if ($media_file->factId() === $fact_id) { return response(view('modals/edit-media-file', [ 'media_file' => $media_file, - 'max_upload_size' => $this->maxUploadFilesize(), + 'max_upload_size' => $this->media_file_service->maxUploadFilesize(), 'media' => $media, - 'media_types' => $this->mediaTypes(), - 'unused_files' => $this->unusedFiles($tree), + 'media_types' => $this->media_file_service->mediaTypes(), + 'unused_files' => $this->media_file_service->unusedFiles($tree), ])); } } @@ -281,7 +266,7 @@ class EditMediaController extends AbstractEditController } } - $gedcom = $this->createMediaFileGedcom($file, $type, $title); + $gedcom = $this->media_file_service->createMediaFileGedcom($file, $type, $title); $media->updateFact($fact_id, $gedcom, true); @@ -306,9 +291,9 @@ class EditMediaController extends AbstractEditController assert($tree instanceof Tree); return response(view('modals/create-media-object', [ - 'max_upload_size' => $this->maxUploadFilesize(), - 'media_types' => $this->mediaTypes(), - 'unused_files' => $this->unusedFiles($tree), + 'max_upload_size' => $this->media_file_service->maxUploadFilesize(), + 'media_types' => $this->media_file_service->mediaTypes(), + 'unused_files' => $this->media_file_service->unusedFiles($tree), ])); } @@ -385,23 +370,23 @@ class EditMediaController extends AbstractEditController "\n", ], "\n1 CONT ", $note); - $file = $this->uploadFile($request); + $file = $this->media_file_service->uploadFile($request); if ($file === '') { return response(['error_message' => I18N::translate('There was an error uploading your file.')], 406); } - $gedcom = "0 @@ OBJE\n" . $this->createMediaFileGedcom($file, $type, $title); + $gedcom = "0 @@ OBJE\n" . $this->media_file_service->createMediaFileGedcom($file, $type, $title); if ($note !== '') { $gedcom .= "\n1 NOTE " . preg_replace('/\r?\n/', "\n2 CONT ", $note); } - if (in_array($privacy_restriction, self::PRIVACY_RESTRICTIONS, true)) { + if (in_array($privacy_restriction, $this->media_file_service::PRIVACY_RESTRICTIONS, true)) { $gedcom .= "\n1 RESN " . $privacy_restriction; } - if (in_array($edit_restriction, self::EDIT_RESTRICTIONS, true)) { + if (in_array($edit_restriction, $this->media_file_service::EDIT_RESTRICTIONS, true)) { $gedcom .= "\n1 RESN " . $edit_restriction; } @@ -504,179 +489,4 @@ class EditMediaController extends AbstractEditController return redirect($media->url()); } - - /** - * Convert the media file attributes into GEDCOM format. - * - * @param string $file - * @param string $type - * @param string $title - * - * @return string - */ - private function createMediaFileGedcom(string $file, string $type, string $title): string - { - if (preg_match('/\.([a-z0-9]+)/i', $file, $match)) { - $extension = strtolower($match[1]); - $extension = str_replace('jpg', 'jpeg', $extension); - $extension = ' ' . $extension; - } else { - $extension = ''; - } - - $gedcom = '1 FILE ' . $file; - if ($type !== '') { - $gedcom .= "\n2 FORM" . $extension . "\n3 TYPE " . $type; - } - if ($title !== '') { - $gedcom .= "\n2 TITL " . $title; - } - - return $gedcom; - } - - /** - * What is the largest file a user may upload? - */ - private function maxUploadFilesize(): string - { - $bytes = UploadedFile::getMaxFilesize(); - $kb = intdiv($bytes + 1023, 1024); - - return I18N::translate('%s KB', I18N::number($kb)); - } - - /** - * A list of key/value options for media types. - * - * @param string $current - * - * @return array - */ - private function mediaTypes($current = ''): array - { - $media_types = GedcomTag::getFileFormTypes(); - - $media_types = ['' => ''] + [$current => $current] + $media_types; - - return $media_types; - } - - /** - * Store an uploaded file (or URL), either to be added to a media object - * or to create a media object. - * - * @param ServerRequestInterface $request - * - * @return string The value to be stored in the 'FILE' field of the media object. - */ - private function uploadFile(ServerRequestInterface $request): string - { - $tree = $request->getAttribute('tree'); - assert($tree instanceof Tree); - - $params = $request->getParsedBody(); - $file_location = $params['file_location']; - - switch ($file_location) { - case 'url': - $remote = $params['remote']; - - if (strpos($remote, '://') !== false) { - return $remote; - } - - return ''; - - case 'unused': - $unused = $params['unused']; - - if ($tree->mediaFilesystem()->has($unused)) { - return $unused; - } - - return ''; - - case 'upload': - default: - $folder = $params['folder']; - $auto = $params['auto']; - $new_file = $params['new_file']; - - /** @var UploadedFileInterface|null $uploaded_file */ - $uploaded_file = $request->getUploadedFiles()['file']; - if ($uploaded_file === null || $uploaded_file->getError() !== UPLOAD_ERR_OK) { - return ''; - } - - // The filename - $new_file = str_replace('\\', '/', $new_file); - if ($new_file !== '' && strpos($new_file, '/') === false) { - $file = $new_file; - } else { - $file = $uploaded_file->getClientFilename(); - } - - // The folder - $folder = str_replace('\\', '/', $folder); - $folder = trim($folder, '/'); - if ($folder !== '') { - $folder .= '/'; - } - - // Generate a unique name for the file? - if ($auto === '1' || $tree->mediaFilesystem()->has($folder . $file)) { - $folder = ''; - $extension = pathinfo($uploaded_file->getClientFilename(), PATHINFO_EXTENSION); - $file = sha1((string) $uploaded_file->getStream()) . '.' . $extension; - } - - try { - $tree->mediaFilesystem()->writeStream($folder . $file, $uploaded_file->getStream()->detach()); - - return $folder . $file; - } catch (RuntimeException | InvalidArgumentException $ex) { - FlashMessages::addMessage(I18N::translate('There was an error uploading your file.')); - - return ''; - } - } - } - - /** - * A list of media files not already linked to a media object. - * - * @param Tree $tree - * - * @return array - */ - private function unusedFiles(Tree $tree): array - { - $used_files = DB::table('media_file') - ->where('m_file', '=', $tree->id()) - ->where('multimedia_file_refn', 'NOT LIKE', 'http://%') - ->where('multimedia_file_refn', 'NOT LIKE', 'https://%') - ->pluck('multimedia_file_refn') - ->all(); - - $disk_files = $tree->mediaFilesystem()->listContents('', true); - - $disk_files = array_filter($disk_files, static function (array $item) { - // Older versions of webtrees used a couple of special folders. - return - $item['type'] === 'file' && - strpos($item['path'], '/thumbs/') === false && - strpos($item['path'], '/watermarks/') === false; - }); - - $disk_files = array_map(static function (array $item): string { - return $item['path']; - }, $disk_files); - - $unused_files = array_diff($disk_files, $used_files); - - sort($unused_files); - - return array_combine($unused_files, $unused_files); - } } diff --git a/app/Http/Controllers/EditNoteController.php b/app/Http/Controllers/EditNoteController.php index 8b3622f074..6fe0cb48cb 100644 --- a/app/Http/Controllers/EditNoteController.php +++ b/app/Http/Controllers/EditNoteController.php @@ -40,18 +40,6 @@ class EditNoteController extends AbstractEditController * * @return ResponseInterface */ - public function createNoteObject(ServerRequestInterface $request): ResponseInterface - { - return response(view('modals/create-note-object')); - } - - /** - * Show a form to create a new note object. - * - * @param ServerRequestInterface $request - * - * @return ResponseInterface - */ public function editNoteObject(ServerRequestInterface $request): ResponseInterface { $tree = $request->getAttribute('tree'); @@ -107,49 +95,4 @@ class EditNoteController extends AbstractEditController return redirect($note->url()); } - - /** - * Process a form to create a new note object. - * - * @param ServerRequestInterface $request - * - * @return ResponseInterface - */ - public function createNoteObjectAction(ServerRequestInterface $request): ResponseInterface - { - $tree = $request->getAttribute('tree'); - assert($tree instanceof Tree); - - $params = $request->getParsedBody(); - $note = $params['note']; - $privacy_restriction = $params['privacy-restriction']; - $edit_restriction = $params['edit-restriction']; - - // Convert line endings to GEDDCOM continuations - $note = preg_replace('/\r|\r\n|\n/', "\n1 CONT ", $note); - - $gedcom = '0 @@ NOTE ' . $note; - - if (in_array($privacy_restriction, ['none', 'privacy', 'confidential'], true)) { - $gedcom .= "\n1 RESN " . $privacy_restriction; - } - - if ($edit_restriction === 'locked') { - $gedcom .= "\n1 RESN " . $edit_restriction; - } - - $record = $tree->createRecord($gedcom); - - return response([ - 'id' => $record->xref(), - 'text' => view('selects/note', [ - 'note' => $record, - ]), - 'html' => view('modals/record-created', [ - 'title' => I18N::translate('The note has been created'), - 'name' => $record->fullName(), - 'url' => $record->url(), - ]), - ]); - } } diff --git a/app/Http/RequestHandlers/CreateMediaObjectAction.php b/app/Http/RequestHandlers/CreateMediaObjectAction.php new file mode 100644 index 0000000000..20f8e8d17e --- /dev/null +++ b/app/Http/RequestHandlers/CreateMediaObjectAction.php @@ -0,0 +1,125 @@ +<?php + +/** + * webtrees: online genealogy + * Copyright (C) 2019 webtrees development team + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +declare(strict_types=1); + +namespace Fisharebest\Webtrees\Http\RequestHandlers; + +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Services\MediaFileService; +use Fisharebest\Webtrees\Services\PendingChangesService; +use Fisharebest\Webtrees\Tree; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; + +use function assert; +use function in_array; + +/** + * Process a form to create a new media object. + */ +class CreateMediaObjectAction implements RequestHandlerInterface +{ + /** @var MediaFileService */ + private $media_file_service; + + /** @var PendingChangesService */ + private $pending_changes_service; + + /** + * CreateMediaObjectAction constructor. + * + * @param MediaFileService $media_file_service + * @param PendingChangesService $pending_changes_service + */ + public function __construct(MediaFileService $media_file_service, PendingChangesService $pending_changes_service) + { + $this->media_file_service = $media_file_service; + $this->pending_changes_service = $pending_changes_service; + } + + /** + * Process a form to create a new media object. + * + * @param ServerRequestInterface $request + * + * @return ResponseInterface + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $tree = $request->getAttribute('tree'); + assert($tree instanceof Tree); + + $params = $request->getParsedBody(); + $note = $params['media-note']; + $title = $params['title']; + $type = $params['type']; + $privacy_restriction = $params['privacy-restriction']; + $edit_restriction = $params['edit-restriction']; + + // Tidy whitespace + $type = trim(preg_replace('/\s+/', ' ', $type)); + $title = trim(preg_replace('/\s+/', ' ', $title)); + + // Convert line endings to GEDDCOM continuations + $note = str_replace([ + "\r\n", + "\r", + "\n", + ], "\n1 CONT ", $note); + + $file = $this->media_file_service->uploadFile($request); + + if ($file === '') { + return response(['error_message' => I18N::translate('There was an error uploading your file.')], 406); + } + + $gedcom = "0 @@ OBJE\n" . $this->media_file_service->createMediaFileGedcom($file, $type, $title); + + if ($note !== '') { + $gedcom .= "\n1 NOTE " . preg_replace('/\r?\n/', "\n2 CONT ", $note); + } + + if (in_array($privacy_restriction, $this->media_file_service::PRIVACY_RESTRICTIONS, true)) { + $gedcom .= "\n1 RESN " . $privacy_restriction; + } + + if (in_array($edit_restriction, $this->media_file_service::EDIT_RESTRICTIONS, true)) { + $gedcom .= "\n1 RESN " . $edit_restriction; + } + + $record = $tree->createMediaObject($gedcom); + + // Accept the new record to keep the filesystem synchronized with the genealogy. + $this->pending_changes_service->acceptRecord($record); + + // id and text are for select2 / autocomplete + // html is for interactive modals + return response([ + 'id' => $record->xref(), + 'text' => view('selects/media', [ + 'media' => $record, + ]), + 'html' => view('modals/record-created', [ + 'title' => I18N::translate('The media object has been created'), + 'name' => $record->fullName(), + 'url' => $record->url(), + ]), + ]); + } +} diff --git a/app/Http/RequestHandlers/CreateMediaObjectModal.php b/app/Http/RequestHandlers/CreateMediaObjectModal.php new file mode 100644 index 0000000000..d598b00844 --- /dev/null +++ b/app/Http/RequestHandlers/CreateMediaObjectModal.php @@ -0,0 +1,65 @@ +<?php + +/** + * webtrees: online genealogy + * Copyright (C) 2019 webtrees development team + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +declare(strict_types=1); + +namespace Fisharebest\Webtrees\Http\RequestHandlers; + +use Fisharebest\Webtrees\Services\MediaFileService; +use Fisharebest\Webtrees\Tree; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; + +use function assert; + +/** + * Show a form to create a new media object. + */ +class CreateMediaObjectModal implements RequestHandlerInterface +{ + /** @var MediaFileService */ + private $media_file_service; + + /** + * CreateMediaObjectModal constructor. + * + * @param MediaFileService $media_file_service + */ + public function __construct(MediaFileService $media_file_service) + { + $this->media_file_service = $media_file_service; + } + + /** + * @param ServerRequestInterface $request + * + * @return ResponseInterface + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $tree = $request->getAttribute('tree'); + assert($tree instanceof Tree); + + return response(view('modals/create-media-object', [ + 'max_upload_size' => $this->media_file_service->maxUploadFilesize(), + 'media_types' => $this->media_file_service->mediaTypes(), + 'unused_files' => $this->media_file_service->unusedFiles($tree), + 'tree' => $tree, + ])); + } +} diff --git a/app/Http/RequestHandlers/CreateNoteAction.php b/app/Http/RequestHandlers/CreateNoteAction.php new file mode 100644 index 0000000000..018b1dfd50 --- /dev/null +++ b/app/Http/RequestHandlers/CreateNoteAction.php @@ -0,0 +1,79 @@ +<?php + +/** + * webtrees: online genealogy + * Copyright (C) 2019 webtrees development team + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +declare(strict_types=1); + +namespace Fisharebest\Webtrees\Http\RequestHandlers; + +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Tree; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; + +use function assert; + +/** + * Process a form to create a new note object. + */ +class CreateNoteAction implements RequestHandlerInterface +{ + /** + * @param ServerRequestInterface $request + * + * @return ResponseInterface + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $tree = $request->getAttribute('tree'); + assert($tree instanceof Tree); + + $params = $request->getParsedBody(); + $note = $params['note']; + $privacy_restriction = $params['privacy-restriction']; + $edit_restriction = $params['edit-restriction']; + + // Convert line endings to GEDDCOM continuations + $note = preg_replace('/\r|\r\n|\n/', "\n1 CONT ", $note); + + $gedcom = '0 @@ NOTE ' . $note; + + if (in_array($privacy_restriction, ['none', 'privacy', 'confidential'], true)) { + $gedcom .= "\n1 RESN " . $privacy_restriction; + } + + if ($edit_restriction === 'locked') { + $gedcom .= "\n1 RESN " . $edit_restriction; + } + + $record = $tree->createRecord($gedcom); + + // id and text are for select2 / autocomplete + // html is for interactive modals + return response([ + 'id' => $record->xref(), + 'text' => view('selects/note', [ + 'note' => $record, + ]), + 'html' => view('modals/record-created', [ + 'title' => I18N::translate('The note has been created'), + 'name' => $record->fullName(), + 'url' => $record->url(), + ]), + ]); + } +} diff --git a/app/Http/RequestHandlers/CreateNoteModal.php b/app/Http/RequestHandlers/CreateNoteModal.php new file mode 100644 index 0000000000..37624045ae --- /dev/null +++ b/app/Http/RequestHandlers/CreateNoteModal.php @@ -0,0 +1,52 @@ +<?php + +/** + * webtrees: online genealogy + * Copyright (C) 2019 webtrees development team + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +declare(strict_types=1); + +namespace Fisharebest\Webtrees\Http\RequestHandlers; + +use Fisharebest\Webtrees\Tree; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; + +use function assert; +use function response; +use function view; + +/** + * Show a form to create a new note object. + */ +class CreateNoteModal implements RequestHandlerInterface +{ + /** + * Show a form to create a new note object. + * + * @param ServerRequestInterface $request + * + * @return ResponseInterface + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $tree = $request->getAttribute('tree'); + assert($tree instanceof Tree); + + return response(view('modals/create-note-object', [ + 'tree' => $tree, + ])); + } +} diff --git a/app/Http/RequestHandlers/CreateRepositoryModal.php b/app/Http/RequestHandlers/CreateRepositoryModal.php index def6c0b3f3..cffa7497e9 100644 --- a/app/Http/RequestHandlers/CreateRepositoryModal.php +++ b/app/Http/RequestHandlers/CreateRepositoryModal.php @@ -26,6 +26,8 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use function assert; +use function response; +use function view; /** * Show a form to create a new repository. diff --git a/app/Http/RequestHandlers/CreateSourceModal.php b/app/Http/RequestHandlers/CreateSourceModal.php index 2e29b15431..41cf0019e6 100644 --- a/app/Http/RequestHandlers/CreateSourceModal.php +++ b/app/Http/RequestHandlers/CreateSourceModal.php @@ -19,13 +19,14 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Http\RequestHandlers; -use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Tree; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use function assert; +use function response; +use function view; /** * Process a form to create a new source. @@ -46,91 +47,4 @@ class CreateSourceModal implements RequestHandlerInterface 'tree' => $tree, ])); } - - /** - * @param ServerRequestInterface $request - * - * @return ResponseInterface - */ - public function createSourceAction(ServerRequestInterface $request): ResponseInterface - { - $tree = $request->getAttribute('tree'); - assert($tree instanceof Tree); - - $params = $request->getParsedBody(); - $title = $params['source-title']; - $abbreviation = $params['source-abbreviation']; - $author = $params['source-author']; - $publication = $params['source-publication']; - $repository = $params['source-repository']; - $call_number = $params['source-call-number']; - $text = $params['source-text']; - $privacy_restriction = $params['privacy-restriction']; - $edit_restriction = $params['edit-restriction']; - - // Fix whitespace - $title = trim(preg_replace('/\s+/', ' ', $title)); - $abbreviation = trim(preg_replace('/\s+/', ' ', $abbreviation)); - $author = trim(preg_replace('/\s+/', ' ', $author)); - $publication = trim(preg_replace('/\s+/', ' ', $publication)); - $repository = trim(preg_replace('/\s+/', ' ', $repository)); - $call_number = trim(preg_replace('/\s+/', ' ', $call_number)); - - // Convert line endings to GEDDCOM continuations - $text = str_replace([ - "\r\n", - "\r", - "\n", - ], "\n1 CONT ", $text); - - $gedcom = "0 @@ SOUR\n\n1 TITL " . $title; - - if ($abbreviation !== '') { - $gedcom .= "\n1 ABBR " . $abbreviation; - } - - if ($author !== '') { - $gedcom .= "\n1 AUTH " . $author; - } - - if ($publication !== '') { - $gedcom .= "\n1 PUBL " . $publication; - } - - if ($text !== '') { - $gedcom .= "\n1 TEXT " . $text; - } - - if ($repository !== '') { - $gedcom .= "\n1 REPO @" . $repository . '@'; - - if ($call_number !== '') { - $gedcom .= "\n2 CALN " . $call_number; - } - } - - if (in_array($privacy_restriction, ['none', 'privacy', 'confidential'], true)) { - $gedcom .= "\n1 RESN " . $privacy_restriction; - } - - if ($edit_restriction === 'locked') { - $gedcom .= "\n1 RESN " . $edit_restriction; - } - - $record = $tree->createRecord($gedcom); - - // id and text are for select2 / autocomplete - // html is for interactive modals - return response([ - 'id' => $record->xref(), - 'text' => view('selects/source', [ - 'source' => $record, - ]), - 'html' => view('modals/record-created', [ - 'title' => I18N::translate('The source has been created'), - 'name' => $record->fullName(), - 'url' => $record->url(), - ]), - ]); - } } diff --git a/app/Services/MediaFileService.php b/app/Services/MediaFileService.php new file mode 100644 index 0000000000..9532562713 --- /dev/null +++ b/app/Services/MediaFileService.php @@ -0,0 +1,240 @@ +<?php + +/** + * webtrees: online genealogy + * Copyright (C) 2019 webtrees development team + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +declare(strict_types=1); + +namespace Fisharebest\Webtrees\Services; + +use Fisharebest\Webtrees\FlashMessages; +use Fisharebest\Webtrees\GedcomTag; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Tree; +use Illuminate\Database\Capsule\Manager as DB; +use InvalidArgumentException; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\UploadedFileInterface; +use RuntimeException; +use Symfony\Component\HttpFoundation\File\UploadedFile; + +use function array_combine; +use function array_diff; +use function array_filter; +use function array_map; +use function assert; +use function intdiv; +use function pathinfo; +use function preg_match; +use function sha1; +use function sort; +use function str_replace; +use function strpos; +use function strtolower; +use function trim; + +use const PATHINFO_EXTENSION; +use const UPLOAD_ERR_OK; + +/** + * Managing media files. + */ +class MediaFileService +{ + public const EDIT_RESTRICTIONS = [ + 'locked', + ]; + + public const PRIVACY_RESTRICTIONS = [ + 'none', + 'privacy', + 'confidential', + ]; + + /** + * What is the largest file a user may upload? + */ + public function maxUploadFilesize(): string + { + $bytes = UploadedFile::getMaxFilesize(); + $kb = intdiv($bytes + 1023, 1024); + + return I18N::translate('%s KB', I18N::number($kb)); + } + + /** + * A list of key/value options for media types. + * + * @param string $current + * + * @return array + */ + public function mediaTypes($current = ''): array + { + $media_types = GedcomTag::getFileFormTypes(); + + $media_types = ['' => ''] + [$current => $current] + $media_types; + + return $media_types; + } + + /** + * A list of media files not already linked to a media object. + * + * @param Tree $tree + * + * @return array + */ + public function unusedFiles(Tree $tree): array + { + $used_files = DB::table('media_file') + ->where('m_file', '=', $tree->id()) + ->where('multimedia_file_refn', 'NOT LIKE', 'http://%') + ->where('multimedia_file_refn', 'NOT LIKE', 'https://%') + ->pluck('multimedia_file_refn') + ->all(); + + $disk_files = $tree->mediaFilesystem()->listContents('', true); + + $disk_files = array_filter($disk_files, static function (array $item) { + // Older versions of webtrees used a couple of special folders. + return + $item['type'] === 'file' && + strpos($item['path'], '/thumbs/') === false && + strpos($item['path'], '/watermarks/') === false; + }); + + $disk_files = array_map(static function (array $item): string { + return $item['path']; + }, $disk_files); + + $unused_files = array_diff($disk_files, $used_files); + + sort($unused_files); + + return array_combine($unused_files, $unused_files); + } + + /** + * Store an uploaded file (or URL), either to be added to a media object + * or to create a media object. + * + * @param ServerRequestInterface $request + * + * @return string The value to be stored in the 'FILE' field of the media object. + */ + public function uploadFile(ServerRequestInterface $request): string + { + $tree = $request->getAttribute('tree'); + assert($tree instanceof Tree); + + $params = $request->getParsedBody(); + $file_location = $params['file_location']; + + switch ($file_location) { + case 'url': + $remote = $params['remote']; + + if (strpos($remote, '://') !== false) { + return $remote; + } + + return ''; + + case 'unused': + $unused = $params['unused']; + + if ($tree->mediaFilesystem()->has($unused)) { + return $unused; + } + + return ''; + + case 'upload': + default: + $folder = $params['folder']; + $auto = $params['auto']; + $new_file = $params['new_file']; + + /** @var UploadedFileInterface|null $uploaded_file */ + $uploaded_file = $request->getUploadedFiles()['file']; + if ($uploaded_file === null || $uploaded_file->getError() !== UPLOAD_ERR_OK) { + return ''; + } + + // The filename + $new_file = str_replace('\\', '/', $new_file); + if ($new_file !== '' && strpos($new_file, '/') === false) { + $file = $new_file; + } else { + $file = $uploaded_file->getClientFilename(); + } + + // The folder + $folder = str_replace('\\', '/', $folder); + $folder = trim($folder, '/'); + if ($folder !== '') { + $folder .= '/'; + } + + // Generate a unique name for the file? + if ($auto === '1' || $tree->mediaFilesystem()->has($folder . $file)) { + $folder = ''; + $extension = pathinfo($uploaded_file->getClientFilename(), PATHINFO_EXTENSION); + $file = sha1((string) $uploaded_file->getStream()) . '.' . $extension; + } + + try { + $tree->mediaFilesystem()->writeStream($folder . $file, $uploaded_file->getStream()->detach()); + + return $folder . $file; + } catch (RuntimeException | InvalidArgumentException $ex) { + FlashMessages::addMessage(I18N::translate('There was an error uploading your file.')); + + return ''; + } + } + } + + /** + * Convert the media file attributes into GEDCOM format. + * + * @param string $file + * @param string $type + * @param string $title + * + * @return string + */ + public function createMediaFileGedcom(string $file, string $type, string $title): string + { + if (preg_match('/\.([a-z0-9]+)/i', $file, $match)) { + $extension = strtolower($match[1]); + $extension = str_replace('jpg', 'jpeg', $extension); + $extension = ' ' . $extension; + } else { + $extension = ''; + } + + $gedcom = '1 FILE ' . $file; + if ($type !== '') { + $gedcom .= "\n2 FORM" . $extension . "\n3 TYPE " . $type; + } + if ($title !== '') { + $gedcom .= "\n2 TITL " . $title; + } + + return $gedcom; + } +} |
