diff options
| -rw-r--r-- | app/Http/RequestHandlers/CheckTree.php | 4 | ||||
| -rw-r--r-- | app/Http/RequestHandlers/ExportGedcomClient.php | 109 | ||||
| -rw-r--r-- | app/Http/RequestHandlers/ExportGedcomPage.php | 22 | ||||
| -rw-r--r-- | app/Module/ClippingsCartModule.php | 111 | ||||
| -rw-r--r-- | app/Services/GedcomExportService.php | 145 | ||||
| -rw-r--r-- | phpstan-baseline.neon | 24 | ||||
| -rw-r--r-- | resources/views/admin/gedcom-export-options.phtml | 53 | ||||
| -rw-r--r-- | resources/views/admin/trees-export.phtml | 31 | ||||
| -rw-r--r-- | resources/views/modules/clippings/download.phtml | 10 | ||||
| -rw-r--r-- | tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php | 23 | ||||
| -rw-r--r-- | tests/app/TreeTest.php | 3 |
11 files changed, 265 insertions, 270 deletions
diff --git a/app/Http/RequestHandlers/CheckTree.php b/app/Http/RequestHandlers/CheckTree.php index 2db2af8cdb..1cd7167dfc 100644 --- a/app/Http/RequestHandlers/CheckTree.php +++ b/app/Http/RequestHandlers/CheckTree.php @@ -20,6 +20,7 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Http\RequestHandlers; use Fisharebest\Webtrees\Elements\AbstractXrefElement; +use Fisharebest\Webtrees\Elements\MultimediaFileReference; use Fisharebest\Webtrees\Elements\MultimediaFormat; use Fisharebest\Webtrees\Elements\SubmitterText; use Fisharebest\Webtrees\Elements\UnknownElement; @@ -296,6 +297,9 @@ class CheckTree implements RequestHandlerInterface $message = I18N::translate('webtrees cannot create thumbnails for this file format.'); $warnings[] = $this->lineError($tree, $record->type, $record->xref, $line_number, $line, $message); } + } elseif ($element instanceof MultimediaFileReference && $value === 'gedcom.ged') { + $message = I18N::translate('This filename is not compatible with the GEDZIP file format.'); + $errors[] = $this->lineError($tree, $record->type, $record->xref, $line_number, $line, $message); } } diff --git a/app/Http/RequestHandlers/ExportGedcomClient.php b/app/Http/RequestHandlers/ExportGedcomClient.php index 79b870b4f0..0980406c4f 100644 --- a/app/Http/RequestHandlers/ExportGedcomClient.php +++ b/app/Http/RequestHandlers/ExportGedcomClient.php @@ -19,36 +19,19 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Http\RequestHandlers; -use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Encodings\ANSEL; use Fisharebest\Webtrees\Encodings\ASCII; use Fisharebest\Webtrees\Encodings\UTF16BE; use Fisharebest\Webtrees\Encodings\UTF8; use Fisharebest\Webtrees\Encodings\Windows1252; -use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\Http\ViewResponseTrait; -use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Services\GedcomExportService; use Fisharebest\Webtrees\Validator; -use Illuminate\Database\Capsule\Manager as DB; -use League\Flysystem\Filesystem; use League\Flysystem\FilesystemException; -use League\Flysystem\ZipArchive\FilesystemZipArchiveProvider; -use League\Flysystem\ZipArchive\ZipArchiveAdapter; -use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\RequestHandlerInterface; -use function addcslashes; -use function fclose; -use function pathinfo; -use function strtolower; -use function tmpfile; - -use const PATHINFO_EXTENSION; - /** * Download a GEDCOM file to the client. */ @@ -58,108 +41,30 @@ class ExportGedcomClient implements RequestHandlerInterface private GedcomExportService $gedcom_export_service; - private ResponseFactoryInterface $response_factory; - - private StreamFactoryInterface $stream_factory; - /** * ExportGedcomServer constructor. * - * @param GedcomExportService $gedcom_export_service - * @param ResponseFactoryInterface $response_factory - * @param StreamFactoryInterface $stream_factory + * @param GedcomExportService $gedcom_export_service */ - public function __construct( - GedcomExportService $gedcom_export_service, - ResponseFactoryInterface $response_factory, - StreamFactoryInterface $stream_factory - ) { + public function __construct(GedcomExportService $gedcom_export_service) + { $this->gedcom_export_service = $gedcom_export_service; - $this->response_factory = $response_factory; - $this->stream_factory = $stream_factory; } /** * @param ServerRequestInterface $request * * @return ResponseInterface - * @throws FilesystemException */ public function handle(ServerRequestInterface $request): ResponseInterface { - $tree = Validator::attributes($request)->tree(); - - $data_filesystem = Registry::filesystem()->data(); - - $format = Validator::parsedBody($request)->isInArray(['gedcom', 'zip'])->string('format'); + $tree = Validator::attributes($request)->tree(); + $filename = Validator::parsedBody($request)->string('filename'); + $format = Validator::parsedBody($request)->isInArray(['gedcom', 'zip', 'zipmedia', 'gedzip'])->string('format'); $privacy = Validator::parsedBody($request)->isInArray(['none', 'gedadmin', 'user', 'visitor'])->string('privacy'); $encoding = Validator::parsedBody($request)->isInArray([UTF8::NAME, UTF16BE::NAME, ANSEL::NAME, ASCII::NAME, Windows1252::NAME])->string('encoding'); $line_endings = Validator::parsedBody($request)->isInArray(['CRLF', 'LF'])->string('line_endings'); - $media_path = Validator::parsedBody($request)->string('media_path', ''); - - $access_levels = [ - 'gedadmin' => Auth::PRIV_NONE, - 'user' => Auth::PRIV_USER, - 'visitor' => Auth::PRIV_PRIVATE, - 'none' => Auth::PRIV_HIDE, - ]; - - $access_level = $access_levels[$privacy]; - - // What to call the downloaded file - $download_filename = $tree->name(); - - // Force a ".ged" suffix - if (strtolower(pathinfo($download_filename, PATHINFO_EXTENSION)) !== 'ged') { - $download_filename .= '.ged'; - } - - if ($format === 'zip') { - $resource = $this->gedcom_export_service->export($tree, true, $encoding, $access_level, $media_path, $line_endings); - - $path = $tree->getPreference('MEDIA_DIRECTORY'); - - // Create a new/empty .ZIP file - $temp_zip_file = stream_get_meta_data(tmpfile())['uri']; - $zip_provider = new FilesystemZipArchiveProvider($temp_zip_file, 0755); - $zip_adapter = new ZipArchiveAdapter($zip_provider); - $zip_filesystem = new Filesystem($zip_adapter); - $zip_filesystem->writeStream($download_filename, $resource); - fclose($resource); - - $media_filesystem = $tree->mediaFilesystem($data_filesystem); - - $records = DB::table('media') - ->where('m_file', '=', $tree->id()) - ->get() - ->map(Registry::mediaFactory()->mapper($tree)) - ->filter(GedcomRecord::accessFilter()); - - foreach ($records as $record) { - foreach ($record->mediaFiles() as $media_file) { - $from = $media_file->filename(); - $to = $path . $media_file->filename(); - if (!$media_file->isExternal() && $media_filesystem->fileExists($from) && !$zip_filesystem->fileExists($to)) { - $zip_filesystem->writeStream($to, $media_filesystem->readStream($from)); - } - } - } - - $stream = $this->stream_factory->createStreamFromFile($temp_zip_file); - $filename = addcslashes($download_filename, '"') . '.zip'; - - return $this->response_factory->createResponse() - ->withBody($stream) - ->withHeader('content-type', 'application/zip') - ->withHeader('content-disposition', 'attachment; filename="' . $filename . '"'); - } - - $resource = $this->gedcom_export_service->export($tree, true, $encoding, $access_level, $media_path); - $stream = $this->stream_factory->createStreamFromResource($resource); - return $this->response_factory->createResponse() - ->withBody($stream) - ->withHeader('content-type', 'text/x-gedcom; charset=' . UTF8::NAME) - ->withHeader('content-disposition', 'attachment; filename="' . addcslashes($download_filename, '"') . '"'); + return $this->gedcom_export_service->downloadResponse($tree, true, $encoding, $privacy, $line_endings, $filename, $format); } } diff --git a/app/Http/RequestHandlers/ExportGedcomPage.php b/app/Http/RequestHandlers/ExportGedcomPage.php index 73cebda5aa..0d75560ce7 100644 --- a/app/Http/RequestHandlers/ExportGedcomPage.php +++ b/app/Http/RequestHandlers/ExportGedcomPage.php @@ -26,9 +26,12 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; +use function date; use function e; +use function extension_loaded; use function pathinfo; use function strtolower; +use function substr; use const PATHINFO_EXTENSION; @@ -54,14 +57,23 @@ class ExportGedcomPage implements RequestHandlerInterface $filename = $tree->name(); // Force a ".ged" suffix - if (strtolower(pathinfo($filename, PATHINFO_EXTENSION)) !== 'ged') { - $filename .= '.ged'; + if (strtolower(pathinfo($filename, PATHINFO_EXTENSION)) === 'ged') { + $download_filename = substr($filename, 0, -4); + } else { + $download_filename = $filename; } + $download_filenames = [ + $download_filename => $download_filename, + $download_filename . date('-Y-m-d') => $download_filename . date('-Y-m-d'), + ]; + return $this->viewResponse('admin/trees-export', [ - 'filename' => $filename, - 'title' => $title, - 'tree' => $tree, + 'download_filenames' => $download_filenames, + 'filename' => $filename, + 'title' => $title, + 'tree' => $tree, + 'zip_available' => extension_loaded('zip'), ]); } } diff --git a/app/Module/ClippingsCartModule.php b/app/Module/ClippingsCartModule.php index a6acc75ad1..db08e13b51 100644 --- a/app/Module/ClippingsCartModule.php +++ b/app/Module/ClippingsCartModule.php @@ -52,14 +52,9 @@ use Fisharebest\Webtrees\Submitter; use Fisharebest\Webtrees\Tree; use Fisharebest\Webtrees\Validator; use Illuminate\Support\Collection; -use League\Flysystem\Filesystem; use League\Flysystem\FilesystemException; -use League\Flysystem\ZipArchive\FilesystemZipArchiveProvider; -use League\Flysystem\ZipArchive\ZipArchiveAdapter; -use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamFactoryInterface; use function app; use function array_filter; @@ -67,7 +62,8 @@ use function array_keys; use function array_map; use function array_search; use function assert; -use function fclose; +use function date; +use function extension_loaded; use function in_array; use function is_array; use function is_string; @@ -75,8 +71,6 @@ use function preg_match_all; use function redirect; use function route; use function str_replace; -use function stream_get_meta_data; -use function tmpfile; use function uasort; use function view; @@ -118,28 +112,18 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface private LinkedRecordService $linked_record_service; - private ResponseFactoryInterface $response_factory; - - private StreamFactoryInterface $stream_factory; - /** * ClippingsCartModule constructor. * - * @param GedcomExportService $gedcom_export_service - * @param LinkedRecordService $linked_record_service - * @param ResponseFactoryInterface $response_factory - * @param StreamFactoryInterface $stream_factory + * @param GedcomExportService $gedcom_export_service + * @param LinkedRecordService $linked_record_service */ public function __construct( GedcomExportService $gedcom_export_service, - LinkedRecordService $linked_record_service, - ResponseFactoryInterface $response_factory, - StreamFactoryInterface $stream_factory + LinkedRecordService $linked_record_service ) { $this->gedcom_export_service = $gedcom_export_service; $this->linked_record_service = $linked_record_service; - $this->response_factory = $response_factory; - $this->stream_factory = $stream_factory; } /** @@ -257,10 +241,17 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface $title = I18N::translate('Family tree clippings cart') . ' — ' . I18N::translate('Download'); + $download_filenames = [ + 'clippings' => 'clippings', + 'clippings-' . date('Y-m-d') => 'clippings-' . date('Y-m-d'), + ]; + return $this->viewResponse('modules/clippings/download', [ - 'module' => $this->name(), - 'title' => $title, - 'tree' => $tree, + 'download_filenames' => $download_filenames, + 'module' => $this->name(), + 'title' => $title, + 'tree' => $tree, + 'zip_available' => extension_loaded('zip'), ]); } @@ -274,25 +265,22 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface { $tree = Validator::attributes($request)->tree(); - $data_filesystem = Registry::filesystem()->data(); + if (Auth::isAdmin()) { + $privacy_options = ['none', 'gedadmin', 'user', 'visitor']; + } elseif (Auth::isManager($tree)) { + $privacy_options = ['gedadmin', 'user', 'visitor']; + } elseif (Auth::isMember($tree)) { + $privacy_options = ['user', 'visitor']; + } else { + $privacy_options = ['visitor']; + } - $format = Validator::parsedBody($request)->isInArray(['gedcom', 'zip'])->string('format'); - $privacy = Validator::parsedBody($request)->isInArray(['none', 'gedadmin', 'user', 'visitor'])->string('privacy'); + $filename = Validator::parsedBody($request)->string('filename'); + $format = Validator::parsedBody($request)->isInArray(['gedcom', 'zip', 'zipmedia', 'gedzip'])->string('format'); + $privacy = Validator::parsedBody($request)->isInArray($privacy_options)->string('privacy'); $encoding = Validator::parsedBody($request)->isInArray([UTF8::NAME, UTF16BE::NAME, ANSEL::NAME, ASCII::NAME, Windows1252::NAME])->string('encoding'); $line_endings = Validator::parsedBody($request)->isInArray(['CRLF', 'LF'])->string('line_endings'); - if ($privacy === 'none' && !Auth::isManager($tree)) { - $privacy = 'member'; - } - - if ($privacy === 'gedadmin' && !Auth::isManager($tree)) { - $privacy = 'member'; - } - - if ($privacy === 'user' && !Auth::isMember($tree)) { - $privacy = 'visitor'; - } - $cart = Session::get('cart'); $cart = is_array($cart) ? $cart : []; @@ -346,51 +334,8 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface } } - // Media file prefix - $path = $tree->getPreference('MEDIA_DIRECTORY'); - // We have already applied privacy filtering, so do not do it again. - $resource = $this->gedcom_export_service->export($tree, false, $encoding, Auth::PRIV_HIDE, $path, $line_endings, $records); - - if ($format === 'gedcom') { - return $this->response_factory->createResponse() - ->withBody($this->stream_factory->createStreamFromResource($resource)) - ->withHeader('content-type', 'text/x-gedcom; charset=' . $encoding) - ->withHeader('content-disposition', 'attachment; filename="clippings.ged'); - } - - // Create a new/empty .ZIP file - $temp_zip_file = stream_get_meta_data(tmpfile())['uri']; - $zip_provider = new FilesystemZipArchiveProvider($temp_zip_file, 0755); - $zip_adapter = new ZipArchiveAdapter($zip_provider); - $zip_filesystem = new Filesystem($zip_adapter); - - $media_filesystem = $tree->mediaFilesystem($data_filesystem); - - foreach ($records as $record) { - if ($record instanceof Media) { - // Add the media files to the archive - foreach ($record->mediaFiles() as $media_file) { - $from = $media_file->filename(); - $to = $path . $media_file->filename(); - if (!$media_file->isExternal() && $media_filesystem->fileExists($from)) { - $zip_filesystem->writeStream($to, $media_filesystem->readStream($from)); - } - } - } - } - - // Finally, add the GEDCOM file to the .ZIP file. - $zip_filesystem->writeStream('clippings.ged', $resource); - fclose($resource); - - // Use a stream, so that we do not have to load the entire file into memory. - $resource = $this->stream_factory->createStreamFromFile($temp_zip_file); - - return $this->response_factory->createResponse() - ->withBody($resource) - ->withHeader('content-type', 'application/zip') - ->withHeader('content-disposition', 'attachment; filename="clippings.zip'); + return $this->gedcom_export_service->downloadResponse($tree, false, $encoding, 'none', $line_endings, $filename, $format, $records); } /** diff --git a/app/Services/GedcomExportService.php b/app/Services/GedcomExportService.php index 859988fb2c..8aeaedace0 100644 --- a/app/Services/GedcomExportService.php +++ b/app/Services/GedcomExportService.php @@ -36,23 +36,35 @@ use Illuminate\Database\Capsule\Manager as DB; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Expression; use Illuminate\Support\Collection; +use League\Flysystem\Filesystem; +use League\Flysystem\FilesystemOperator; +use League\Flysystem\ZipArchive\FilesystemZipArchiveProvider; +use League\Flysystem\ZipArchive\ZipArchiveAdapter; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; use RuntimeException; +use function addcslashes; use function date; use function explode; +use function fclose; use function fopen; use function fwrite; use function pathinfo; +use function preg_match_all; use function rewind; use function str_contains; -use function str_starts_with; use function stream_filter_append; +use function stream_get_meta_data; use function strlen; use function strpos; use function strtolower; use function strtoupper; +use function tmpfile; use const PATHINFO_EXTENSION; +use const PREG_SET_ORDER; use const STREAM_FILTER_WRITE; /** @@ -60,16 +72,105 @@ use const STREAM_FILTER_WRITE; */ class GedcomExportService { + private const ACCESS_LEVELS = [ + 'gedadmin' => Auth::PRIV_NONE, + 'user' => Auth::PRIV_USER, + 'visitor' => Auth::PRIV_PRIVATE, + 'none' => Auth::PRIV_HIDE, + ]; + + private ResponseFactoryInterface $response_factory; + + private StreamFactoryInterface $stream_factory; + + /** + * @param ResponseFactoryInterface $response_factory + * @param StreamFactoryInterface $stream_factory + */ + public function __construct(ResponseFactoryInterface $response_factory, StreamFactoryInterface $stream_factory) + { + $this->response_factory = $response_factory; + $this->stream_factory = $stream_factory; + } + + /** + * @param Tree $tree - Export data from this tree + * @param bool $sort_by_xref - Write GEDCOM records in XREF order + * @param string $encoding - Convert from UTF-8 to other encoding + * @param string $privacy - Filter records by role + * @param string $filename - Name of download file, without an extension + * @param string $format - One of: gedcom, zip, zipmedia, gedzip + * + * @return ResponseInterface + */ + public function downloadResponse( + Tree $tree, + bool $sort_by_xref, + string $encoding, + string $privacy, + string $line_endings, + string $filename, + string $format, + Collection $records = null + ): ResponseInterface { + $access_level = self::ACCESS_LEVELS[$privacy]; + + if ($format === 'gedcom') { + $resource = $this->export($tree, $sort_by_xref, $encoding, $access_level, $line_endings, $records); + $stream = $this->stream_factory->createStreamFromResource($resource); + + return $this->response_factory->createResponse() + ->withBody($stream) + ->withHeader('content-type', 'text/x-gedcom; charset=' . UTF8::NAME) + ->withHeader('content-disposition', 'attachment; filename="' . addcslashes($filename, '"') . '.ged"'); + } + + // Create a new/empty .ZIP file + $temp_zip_file = stream_get_meta_data(tmpfile())['uri']; + $zip_provider = new FilesystemZipArchiveProvider($temp_zip_file, 0755); + $zip_adapter = new ZipArchiveAdapter($zip_provider); + $zip_filesystem = new Filesystem($zip_adapter); + + if ($format === 'zipmedia') { + $media_path = $tree->getPreference('MEDIA_DIRECTORY'); + } elseif ($format === 'gedzip') { + $media_path = ''; + } else { + // Don't add media + $media_path = null; + } + + $resource = $this->export($tree, $sort_by_xref, $encoding, $access_level, $line_endings, $records, $zip_filesystem, $media_path); + + if ($format === 'gedzip') { + $zip_filesystem->writeStream('gedcom.ged', $resource); + $extension = '.gdz'; + } else { + $zip_filesystem->writeStream($filename . '.ged', $resource); + $extension = '.zip'; + } + + fclose($resource); + + $stream = $this->stream_factory->createStreamFromFile($temp_zip_file); + + return $this->response_factory->createResponse() + ->withBody($stream) + ->withHeader('content-type', 'application/zip') + ->withHeader('content-disposition', 'attachment; filename="' . addcslashes($filename, '"') . $extension . '"'); + } + /** * Write GEDCOM data to a stream. * - * @param Tree $tree - Export data from this tree - * @param bool $sort_by_xref - Write GEDCOM records in XREF order - * @param string $encoding - Convert from UTF-8 to other encoding - * @param int $access_level - Apply privacy filtering - * @param string $media_path - Prepend path to media filenames - * @param string $line_endings - CRLF or LF - * @param Collection<int,string>|null $records - Just export these records + * @param Tree $tree - Export data from this tree + * @param bool $sort_by_xref - Write GEDCOM records in XREF order + * @param string $encoding - Convert from UTF-8 to other encoding + * @param int $access_level - Apply privacy filtering + * @param string $line_endings - CRLF or LF + * @param Collection<int,string>|null $records - Just export these records + * @param FilesystemOperator|null $zip_filesystem - Write media files to this filesystem + * @param string|null $media_path - Location within the zip filesystem * * @return resource */ @@ -78,9 +179,10 @@ class GedcomExportService bool $sort_by_xref = false, string $encoding = UTF8::NAME, int $access_level = Auth::PRIV_HIDE, - string $media_path = '', string $line_endings = 'CRLF', - Collection $records = null + Collection $records = null, + FilesystemOperator $zip_filesystem = null, + string $media_path = null ) { $stream = fopen('php://memory', 'wb+'); @@ -125,6 +227,8 @@ class GedcomExportService ]; } + $media_filesystem = Registry::filesystem()->media($tree); + foreach ($data as $rows) { foreach ($rows as $datum) { if (is_string($datum)) { @@ -140,8 +244,16 @@ class GedcomExportService $datum->o_gedcom; } - if ($media_path !== '') { - $gedcom = $this->convertMediaPath($gedcom, $media_path); + if ($media_path !== null && $zip_filesystem !== null && preg_match('/0 @' . Gedcom::REGEX_XREF . '@ OBJE/', $gedcom) === 1) { + preg_match_all('/\n1 FILE (.+)/', $gedcom, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + $media_file = $match[1]; + + if ($media_filesystem->fileExists($media_file)) { + $zip_filesystem->writeStream($media_path . $media_file, $media_filesystem->readStream($media_file)); + } + } } $gedcom = $this->wrapLongLines($gedcom, Gedcom::LINE_LENGTH) . "\n"; @@ -235,14 +347,7 @@ class GedcomExportService // Don’t modify external links if (!str_contains($filename, '://')) { - // Convert separators to match new path. - if (str_contains($media_path, '\\')) { - $filename = strtr($filename, ['/' => '\\']); - } - - if (!str_starts_with($filename, $media_path)) { - $filename = $media_path . $filename; - } + $filename = $media_path . $filename; } return "\n1 FILE " . $filename; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0f76568697..53e39fdb07 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -512,12 +512,12 @@ parameters: - message: "#^Cannot access property \\$type on mixed\\.$#" - count: 46 + count: 47 path: app/Http/RequestHandlers/CheckTree.php - message: "#^Cannot access property \\$xref on mixed\\.$#" - count: 45 + count: 46 path: app/Http/RequestHandlers/CheckTree.php - @@ -591,16 +591,6 @@ parameters: path: app/Http/RequestHandlers/EditNoteAction.php - - message: "#^Cannot call method mediaFiles\\(\\) on mixed\\.$#" - count: 1 - path: app/Http/RequestHandlers/ExportGedcomClient.php - - - - message: "#^Parameter \\#1 \\$fp of function stream_get_meta_data expects resource, resource\\|false given\\.$#" - count: 1 - path: app/Http/RequestHandlers/ExportGedcomClient.php - - - message: "#^Parameter \\#1 \\$content of function response expects array\\|object\\|string, string\\|false given\\.$#" count: 1 path: app/Http/RequestHandlers/FaviconIco.php @@ -1236,11 +1226,6 @@ parameters: path: app/Module/CensusAssistantModule.php - - message: "#^Parameter \\#1 \\$fp of function stream_get_meta_data expects resource, resource\\|false given\\.$#" - count: 1 - path: app/Module/ClippingsCartModule.php - - - message: "#^Argument of an invalid type array\\<int, string\\>\\|false supplied for foreach, only iterables are supported\\.$#" count: 1 path: app/Module/CloudsTheme.php @@ -2571,6 +2556,11 @@ parameters: path: app/Services/GedcomExportService.php - + message: "#^Parameter \\#1 \\$fp of function stream_get_meta_data expects resource, resource\\|false given\\.$#" + count: 1 + path: app/Services/GedcomExportService.php + + - message: "#^Method Fisharebest\\\\Webtrees\\\\Services\\\\GedcomImportService\\:\\:createMediaObject\\(\\) should return string but returns mixed\\.$#" count: 1 path: app/Services/GedcomImportService.php diff --git a/resources/views/admin/gedcom-export-options.phtml b/resources/views/admin/gedcom-export-options.phtml index 2ba1f8d77a..0cefd2aa55 100644 --- a/resources/views/admin/gedcom-export-options.phtml +++ b/resources/views/admin/gedcom-export-options.phtml @@ -7,9 +7,12 @@ use Fisharebest\Webtrees\Encodings\UTF16BE; use Fisharebest\Webtrees\Encodings\UTF8; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\View; /** - * @var Tree $tree + * @var array<string> $download_filenames + * @var Tree $tree + * @var bool $zip_available */ ?> @@ -21,7 +24,7 @@ use Fisharebest\Webtrees\Tree; <div class="col-sm-9"> <div class="form-check"> - <input class="form-check-input" type="radio" name="format" id="format-gedcom" value="gedcom" checked="checked"> + <input class="form-check-input" type="radio" name="format" id="format-gedcom" value="gedcom" checked="checked" data-wt-extension=".ged"> <label class="form-check-label" for="format-gedcom"> <?= I18N::translate('GEDCOM') ?> @@ -29,16 +32,47 @@ use Fisharebest\Webtrees\Tree; </div> <div class="form-check"> - <input class="form-check-input" type="radio" name="format" id="format-zip" value="zip"> + <input class="form-check-input" type="radio" name="format" id="format-zip" value="zip" data-wt-extension=".zip" <?= $zip_available ? '' : 'disabled="disabled"'?>> <label class="form-check-label" for="format-zip"> - <?= /* I18N: ZIP = file format */ I18N::translate('ZIP (includes media files)') ?> + <?= /* I18N: ZIP = file format */ I18N::translate('ZIP') ?> + </label> + </div> + + <div class="form-check"> + <input class="form-check-input" type="radio" name="format" id="format-zipmedia" value="zipmedia" data-wt-extension=".zip" <?= $zip_available ? '' : 'disabled="disabled"'?>> + + <label class="form-check-label" for="format-zipmedia"> + <?= /* I18N: ZIP = file format */ I18N::translate('ZIP') ?> + <?= I18N::translate('(includes media files)') ?> + </label> + </div> + + <div class="form-check"> + <input class="form-check-input" type="radio" name="format" id="format-gdz" value="gedzip" data-wt-extension=".gdz" <?= $zip_available ? '' : 'disabled="disabled"'?>> + + <label class="form-check-label" for="format-gdz"> + <?= /* I18N: GEDZIP = file format */ I18N::translate('GEDZIP') ?> + <?= I18N::translate('(includes media files)') ?> </label> </div> </div> </div> <div class="row mb-3"> + <label class="col-sm-3" for="filename"> + <?= I18N::translate('Filename') ?> + </label> + + <div class="col-sm-9"> + <div class="input-group"> + <?= view('components/select', ['name' => 'filename', 'id' => 'filename', 'selected' => 0, 'options' => $download_filenames]) ?> + <span class="input-group-text" id="extension">.ged</span> + </div> + </div> +</div> + +<div class="row mb-3"> <label class="col-sm-3" for="encoding"> <?= I18N::translate('Character encoding') ?> </label> @@ -76,3 +110,14 @@ use Fisharebest\Webtrees\Tree; </div> </div> +<?php View::push('javascript') ?> +<script> + 'use strict'; + + document.querySelectorAll('[name=format]').forEach(element => element.addEventListener('change', event => document.getElementById('extension').innerText = event.target.dataset.wtExtension)); + + // Firefox will reload the page and change the selected item. + document.getElementById('extension').innerText = document.querySelector('[name=format]:checked').dataset.wtExtension; + +</script> +<?php View::endpush(); ?> diff --git a/resources/views/admin/trees-export.phtml b/resources/views/admin/trees-export.phtml index 8fbc4e51a9..94515445ae 100644 --- a/resources/views/admin/trees-export.phtml +++ b/resources/views/admin/trees-export.phtml @@ -8,9 +8,11 @@ use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Tree; /** - * @var string $filename - * @var string $title - * @var Tree $tree + * @var array<string> $download_filenames + * @var string $filename + * @var string $title + * @var Tree $tree + * @var bool $zip_available */ ?> @@ -38,7 +40,7 @@ use Fisharebest\Webtrees\Tree; <button type="submit" class="btn btn-primary"> <?= view('icons/save') ?> <?= /* I18N: A button label. */ - I18N::translate('continue') ?> + I18N::translate('save') ?> </button> </form> </div> @@ -59,29 +61,12 @@ use Fisharebest\Webtrees\Tree; <?= I18N::translate('Export preferences') ?> </p> - <?= view('admin/gedcom-export-options', ['tree' => $tree]) ?> - - <?php if ($tree->getPreference('GEDCOM_MEDIA_PATH') !== '') : ?> - <div class="row mb-3"> - <label class="col-sm-3" for="media-path"> - <?= /* I18N: A media path (e.g. C:\aaa\bbb\ccc\) in a GEDCOM file */ I18N::translate('Add the GEDCOM media path to filenames') ?> - </label> - - <div class="col-sm-9"> - <div class="form-check"> - <input class="form-check-input" type="checkbox" id="media-path" name="media_path"> - <label class="form-check-label" for="media-path"> - <?= /* I18N: %s is the name of a folder. */ I18N::translate('Media filenames will be prefixed by %s.', '<code dir="ltr">' . e($tree->getPreference('GEDCOM_MEDIA_PATH')) . '</code>') ?> - </label> - </div> - </div> - </div> - <?php endif ?> + <?= view('admin/gedcom-export-options', ['download_filenames' =>$download_filenames, 'tree' => $tree, 'zip_available' => $zip_available]) ?> <button type="submit" class="btn btn-primary"> <?= view('icons/download') ?> <?= /* I18N: A button label. */ - I18N::translate('continue') ?> + I18N::translate('download') ?> </button> </form> </div> diff --git a/resources/views/modules/clippings/download.phtml b/resources/views/modules/clippings/download.phtml index e411ee2d56..695e11130b 100644 --- a/resources/views/modules/clippings/download.phtml +++ b/resources/views/modules/clippings/download.phtml @@ -4,9 +4,11 @@ use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Tree; /** - * @var string $module - * @var string $title - * @var Tree $tree + * @var array<string> $download_filenames + * @var string $module + * @var string $title + * @var Tree $tree + * @var bool $zip_available */ ?> @@ -21,7 +23,7 @@ use Fisharebest\Webtrees\Tree; <?= I18N::translate('Export preferences') ?> </div> <div class="col-sm-9 wt-page-options-value"> - <?= view('admin/gedcom-export-options', ['tree' => $tree]) ?> + <?= view('admin/gedcom-export-options', ['download_filenames' =>$download_filenames, 'tree' => $tree, 'zip_available' => $zip_available]) ?> </div> </div> diff --git a/tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php b/tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php index b8f7425699..059e4b4eb6 100644 --- a/tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php +++ b/tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php @@ -32,6 +32,7 @@ use Fisharebest\Webtrees\Services\UpgradeService; use Fisharebest\Webtrees\Services\UserService; use Fisharebest\Webtrees\TestCase; use Illuminate\Support\Collection; +use Nyholm\Psr7\Factory\Psr17Factory; /** * Test UpgradeController class. @@ -48,7 +49,7 @@ class UpgradeWizardStepTest extends TestCase public function testIgnoreStepInvalid(): void { $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), new UpgradeService(new TimeoutService()) ); @@ -68,7 +69,7 @@ class UpgradeWizardStepTest extends TestCase $mock_upgrade_service = $this->createMock(UpgradeService::class); $mock_upgrade_service->method('latestVersion')->willReturn('999.999.999'); $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), $mock_upgrade_service ); @@ -89,7 +90,7 @@ class UpgradeWizardStepTest extends TestCase $mock_upgrade_service = $this->createMock(UpgradeService::class); $mock_upgrade_service->method('latestVersion')->willReturn(''); $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), $mock_upgrade_service ); @@ -108,7 +109,7 @@ class UpgradeWizardStepTest extends TestCase $mock_upgrade_service = $this->createMock(UpgradeService::class); $mock_upgrade_service->method('latestVersion')->willReturn('0.0.0'); $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), $mock_upgrade_service ); @@ -123,7 +124,7 @@ class UpgradeWizardStepTest extends TestCase public function testStepPrepare(): void { $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), new UpgradeService(new TimeoutService()) ); @@ -140,7 +141,7 @@ class UpgradeWizardStepTest extends TestCase public function testStepPending(): void { $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), new UpgradeService(new TimeoutService()) ); @@ -164,7 +165,7 @@ class UpgradeWizardStepTest extends TestCase $tree->createIndividual("0 @@ INDI\n1 NAME Joe Bloggs"); $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), new UpgradeService(new TimeoutService()) ); @@ -186,7 +187,7 @@ class UpgradeWizardStepTest extends TestCase $tree_service->method('all')->willReturn($all_trees); $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), $tree_service, new UpgradeService(new TimeoutService()) ); @@ -212,7 +213,7 @@ class UpgradeWizardStepTest extends TestCase $mock_upgrade_service = $this->createMock(UpgradeService::class); $mock_upgrade_service->method('downloadFile')->will(self::throwException(new Exception())); $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), $mock_upgrade_service ); @@ -229,7 +230,7 @@ class UpgradeWizardStepTest extends TestCase $mock_upgrade_service = $this->createMock(UpgradeService::class); $mock_upgrade_service->method('downloadFile')->willReturn(123456); $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), $mock_upgrade_service ); @@ -248,7 +249,7 @@ class UpgradeWizardStepTest extends TestCase $mock_upgrade_service = $this->createMock(UpgradeService::class); $mock_upgrade_service->method('webtreesZipContents')->willReturn(new Collection()); $handler = new UpgradeWizardStep( - new GedcomExportService(), + new GedcomExportService(new Psr17Factory(), new Psr17Factory()), new TreeService(new GedcomImportService()), $mock_upgrade_service ); diff --git a/tests/app/TreeTest.php b/tests/app/TreeTest.php index 71d2143ca8..7d6dff01f2 100644 --- a/tests/app/TreeTest.php +++ b/tests/app/TreeTest.php @@ -26,6 +26,7 @@ use Fisharebest\Webtrees\Services\GedcomImportService; use Fisharebest\Webtrees\Services\TreeService; use Fisharebest\Webtrees\Services\UserService; use InvalidArgumentException; +use Nyholm\Psr7\Factory\Psr17Factory; use Symfony\Component\Cache\Adapter\NullAdapter; use function fclose; @@ -358,7 +359,7 @@ class TreeTest extends TestCase { $tree = $this->importTree('demo.ged'); - $gedcom_export_service = new GedcomExportService(); + $gedcom_export_service = new GedcomExportService(new Psr17Factory(), new Psr17Factory()); $resource = $gedcom_export_service->export($tree, true); $original = file_get_contents(__DIR__ . '/../data/demo.ged'); |
