diff --git a/app/Factories/ImageFactory.php b/app/Factories/ImageFactory.php index 0fef92eb790..5dda4f8b6b0 100644 --- a/app/Factories/ImageFactory.php +++ b/app/Factories/ImageFactory.php @@ -34,6 +34,7 @@ use Intervention\Image\ImageManager; use League\Flysystem\FilesystemException; use League\Flysystem\FilesystemOperator; +use League\Flysystem\UnableToReadFile; use Psr\Http\Message\ResponseInterface; use RuntimeException; use Throwable; @@ -41,6 +42,7 @@ use function addcslashes; use function basename; use function extension_loaded; +use function get_class; use function pathinfo; use function response; use function strlen; @@ -132,11 +134,11 @@ public function thumbnailResponse( return $this->imageResponse($data, $image->mime(), ''); } catch (NotReadableException $ex) { return $this->replacementImageResponse('.' . pathinfo($path, PATHINFO_EXTENSION)); - } catch (FileNotFoundException $ex) { + } catch (UnableToReadFile $ex) { return $this->replacementImageResponse((string) StatusCodeInterface::STATUS_NOT_FOUND); } catch (Throwable $ex) { return $this->replacementImageResponse((string) StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR) - ->withHeader('X-Thumbnail-Exception', $ex->getMessage()); + ->withHeader('X-Thumbnail-Exception', basename(get_class($ex)) . ': ' . $ex->getMessage()); } } @@ -176,7 +178,7 @@ public function mediaFileResponse(MediaFile $media_file, bool $add_watermark, bo } catch (NotReadableException $ex) { return $this->replacementImageResponse(pathinfo($filename, PATHINFO_EXTENSION)) ->withHeader('X-Image-Exception', $ex->getMessage()); - } catch (FileNotFoundException $ex) { + } catch (UnableToReadFile $ex) { return $this->replacementImageResponse((string) StatusCodeInterface::STATUS_NOT_FOUND); } catch (Throwable $ex) { return $this->replacementImageResponse((string) StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR) @@ -244,11 +246,11 @@ public function mediaFileThumbnailResponse( return $this->imageResponse($data, $mime_type, ''); } catch (NotReadableException $ex) { return $this->replacementImageResponse('.' . pathinfo($path, PATHINFO_EXTENSION)); - } catch (FileNotFoundException $ex) { + } catch (UnableToReadFile $ex) { return $this->replacementImageResponse((string) StatusCodeInterface::STATUS_NOT_FOUND); } catch (Throwable $ex) { return $this->replacementImageResponse((string) StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR) - ->withHeader('X-Thumbnail-Exception', $ex->getMessage()); + ->withHeader('X-Thumbnail-Exception', basename(get_class($ex)) . ': ' . $ex->getMessage()); } } diff --git a/app/Http/RequestHandlers/EditMediaFileAction.php b/app/Http/RequestHandlers/EditMediaFileAction.php index e40556d72ea..c52924e52e6 100644 --- a/app/Http/RequestHandlers/EditMediaFileAction.php +++ b/app/Http/RequestHandlers/EditMediaFileAction.php @@ -27,8 +27,7 @@ use Fisharebest\Webtrees\Services\MediaFileService; use Fisharebest\Webtrees\Services\PendingChangesService; use Fisharebest\Webtrees\Tree; -use League\Flysystem\FileExistsException; -use League\Flysystem\FileNotFoundException; +use League\Flysystem\UnableToReadFile; use League\Flysystem\FilesystemException; use League\Flysystem\UnableToMoveFile; use League\Flysystem\Util; diff --git a/app/Http/RequestHandlers/ExportGedcomClient.php b/app/Http/RequestHandlers/ExportGedcomClient.php index 7260ced53fd..595fcc9fa4b 100644 --- a/app/Http/RequestHandlers/ExportGedcomClient.php +++ b/app/Http/RequestHandlers/ExportGedcomClient.php @@ -27,6 +27,7 @@ use Fisharebest\Webtrees\Tree; use Illuminate\Database\Capsule\Manager as DB; use League\Flysystem\Filesystem; +use League\Flysystem\ZipArchive\FilesystemZipArchiveProvider; use League\Flysystem\ZipArchive\ZipArchiveAdapter; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; @@ -121,7 +122,8 @@ public function handle(ServerRequestInterface $request): ResponseInterface // Create a new/empty .ZIP file $temp_zip_file = stream_get_meta_data(tmpfile())['uri']; - $zip_adapter = new ZipArchiveAdapter($temp_zip_file); + $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, $tmp_stream); fclose($tmp_stream); @@ -146,9 +148,6 @@ public function handle(ServerRequestInterface $request): ResponseInterface } } - // Need to force-close ZipArchive filesystems. - $zip_adapter->getArchive()->close(); - // Use a stream, so that we do not have to load the entire file into memory. $stream_factory = app(StreamFactoryInterface::class); assert($stream_factory instanceof StreamFactoryInterface); diff --git a/app/Http/RequestHandlers/GedcomLoad.php b/app/Http/RequestHandlers/GedcomLoad.php index 24bbc29d429..c096398146c 100644 --- a/app/Http/RequestHandlers/GedcomLoad.php +++ b/app/Http/RequestHandlers/GedcomLoad.php @@ -257,7 +257,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface DB::connection()->rollBack(); // Deadlock? Try again. - if ($this->causedByDeadlock($ex)) { + if ($this->causedByConcurrencyError($ex)) { return $this->viewResponse('admin/import-progress', [ 'errors' => '', 'progress' => $progress ?? 0.0, diff --git a/app/Http/RequestHandlers/ManageMediaData.php b/app/Http/RequestHandlers/ManageMediaData.php index edacbb208b7..2f25eefc371 100644 --- a/app/Http/RequestHandlers/ManageMediaData.php +++ b/app/Http/RequestHandlers/ManageMediaData.php @@ -32,6 +32,7 @@ use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\JoinClause; use League\Flysystem\FilesystemOperator; +use League\Flysystem\UnableToReadFile; use League\Flysystem\UnableToRetrieveMetadata; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -128,7 +129,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $url = route(AdminMediaFileDownload::class, ['path' => $path]); $img = '' . $img . ''; - } catch (FileNotFoundException $ex) { + } catch (UnableToReadFile $ex) { $url = route(AdminMediaFileThumbnail::class, ['path' => $path]); $img = ''; } diff --git a/app/Http/RequestHandlers/UploadMediaAction.php b/app/Http/RequestHandlers/UploadMediaAction.php index bcd41b94ee0..62b9f32c07f 100644 --- a/app/Http/RequestHandlers/UploadMediaAction.php +++ b/app/Http/RequestHandlers/UploadMediaAction.php @@ -116,7 +116,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $path = $folder . $filename; - if ($data_filesystem->has($path)) { + if ($data_filesystem->fileExists($path)) { FlashMessages::addMessage(I18N::translate('The file %s already exists. Use another filename.', $path, 'error')); continue; } diff --git a/app/Module/ClippingsCartModule.php b/app/Module/ClippingsCartModule.php index ece0452ada1..b450fe4f303 100644 --- a/app/Module/ClippingsCartModule.php +++ b/app/Module/ClippingsCartModule.php @@ -48,6 +48,7 @@ use Fisharebest\Webtrees\Tree; use Illuminate\Support\Collection; use League\Flysystem\Filesystem; +use League\Flysystem\ZipArchive\FilesystemZipArchiveProvider; use League\Flysystem\ZipArchive\ZipArchiveAdapter; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; @@ -279,7 +280,8 @@ public function postDownloadAction(ServerRequestInterface $request): ResponseInt // Create a new/empty .ZIP file $temp_zip_file = stream_get_meta_data(tmpfile())['uri']; - $zip_adapter = new ZipArchiveAdapter($temp_zip_file); + $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); @@ -375,9 +377,6 @@ public function postDownloadAction(ServerRequestInterface $request): ResponseInt // Finally add the GEDCOM file to the .ZIP file. $zip_filesystem->writeStream('clippings.ged', $stream); - // Need to force-close ZipArchive filesystems. - $zip_adapter->getArchive()->close(); - // Use a stream, so that we do not have to load the entire file into memory. $stream = app(StreamFactoryInterface::class)->createStreamFromFile($temp_zip_file); diff --git a/app/Services/HousekeepingService.php b/app/Services/HousekeepingService.php index 598e887c3c5..957d21a84cb 100644 --- a/app/Services/HousekeepingService.php +++ b/app/Services/HousekeepingService.php @@ -23,7 +23,10 @@ use Fisharebest\Webtrees\Carbon; use Illuminate\Database\Capsule\Manager as DB; use League\Flysystem\Filesystem; +use League\Flysystem\FilesystemException; use League\Flysystem\FilesystemOperator; +use League\Flysystem\UnableToDeleteDirectory; +use League\Flysystem\UnableToDeleteFile; /** * Clean up old data, files and folders. @@ -461,17 +464,13 @@ private function deleteFileOrFolder(FilesystemOperator $filesystem, string $path { if ($filesystem->fileExists($path)) { try { - $metadata = $filesystem->getMetadata($path); - - if ($metadata['type'] === 'dir') { + $filesystem->delete($path); + } catch (FilesystemException | UnableToDeleteFile $ex) { + try { $filesystem->deleteDirectory($path); + } catch (FilesystemException | UnableToDeleteDirectory $ex) { + return false; } - - if ($metadata['type'] === 'file') { - $filesystem->delete($path); - } - } catch (Exception $ex) { - return false; } } diff --git a/app/Services/ServerCheckService.php b/app/Services/ServerCheckService.php index c46f7aa3bcd..07c8a8cf191 100644 --- a/app/Services/ServerCheckService.php +++ b/app/Services/ServerCheckService.php @@ -51,10 +51,9 @@ class ServerCheckService private const PHP_SUPPORT_URL = 'https://www.php.net/supported-versions.php'; private const PHP_MINOR_VERSION = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; private const PHP_SUPPORT_DATES = [ - '7.1' => '2019-12-01', - '7.2' => '2020-11-30', '7.3' => '2021-12-06', '7.4' => '2022-11-28', + '8.0' => '2023-11-26', ]; // As required by illuminate/database 5.8 diff --git a/app/Webtrees.php b/app/Webtrees.php index d94f4b67774..3613d5badd2 100644 --- a/app/Webtrees.php +++ b/app/Webtrees.php @@ -99,7 +99,7 @@ class Webtrees public const STABILITY = '-dev'; // Version number - public const VERSION = '2.0.13' . self::STABILITY; + public const VERSION = '2.1.0' . self::STABILITY; // Project website. public const URL = 'https://webtrees.net/';