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/';