From 18d2de7c2baaed4ef4c2a70b060727ccc966ab69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Viguier?= Date: Sat, 11 Jan 2025 12:26:46 +0100 Subject: [PATCH] Fix diagnostics not being complete on version 6 (#2892) --- app/Actions/Diagnostics/Errors.php | 5 +- .../Pipes/Checks/AdminUserExistsCheck.php | 3 +- .../Pipes/Checks/AppUrlMatchCheck.php | 58 +++++++-------- .../Pipes/Checks/BasicPermissionCheck.php | 37 +++++----- .../Pipes/Checks/ConfigSanityCheck.php | 41 ++--------- .../Pipes/Checks/CountSizeVariantsCheck.php | 10 ++- .../Pipes/Checks/DBIntegrityCheck.php | 3 +- .../Pipes/Checks/DBSupportCheck.php | 5 +- .../Pipes/Checks/ForeignKeyListInfo.php | 25 +++---- .../Pipes/Checks/GDSupportCheck.php | 9 +-- .../Pipes/Checks/ImageOptCheck.php | 5 +- .../Pipes/Checks/IniSettingsCheck.php | 39 +++++----- .../Pipes/Checks/MigrationCheck.php | 5 +- .../Pipes/Checks/PHPVersionCheck.php | 17 ++--- .../Pipes/Checks/PlaceholderExistsCheck.php | 15 ++-- .../Pipes/Checks/SmallMediumExistsCheck.php | 33 ++++++--- .../Pipes/Checks/SupporterCheck.php | 3 +- .../Pipes/Checks/TimezoneCheck.php | 7 +- .../Pipes/Checks/UpdatableCheck.php | 7 +- .../Pipes/Infos/CountForeignKeyInfo.php | 4 +- .../Pipes/Infos/ExtensionsInfo.php | 4 +- .../Pipes/Infos/InstallTypeInfo.php | 4 +- .../Diagnostics/Pipes/Infos/SystemInfo.php | 4 +- .../Diagnostics/Pipes/Infos/VersionInfo.php | 4 +- app/Console/Commands/Diagnostics.php | 28 +++++++- app/Contracts/DiagnosticPipe.php | 10 +-- app/Contracts/DiagnosticStringPipe.php | 26 +++++++ app/DTO/DiagnosticData.php | 72 +++++++++++++++++++ app/Enum/MessageType.php | 16 +++++ app/Http/Resources/Diagnostics/ErrorLine.php | 48 ++++++------- .../Administration/DiagnosticsController.php | 29 +++++++- .../diagnostics/ErrorsDiagnostics.vue | 37 ++++++++-- resources/js/lychee.d.ts | 7 +- resources/js/views/Diagnostics.vue | 15 ++-- 34 files changed, 412 insertions(+), 223 deletions(-) create mode 100644 app/Contracts/DiagnosticStringPipe.php create mode 100644 app/DTO/DiagnosticData.php create mode 100644 app/Enum/MessageType.php diff --git a/app/Actions/Diagnostics/Errors.php b/app/Actions/Diagnostics/Errors.php index d11f8968786..6a9a270cc79 100644 --- a/app/Actions/Diagnostics/Errors.php +++ b/app/Actions/Diagnostics/Errors.php @@ -20,6 +20,7 @@ use App\Actions\Diagnostics\Pipes\Checks\SupporterCheck; use App\Actions\Diagnostics\Pipes\Checks\TimezoneCheck; use App\Actions\Diagnostics\Pipes\Checks\UpdatableCheck; +use App\DTO\DiagnosticData; use Illuminate\Pipeline\Pipeline; class Errors @@ -55,14 +56,14 @@ class Errors * * @param string[] $skip class names of checks that will be skipped * - * @return string[] array of messages + * @return DiagnosticData[] array of messages */ public function get(array $skip = []): array { $filteredPipes = collect($this->pipes); $this->pipes = $filteredPipes->reject(fn ($p) => in_array((new \ReflectionClass($p))->getShortName(), $skip, true))->all(); - /** @var string[] $errors */ + /** @var DiagnosticData[] $errors */ $errors = []; return app(Pipeline::class) diff --git a/app/Actions/Diagnostics/Pipes/Checks/AdminUserExistsCheck.php b/app/Actions/Diagnostics/Pipes/Checks/AdminUserExistsCheck.php index 36ddce5a21a..fabe7dad9d6 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/AdminUserExistsCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/AdminUserExistsCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Models\User; use Illuminate\Support\Facades\Schema; @@ -22,7 +23,7 @@ public function handle(array &$data, \Closure $next): array $numberOfAdmin = User::query()->where('may_administrate', '=', true)->count(); if ($numberOfAdmin === 0) { // @codeCoverageIgnoreStart - $data[] = 'Error: User Admin not found in database. Please run: "php lychee:create_user {username} {password}"'; + $data[] = DiagnosticData::error('User Admin not found in database. Please run: "php lychee:create_user {username} {password}"', self::class); // @codeCoverageIgnoreEnd } diff --git a/app/Actions/Diagnostics/Pipes/Checks/AppUrlMatchCheck.php b/app/Actions/Diagnostics/Pipes/Checks/AppUrlMatchCheck.php index 97babf5f0e5..a0ef912cf0f 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/AppUrlMatchCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/AppUrlMatchCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Facades\Helpers; use Safe\Exceptions\PcreException; use function Safe\preg_match; @@ -27,8 +28,7 @@ public function handle(array &$data, \Closure $next): array $dir_url = config('app.dir_url'); if (config('app.url') === 'http://localhost') { // @codeCoverageIgnoreStart - $data[] = 'Warning: APP_URL is still set to default, this will break access to all your images'; - $data[] = self::INVISIBLE_WARNING . 'and assets if you are using Lychee behind a sub-domain.'; + $data[] = DiagnosticData::warn('APP_URL is still set to default, this will break access to all your images and assets if you are using Lychee behind a sub-domain.', self::class); // @codeCoverageIgnoreEnd } @@ -40,63 +40,63 @@ public function handle(array &$data, \Closure $next): array if ($bad !== '') { // @codeCoverageIgnoreStart - $data[] = sprintf( - 'Error: APP_URL (%s) contains a sub-path (%s).', - $censored_app_url, - $censored_bad, - ); - $data[] = sprintf( - self::INVISIBLE_ERROR . 'Instead set APP_DIR to (%s) and APP_URL to (%s) in your .env', - $censored_bad, - $censored_current, + $data[] = DiagnosticData::error( + sprintf('APP_URL (%s) contains a sub-path (%s).', $censored_app_url, $censored_bad), + self::class, + [ + sprintf('Instead set APP_DIR to (%s) and APP_URL to (%s) in your .env', $censored_bad, $censored_current), + ] ); // @codeCoverageIgnoreEnd } if ($bad !== '') { // @codeCoverageIgnoreStart - $data[] = sprintf( - 'Warning: APP_URL (%s) contains a sub-path (%s).', - $censored_app_url, - $censored_bad + $data[] = DiagnosticData::error( + sprintf('APP_URL (%s) contains a sub-path (%s).', $censored_app_url, $censored_bad), + self::class, + ['This may impact your WebAuthn authentication.'] ); - $data[] = self::INVISIBLE_WARNING . 'This may impact your WebAuthn authentication.'; // @codeCoverageIgnoreEnd } if (!$this->checkUrlMatchCurrentHost()) { // @codeCoverageIgnoreStart - $data[] = sprintf( - 'Error: APP_URL (%s) does not match the current url (%s).', - $censored_app_url, - $censored_current, + $data[] = DiagnosticData::error( + sprintf('APP_URL (%s) does not match the current url (%s).', $censored_app_url, $censored_current), + self::class, + ['This will break WebAuthn authentication.'] ); - $data[] = self::INVISIBLE_ERROR . 'This will break WebAuthn authentication.'; // @codeCoverageIgnoreEnd } $config_url_imgage = config('filesystems.disks.images.url'); if ($config_url_imgage === '') { // @codeCoverageIgnoreStart - $data[] = 'Error: LYCHEE_UPLOADS_URL is set and empty. This will prevent images to be displayed. Remove the line from your .env'; + $data[] = DiagnosticData::error( + 'LYCHEE_UPLOADS_URL is set and empty. This will prevent images to be displayed. Remove the line from your .env', + self::class + ); // @codeCoverageIgnoreEnd } if (!str_starts_with($config_url_imgage, '/') && !str_starts_with($config_url_imgage, 'http')) { // @codeCoverageIgnoreStart - $data[] = 'Error: LYCHEE_UPLOADS_URL is set but starts with neither a / nor http.'; - $data[] = self::INVISIBLE_ERROR . 'This will prevent images from being displayed. Remove the line from your .env'; + $data[] = DiagnosticData::error( + 'LYCHEE_UPLOADS_URL is set but starts with neither a / nor http.', + self::class, + ['This will prevent images from being displayed. Remove the line from your .env'] + ); // @codeCoverageIgnoreEnd } if (($config_url . $dir_url . '/uploads') === $config_url_imgage && !$this->checkUrlMatchCurrentHost()) { // @codeCoverageIgnoreStart - $data[] = sprintf( - 'Error: APP_URL (%s) does not match the current url (%s).', - $censored_app_url, - $censored_current + $data[] = DiagnosticData::error( + sprintf('APP_URL (%s) does not match the current url (%s).', $censored_app_url, $censored_current), + self::class, + ['This will prevent images from being properly displayed.'] ); - $data[] = self::INVISIBLE_ERROR . 'This will prevent images from being properly displayed.'; // @codeCoverageIgnoreEnd } diff --git a/app/Actions/Diagnostics/Pipes/Checks/BasicPermissionCheck.php b/app/Actions/Diagnostics/Pipes/Checks/BasicPermissionCheck.php index 8d18ca39ddd..c767c324847 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/BasicPermissionCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/BasicPermissionCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Enum\StorageDiskType; use App\Exceptions\Handler; use App\Exceptions\Internal\InvalidConfigOption; @@ -71,7 +72,7 @@ public function handle(array &$data, \Closure $next): array /** * Check all the folders with the correct permissions. * - * @param array $data + * @param DiagnosticData[] $data * * @return void */ @@ -91,7 +92,7 @@ public function folders(array &$data): void $groupIDsOrFalse = posix_getgroups(); // @codeCoverageIgnoreStart } catch (PosixException) { - $data[] = 'Error: Could not determine groups of process'; + $data[] = DiagnosticData::error('Could not determine groups of process', self::class); return; } @@ -128,17 +129,17 @@ function (int $gid): string { if ($this->numOwnerIssues > self::MAX_ISSUE_REPORTS_PER_TYPE) { // @codeCoverageIgnoreStart - $data[] = sprintf('Warning: %d more directories with wrong owner', $this->numOwnerIssues - self::MAX_ISSUE_REPORTS_PER_TYPE); + $data[] = DiagnosticData::warn(sprintf('%d more directories with wrong owner', $this->numOwnerIssues - self::MAX_ISSUE_REPORTS_PER_TYPE), self::class); // @codeCoverageIgnoreEnd } if ($this->numPermissionIssues > self::MAX_ISSUE_REPORTS_PER_TYPE) { // @codeCoverageIgnoreStart - $data[] = sprintf('Warning: %d more directories with wrong permissions', $this->numPermissionIssues - self::MAX_ISSUE_REPORTS_PER_TYPE); + $data[] = DiagnosticData::warn(sprintf('%d more directories with wrong permissions', $this->numPermissionIssues - self::MAX_ISSUE_REPORTS_PER_TYPE), self::class); // @codeCoverageIgnoreEnd } if ($this->numAccessIssues > self::MAX_ISSUE_REPORTS_PER_TYPE) { // @codeCoverageIgnoreStart - $data[] = sprintf('Warning: %d more inaccessible directories', $this->numAccessIssues - self::MAX_ISSUE_REPORTS_PER_TYPE); + $data[] = DiagnosticData::warn(sprintf('%d more inaccessible directories', $this->numAccessIssues - self::MAX_ISSUE_REPORTS_PER_TYPE), self::class); // @codeCoverageIgnoreEnd } } @@ -146,7 +147,7 @@ function (int $gid): string { /** * Check if user.css has the correct permissions. * - * @param array $data + * @param DiagnosticData[] $data * * @return void */ @@ -155,10 +156,11 @@ public function userCSS(array &$data): void $p = Storage::disk('dist')->path('user.css'); if (!Helpers::hasPermissions($p)) { // @codeCoverageIgnoreStart - $data[] = sprintf("Warning: '%s' does not exist or has insufficient read/write privileges.", $this->anonymize($p)); + $data[] = DiagnosticData::warn(sprintf("'%s' does not exist or has insufficient read/write privileges.", $this->anonymize($p)), self::class); + $p = Storage::disk('dist')->path(''); if (!Helpers::hasPermissions($p)) { - $data[] = sprintf("Warning: '%s' has insufficient read/write privileges.", $this->anonymize($p)); + $data[] = DiagnosticData::warn(sprintf("'%s' has insufficient read/write privileges.", $this->anonymize($p)), self::class); } // @codeCoverageIgnoreEnd } @@ -170,8 +172,8 @@ public function userCSS(array &$data): void * For efficiency reasons only the directory permissions are checked, * not the permissions of every single file. * - * @param string $path the path of the directory or file to check - * @param string[] $data the list of errors to append to + * @param string $path the path of the directory or file to check + * @param DiagnosticData[] $data the list of errors to append to * * @noinspection PhpComposerExtensionStubsInspection */ @@ -186,7 +188,7 @@ private function checkDirectoryPermissionsRecursively(string $path, array &$data $actualPerm = fileperms($path); // @codeCoverageIgnoreStart } catch (FilesystemException) { - $data[] = sprintf('Warning: Unable to determine permissions for %s' . PHP_EOL, $this->anonymize($path)); + $data[] = DiagnosticData::warn(sprintf('Unable to determine permissions for %s', $this->anonymize($path)), self::class); return; } @@ -216,7 +218,7 @@ private function checkDirectoryPermissionsRecursively(string $path, array &$data // @codeCoverageIgnoreStart $this->numOwnerIssues++; if ($this->numOwnerIssues <= self::MAX_ISSUE_REPORTS_PER_TYPE) { - $data[] = sprintf('Warning: %s is owned by group %s, but should be owned by one out of %s', $this->anonymize($path), $owningGroupName, $this->groupNames); + $data[] = DiagnosticData::warn(sprintf('%s is owned by group %s, but should be owned by one out of %s', $this->anonymize($path), $owningGroupName, $this->groupNames), self::class); } // @codeCoverageIgnoreEnd } @@ -225,12 +227,7 @@ private function checkDirectoryPermissionsRecursively(string $path, array &$data // @codeCoverageIgnoreStart $this->numPermissionIssues++; if ($this->numPermissionIssues <= self::MAX_ISSUE_REPORTS_PER_TYPE) { - $data[] = sprintf( - 'Warning: %s has permissions %04o, but should have %04o', - $this->anonymize($path), - $actualPerm, - $expectedPerm - ); + $data[] = DiagnosticData::warn(sprintf('%s has permissions %04o, but should have %04o', $this->anonymize($path), $actualPerm, $expectedPerm), self::class); } // @codeCoverageIgnoreEnd } @@ -245,7 +242,7 @@ private function checkDirectoryPermissionsRecursively(string $path, array &$data !is_readable($path) => 'not readable', default => '', }; - $data[] = sprintf('Error: %s is %s by %s', $this->anonymize($path), $problem, $this->groupNames); + $data[] = DiagnosticData::error(sprintf('%s is %s by %s', $this->anonymize($path), $problem, $this->groupNames), self::class); } // @codeCoverageIgnoreEnd } @@ -258,7 +255,7 @@ private function checkDirectoryPermissionsRecursively(string $path, array &$data } // @codeCoverageIgnoreStart } catch (\Exception $e) { - $data[] = 'Error: ' . $e->getMessage(); + $data[] = DiagnosticData::error($e->getMessage(), self::class); Handler::reportSafely($e); } // @codeCoverageIgnoreEnd diff --git a/app/Actions/Diagnostics/Pipes/Checks/ConfigSanityCheck.php b/app/Actions/Diagnostics/Pipes/Checks/ConfigSanityCheck.php index dde33e3e0a4..4150761ef3a 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/ConfigSanityCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/ConfigSanityCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Models\Configs; use Illuminate\Support\Facades\Schema; @@ -12,9 +13,6 @@ */ class ConfigSanityCheck implements DiagnosticPipe { - /** @var array */ - private array $settings; - /** * {@inheritDoc} */ @@ -24,43 +22,16 @@ public function handle(array &$data, \Closure $next): array return $next($data); } - // Load settings - $this->settings = Configs::get(); - - $this->checkKeysExistsAndSet($data); - $this->sanity($data); - $this->checkDropBoxKeyWarning($data); return $next($data); } - /** - * Check that a certain set of configuration exists in the database. - * - * @param array $data - * - * @return void - */ - private function checkKeysExistsAndSet(array &$data): void - { - $keys_checked = [ - 'sorting_photos_col', 'sorting_albums_col', - 'imagick', 'skip_duplicates', 'check_for_updates', 'version', - ]; - - foreach ($keys_checked as $key) { - if (!isset($this->settings[$key])) { - $data[] = 'Error: ' . $key . ' not set in database'; - } - } - } - /** * Warning if the Dropbox key does not exists. * - * @param array $data + * @param DiagnosticData[] $data * * @return void */ @@ -68,15 +39,15 @@ private function checkDropBoxKeyWarning(array &$data): void { $dropbox = Configs::getValueAsString('dropbox_key'); if ($dropbox === '') { - $data[] = 'Warning: Dropbox import not working. dropbox_key is empty.'; - $data[] = 'Info: To hide this Dropbox warning, set the dropbox_key to "disabled"'; + $data[] = DiagnosticData::warn('Dropbox import not working. dropbox_key is empty.', self::class); + $data[] = DiagnosticData::info('To hide this Dropbox warning, set the dropbox_key to "disabled".', self::class); } } /** * Sanity check of the config. * - * @param array $return + * @param DiagnosticData[] $return */ private function sanity(array &$return): void { @@ -85,7 +56,7 @@ private function sanity(array &$return): void foreach ($configs as $config) { $message = $config->sanity($config->value); if ($message !== '') { - $return[] = $message; + $return[] = DiagnosticData::error(str_replace('Error: ', '', $message), self::class); } } } diff --git a/app/Actions/Diagnostics/Pipes/Checks/CountSizeVariantsCheck.php b/app/Actions/Diagnostics/Pipes/Checks/CountSizeVariantsCheck.php index 0a3f6fccf77..62afb05d254 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/CountSizeVariantsCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/CountSizeVariantsCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -24,11 +25,14 @@ public function handle(array &$data, \Closure $next): array $num = DB::table('size_variants')->where('size_variants.filesize', '=', 0)->count(); if ($num > 0) { // @codeCoverageIgnoreStart - $data[] = sprintf('Info: Found %d small images without filesizes.', $num); - $data[] = sprintf(' You can use `php artisan lychee:variant_filesize %d` to compute them.', $num); + $data[] = DiagnosticData::info( + sprintf('Found %d small images without filesizes.', $num), + self::class, + [sprintf('You can use `php artisan lychee:variant_filesize %d` to compute them.', $num)] + ); // @codeCoverageIgnoreEnd } return $next($data); } -} \ No newline at end of file +} diff --git a/app/Actions/Diagnostics/Pipes/Checks/DBIntegrityCheck.php b/app/Actions/Diagnostics/Pipes/Checks/DBIntegrityCheck.php index b5282ce7d48..5f46505f62f 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/DBIntegrityCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/DBIntegrityCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Models\Photo; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -33,7 +34,7 @@ public function handle(array &$data, \Closure $next): array ->get(); foreach ($photos as $photo) { - $data[] = 'Error: Photo without Original found -- ' . $photo->title . ' in ' . ($photo->album?->title ?? __('gallery.smart_album.unsorted')); + $data[] = DiagnosticData::error('Photo without Original found -- ' . $photo->title . ' in ' . ($photo->album?->title ?? __('gallery.smart_album.unsorted')), self::class); } return $next($data); diff --git a/app/Actions/Diagnostics/Pipes/Checks/DBSupportCheck.php b/app/Actions/Diagnostics/Pipes/Checks/DBSupportCheck.php index bd186d48c24..78c191d1099 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/DBSupportCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/DBSupportCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; /** * Check that the database is supported. @@ -29,14 +30,14 @@ public function handle(array &$data, \Closure $next): array $found = true; if (!extension_loaded($db_possibility[1])) { // @codeCoverageIgnoreStart - $data[] = 'Error: ' . $db_possibility[0] . ' db driver selected and PHP ' . $db_possibility[1] . ' extension not activated'; + $data[] = DiagnosticData::error($db_possibility[0] . ' db driver selected and PHP ' . $db_possibility[1] . ' extension not activated', self::class); // @codeCoverageIgnoreEnd } } } if (!$found) { // @codeCoverageIgnoreStart - $data[] = 'Error: could not find the database solution for ' . config('database.default'); + $data[] = DiagnosticData::error('could not find the database solution for ' . config('database.default'), self::class); // @codeCoverageIgnoreEnd } diff --git a/app/Actions/Diagnostics/Pipes/Checks/ForeignKeyListInfo.php b/app/Actions/Diagnostics/Pipes/Checks/ForeignKeyListInfo.php index 20e2d24bcd2..007370d9d94 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/ForeignKeyListInfo.php +++ b/app/Actions/Diagnostics/Pipes/Checks/ForeignKeyListInfo.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use Illuminate\Support\Facades\DB; /** @@ -31,7 +32,7 @@ public function handle(array &$data, \Closure $next): array } /** - * @param array $data + * @param DiagnosticData[] $data * * @return void */ @@ -40,12 +41,15 @@ private function sqlite(array &$data): void $fks = DB::select("SELECT m.name , p.* FROM sqlite_master m JOIN pragma_foreign_key_list(m.name) p ON m.name != p.\"table\" WHERE m.type = 'table' ORDER BY m.name;"); foreach ($fks as $fk) { - $data[] = sprintf('Foreign key: %-30s → %-20s : %s', $fk->name . '.' . $fk->from, $fk->table . '.' . $fk->to, strval($fk->on_update)); + $data[] = DiagnosticData::info( + sprintf('Foreign key: %-30s → %-20s : %s', $fk->name . '.' . $fk->from, $fk->table . '.' . $fk->to, strval($fk->on_update)), + self::class + ); } } /** - * @param array $data + * @param DiagnosticData[] $data * * @return void */ @@ -60,15 +64,15 @@ private function mysql(array &$data): void order by fks.constraint_schema, fks.table_name; '); foreach ($fks as $fk) { - $data[] = sprintf('Foreign key: %-30s → %-20s : %s', - $fk->TABLE_NAME . '.' . $fk->COLUMN_NAME, - $fk->REFERENCED_TABLE_NAME . '.' . $fk->REFERENCED_COLUMN_NAME, - strval($fk->UPDATE_RULE)); + $data[] = DiagnosticData::info( + sprintf('Foreign key: %-30s → %-20s : %s', $fk->TABLE_NAME . '.' . $fk->COLUMN_NAME, $fk->REFERENCED_TABLE_NAME . '.' . $fk->REFERENCED_COLUMN_NAME, strval($fk->UPDATE_RULE)), + self::class + ); } } /** - * @param array $data + * @param DiagnosticData[] $data * * @return void */ @@ -89,10 +93,7 @@ private function pgsql(array &$data): void WHERE tc.constraint_type = \'FOREIGN KEY\';'); foreach ($fks as $fk) { - $data[] = sprintf('Foreign key: %-30s → %-20s', - $fk->table_name . '.' . $fk->column_name, - $fk->foreign_table_name . '.' . $fk->foreign_column_name); + $data[] = DiagnosticData::info(sprintf('Foreign key: %-30s → %-20s', $fk->table_name . '.' . $fk->column_name, $fk->foreign_table_name . '.' . $fk->foreign_column_name), self::class); } } } - diff --git a/app/Actions/Diagnostics/Pipes/Checks/GDSupportCheck.php b/app/Actions/Diagnostics/Pipes/Checks/GDSupportCheck.php index 498f7f4ec45..0580a8c9dea 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/GDSupportCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/GDSupportCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; /** * Verify that GD support the correct images extensions. @@ -18,12 +19,12 @@ public function handle(array &$data, \Closure $next): array $gdVersion = gd_info(); if (!$gdVersion['JPEG Support']) { // @codeCoverageIgnoreStart - $data[] = 'Error: PHP gd extension without jpeg support'; + $data[] = DiagnosticData::error('PHP gd extension without jpeg support', self::class); // @codeCoverageIgnoreEnd } if (!$gdVersion['PNG Support']) { // @codeCoverageIgnoreStart - $data[] = 'Error: PHP gd extension without png support'; + $data[] = DiagnosticData::error('PHP gd extension without png support', self::class); // @codeCoverageIgnoreEnd } if ( @@ -31,12 +32,12 @@ public function handle(array &$data, \Closure $next): array !$gdVersion['GIF Create Support'] ) { // @codeCoverageIgnoreStart - $data[] = 'Error: PHP gd extension without full gif support'; + $data[] = DiagnosticData::error('PHP gd extension without full gif support', self::class); // @codeCoverageIgnoreEnd } if (!$gdVersion['WebP Support']) { // @codeCoverageIgnoreStart - $data[] = 'Error: PHP gd extension without WebP support'; + $data[] = DiagnosticData::error('PHP gd extension without WebP support', self::class); // @codeCoverageIgnoreEnd } } diff --git a/app/Actions/Diagnostics/Pipes/Checks/ImageOptCheck.php b/app/Actions/Diagnostics/Pipes/Checks/ImageOptCheck.php index 0eba844f552..26e253998eb 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/ImageOptCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/ImageOptCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Facades\Helpers; use App\Models\Configs; use Illuminate\Support\Facades\Schema; @@ -52,11 +53,11 @@ public function handle(array &$data, \Closure $next): array foreach ($tools as $tool) { $path = exec('command -v ' . $binaryPath . $tool->binaryName()); if ($path === '') { - $data[] = 'Warning: lossless_optimization set to 1 but ' . $binaryPath . $tool->binaryName() . ' not found!'; + $data[] = DiagnosticData::warn('lossless_optimization set to 1 but ' . $binaryPath . $tool->binaryName() . ' not found!', self::class); } } } else { - $data[] = 'Warning: lossless_optimization set to 1 but exec() is not enabled.'; + $data[] = DiagnosticData::warn('lossless_optimization set to 1 but exec() is not enabled.', self::class); } return $next($data); diff --git a/app/Actions/Diagnostics/Pipes/Checks/IniSettingsCheck.php b/app/Actions/Diagnostics/Pipes/Checks/IniSettingsCheck.php index 251c5cda9e4..58701a3bc4b 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/IniSettingsCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/IniSettingsCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Facades\Helpers; use App\Models\Configs; use LycheeVerify\Verify; @@ -29,101 +30,101 @@ public function handle(array &$data, \Closure $next): array $settings = Configs::get(); if (!$this->verify->validate()) { - $data[] = 'Warning: Your installation has been tampered. Please verify the integrity of your files.'; + $data[] = DiagnosticData::warn('Your installation has been tampered. Please verify the integrity of your files.', self::class); } if (Helpers::convertSize(ini_get('upload_max_filesize')) < Helpers::convertSize(('30M'))) { - $data[] = 'Warning: You may experience problems when uploading a photo of large size. Take a look in the FAQ for details.'; + $data[] = DiagnosticData::warn('You may experience problems when uploading a photo of large size. Take a look in the FAQ for details.', self::class); } if (Helpers::convertSize(ini_get('post_max_size')) < Helpers::convertSize(('100M'))) { - $data[] = 'Warning: You may experience problems when uploading a photo of large size. Take a look in the FAQ for details.'; + $data[] = DiagnosticData::warn('You may experience problems when uploading a photo of large size. Take a look in the FAQ for details.', self::class); } $max_execution_time = intval(ini_get('max_execution_time')); if (0 < $max_execution_time && $max_execution_time < 200) { // @codeCoverageIgnoreStart - $data[] = 'Warning: You may experience problems when uploading a photo of large size or handling many/large albums. Take a look in the FAQ for details.'; + $data[] = DiagnosticData::warn('You may experience problems when uploading a photo of large size or handling many/large albums. Take a look in the FAQ for details.', self::class); // @codeCoverageIgnoreEnd } if (filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN) !== true) { // @codeCoverageIgnoreStart - $data[] = 'Warning: You may experience problems with the Dropbox- and URL-Import. Edit your php.ini and set allow_url_fopen to 1.'; + $data[] = DiagnosticData::warn('You may experience problems with the Dropbox- and URL-Import. Edit your php.ini and set allow_url_fopen to 1.', self::class); // @codeCoverageIgnoreEnd } // Check imagick if (!extension_loaded('imagick')) { // @codeCoverageIgnoreStart - $data[] = 'Warning: Pictures that are rotated lose their metadata! Please install Imagick to avoid that.'; + $data[] = DiagnosticData::warn('Pictures that are rotated lose their metadata! Please install Imagick to avoid that.', self::class); // @codeCoverageIgnoreEnd } else { if (!isset($settings['imagick'])) { // @codeCoverageIgnoreStart - $data[] = 'Warning: Pictures that are rotated lose their metadata! Please enable Imagick in settings to avoid that.'; + $data[] = DiagnosticData::warn('Pictures that are rotated lose their metadata! Please enable Imagick in settings to avoid that.', self::class); // @codeCoverageIgnoreEnd } } if (!Helpers::isExecAvailable()) { // @codeCoverageIgnoreStart - $data[] = 'Warning: exec function has been disabled. You may experience some error 500, please report them to us.'; + $data[] = DiagnosticData::warn('exec function has been disabled. You may experience some error 500, please report them to us.', self::class); // @codeCoverageIgnoreEnd } if (preg_match('!^[-_a-zA-Z]+/\d+(\.\d+)*[a-z]? \(.*\)!', ini_get('user_agent')) === 0) { // @codeCoverageIgnoreStart - $data[] = 'Warning: user_agent for PHP is not properly set. You may experience problems when importing images via URL.'; + $data[] = DiagnosticData::warn('user_agent for PHP is not properly set. You may experience problems when importing images via URL.', self::class); // @codeCoverageIgnoreEnd } if (extension_loaded('xdebug')) { // @codeCoverageIgnoreStart $msg = config('app.debug') !== true - ? 'Error: xdebug is enabled although Lychee is not in debug mode. Outside of debugging, xdebug will generate significant slowdown on your application.' - : 'Warning: xdebug is enabled. This will generate significant slowdown on your application.'; + ? DiagnosticData::error('xdebug is enabled although Lychee is not in debug mode. Outside of debugging, xdebug will generate significant slowdown on your application.', self::class) + : DiagnosticData::warn('xdebug is enabled. This will generate significant slowdown on your application.', self::class); $data[] = $msg; // @codeCoverageIgnoreEnd } if (extension_loaded('xdebug')) { // @codeCoverageIgnoreStart - $data[] = 'Info: xdebug mode:' . ini_get('xdebug.mode'); - $data[] = 'Info: xdebug start_with_request:' . ini_get('xdebug.start_with_request'); + $data[] = DiagnosticData::info('xdebug mode:' . ini_get('xdebug.mode'), self::class); + $data[] = DiagnosticData::info('xdebug start_with_request:' . ini_get('xdebug.start_with_request'), self::class); // @codeCoverageIgnoreEnd } if (ini_get('assert.exception') !== '1') { // @codeCoverageIgnoreStart - $data[] = 'Warning: assert.exception is set to false. Lychee assumes that failing assertions throw proper exceptions.'; + $data[] = DiagnosticData::warn('assert.exception is set to false. Lychee assumes that failing assertions throw proper exceptions.', self::class); // @codeCoverageIgnoreEnd } if (ini_get('zend.assertions') !== '-1' && config('app.debug') !== true) { // @codeCoverageIgnoreStart - $data[] = 'Warning: zend.assertions is enabled although Lychee is not in debug mode. Outside of debugging, code generation for assertions is recommended to be disabled for efficiency reasons'; + $data[] = DiagnosticData::warn('zend.assertions is enabled although Lychee is not in debug mode. Outside of debugging, code generation for assertions is recommended to be disabled for efficiency reasons', self::class); // @codeCoverageIgnoreEnd } if (ini_get('zend.assertions') !== '1' && config('app.debug') === true) { - $data[] = 'Warning: zend.assertions is disabled although Lychee is in debug mode. For easier debugging code generation for assertions should be enabled.'; + $data[] = DiagnosticData::warn('zend.assertions is disabled although Lychee is in debug mode. For easier debugging code generation for assertions should be enabled.', self::class); } $disabledFunctions = explode(',', ini_get('disable_functions')); $tmpfileExists = function_exists('tmpfile') && !in_array('tmpfile', $disabledFunctions, true); if ($tmpfileExists !== true) { // @codeCoverageIgnoreStart - $data[] = 'Error: tmpfile() is disabled, this will prevent you from uploading pictures.'; + $data[] = DiagnosticData::error('tmpfile() is disabled, this will prevent you from uploading pictures.', self::class); // @codeCoverageIgnoreEnd } $path = sys_get_temp_dir(); if (!is_writable($path)) { // @codeCoverageIgnoreStart - $data[] = 'Error: sys_get_temp_dir() is not writable, this will prevent you from uploading pictures.'; + $data[] = DiagnosticData::error('sys_get_temp_dir() is not writable, this will prevent you from uploading pictures.', self::class); // @codeCoverageIgnoreEnd } if (!is_readable($path)) { // @codeCoverageIgnoreStart - $data[] = 'Error: sys_get_temp_dir() is not readable, this will prevent you from uploading pictures.'; + $data[] = DiagnosticData::error('sys_get_temp_dir() is not readable, this will prevent you from uploading pictures.', self::class); // @codeCoverageIgnoreEnd } diff --git a/app/Actions/Diagnostics/Pipes/Checks/MigrationCheck.php b/app/Actions/Diagnostics/Pipes/Checks/MigrationCheck.php index 412c994bdc3..57a7766053d 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/MigrationCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/MigrationCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Metadata\Versions\FileVersion; use App\Metadata\Versions\InstalledVersion; @@ -18,13 +19,13 @@ public function handle(array &$data, \Closure $next): array { if (!self::isUpToDate()) { // @codeCoverageIgnoreStart - $data[] = 'Error: Database is behind file version. Please apply the migrations.'; + $data[] = DiagnosticData::error('Database is behind file version. Please apply the migrations.', self::class); // @codeCoverageIgnoreEnd } if ($this->isInFuture()) { // @codeCoverageIgnoreStart - $data[] = 'Warning: Database is in advance of file version. Please check your installation.'; + $data[] = DiagnosticData::warn('Database is in advance of file version. Please check your installation.', self::class); // @codeCoverageIgnoreEnd } diff --git a/app/Actions/Diagnostics/Pipes/Checks/PHPVersionCheck.php b/app/Actions/Diagnostics/Pipes/Checks/PHPVersionCheck.php index 253c62d9037..d8ec04988f7 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/PHPVersionCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/PHPVersionCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; /** * We want to make sure that our users are using the correct version of PHP. @@ -28,7 +29,7 @@ public function handle(array &$data, \Closure $next): array } /** - * @param array $data + * @param DiagnosticData[] $data * * @return void */ @@ -38,19 +39,19 @@ private function checkPhpVersion(array &$data): void // I hereby solemnly declare this code as covered ! if (floatval(phpversion()) <= self::PHP_ERROR) { // @codeCoverageIgnoreStart - $data[] = 'Error: Upgrade to PHP ' . self::PHP_WARNING . ' or higher'; + $data[] = DiagnosticData::error('Upgrade to PHP ' . self::PHP_WARNING . ' or higher', self::class); // @codeCoverageIgnoreEnd } elseif (floatval(phpversion()) < self::PHP_WARNING) { // @codeCoverageIgnoreStart - $data[] = 'Warning: Upgrade to PHP ' . self::PHP_LATEST . ' or higher'; + $data[] = DiagnosticData::warn('Upgrade to PHP ' . self::PHP_LATEST . ' or higher', self::class); // @codeCoverageIgnoreEnd } elseif (floatval(phpversion()) < self::PHP_LATEST) { - $data[] = 'Info: Latest version of PHP is ' . self::PHP_LATEST; + $data[] = DiagnosticData::info('Latest version of PHP is ' . self::PHP_LATEST, self::class); } } /** - * @param array $data + * @param DiagnosticData[] $data * * @return void */ @@ -59,13 +60,13 @@ private function check32Bits(array &$data): void // 32 or 64 bits ? if (PHP_INT_MAX === 2147483647) { // @codeCoverageIgnoreStart - $data[] = 'Warning: Using 32 bit PHP, recommended upgrade to 64 bit'; + $data[] = DiagnosticData::warn('Using 32 bit PHP, recommended upgrade to 64 bit', self::class); // @codeCoverageIgnoreEnd } } /** - * @param array $data + * @param DiagnosticData[] $data * * @return void */ @@ -96,7 +97,7 @@ private function checkExtensions(array &$data): void foreach ($extensions as $extension) { if (!extension_loaded($extension)) { // @codeCoverageIgnoreStart - $data[] = 'Error: PHP ' . $extension . ' extension not activated'; + $data[] = DiagnosticData::error('PHP ' . $extension . ' extension not activated', self::class); // @codeCoverageIgnoreEnd } } diff --git a/app/Actions/Diagnostics/Pipes/Checks/PlaceholderExistsCheck.php b/app/Actions/Diagnostics/Pipes/Checks/PlaceholderExistsCheck.php index b8c13820a20..24302d38671 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/PlaceholderExistsCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/PlaceholderExistsCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Enum\SizeVariantType; use App\Image\SizeVariantDimensionHelpers; use App\Models\SizeVariant; @@ -14,10 +15,10 @@ */ class PlaceholderExistsCheck implements DiagnosticPipe { - public const INFO_MSG_MISSING = 'Info: Found %d placeholders that could be generated.'; - public const INFO_LINE_MISSING = ' You can use `php artisan lychee:generate_thumbs placeholder %d` to generate them.'; - public const INFO_MSG_UNENCODED = 'Info: Found %d placeholder images that have not been encoded.'; - public const INFO_LINE_UNENCODED = ' You can use `php artisan lychee:encode_placeholders %d` to encode them.'; + public const INFO_MSG_MISSING = 'Found %d placeholders that could be generated.'; + public const INFO_LINE_MISSING = 'You can use `php artisan lychee:generate_thumbs placeholder %d` to generate them.'; + public const INFO_MSG_UNENCODED = 'Found %d placeholder images that have not been encoded.'; + public const INFO_LINE_UNENCODED = 'You can use `php artisan lychee:encode_placeholders %d` to encode them.'; /** * {@inheritDoc} @@ -59,14 +60,12 @@ public function handle(array &$data, \Closure $next): array $num = $result->num_unencoded_placeholder; if ($num > 0) { - $data[] = sprintf(self::INFO_MSG_UNENCODED, $num); - $data[] = sprintf(self::INFO_LINE_UNENCODED, $num); + $data[] = DiagnosticData::info(sprintf(self::INFO_MSG_UNENCODED, $num), self::class, [sprintf(self::INFO_LINE_UNENCODED, $num)]); } $num = $result->max_num_placeholder - $result->num_placeholder; if ($num > 0) { - $data[] = sprintf(self::INFO_MSG_MISSING, $num); - $data[] = sprintf(self::INFO_LINE_MISSING, $num); + $data[] = DiagnosticData::info(sprintf(self::INFO_MSG_MISSING, $num), self::class, [sprintf(self::INFO_LINE_MISSING, $num)]); } return $next($data); diff --git a/app/Actions/Diagnostics/Pipes/Checks/SmallMediumExistsCheck.php b/app/Actions/Diagnostics/Pipes/Checks/SmallMediumExistsCheck.php index b5973fd2ca2..2d5c60dee5a 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/SmallMediumExistsCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/SmallMediumExistsCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Enum\SizeVariantType; use App\Image\SizeVariantDimensionHelpers; use App\Models\SizeVariant; @@ -22,8 +23,8 @@ class SmallMediumExistsCheck implements DiagnosticPipe public const MAX_NUM_MEDIUM = 'max_num_medium'; public const MAX_NUM_SMALL2X = 'max_num_small2x'; public const MAX_NUM_MEDIUM2X = 'max_num_medium2x'; - public const INFO_MSG = 'Info: Found %d %s that could be generated.'; - public const INFO_LINE = ' You can use `php artisan lychee:generate_thumbs %s %d` to generate them.'; + public const INFO_MSG = 'Found %d %s that could be generated.'; + public const INFO_LINE = 'You can use `php artisan lychee:generate_thumbs %s %d` to generate them.'; /** * {@inheritDoc} @@ -108,26 +109,38 @@ public function handle(array &$data, \Closure $next): array $num = $result->{self::MAX_NUM_SMALL} - $result->{self::NUM_SMALL}; // @phpstan-ignore-line if ($num > 0) { - $data[] = sprintf(self::INFO_MSG, $num, SizeVariantType::SMALL->name()); - $data[] = sprintf(self::INFO_LINE, SizeVariantType::SMALL->name(), $num); + $data[] = DiagnosticData::info( + sprintf(self::INFO_MSG, $num, SizeVariantType::SMALL->name()), + self::class, + [sprintf(self::INFO_LINE, SizeVariantType::SMALL->name(), $num)] + ); } $num = $result->{self::MAX_NUM_SMALL2X} - $result->{self::NUM_SMALL2X}; // @phpstan-ignore-line if ($num > 0 && $svHelpers->isEnabledByConfiguration(SizeVariantType::SMALL2X)) { - $data[] = sprintf(self::INFO_MSG, $num, SizeVariantType::SMALL2X->name()); - $data[] = sprintf(self::INFO_LINE, SizeVariantType::SMALL2X->name(), $num); + $data[] = DiagnosticData::info( + sprintf(self::INFO_MSG, $num, SizeVariantType::SMALL2X->name()), + self::class, + [sprintf(self::INFO_LINE, SizeVariantType::SMALL2X->name(), $num)] + ); } $num = $result->{self::MAX_NUM_MEDIUM} - $result->{self::NUM_MEDIUM}; // @phpstan-ignore-line if ($num > 0) { - $data[] = sprintf(self::INFO_MSG, $num, SizeVariantType::MEDIUM->name()); - $data[] = sprintf(self::INFO_LINE, SizeVariantType::MEDIUM->name(), $num); + $data[] = DiagnosticData::info( + sprintf(self::INFO_MSG, $num, SizeVariantType::MEDIUM->name()), + self::class, + [sprintf(self::INFO_LINE, SizeVariantType::MEDIUM->name(), $num)] + ); } $num = $result->{self::MAX_NUM_MEDIUM2X} - $result->{self::NUM_MEDIUM2X}; // @phpstan-ignore-line if ($num > 0 && $svHelpers->isEnabledByConfiguration(SizeVariantType::MEDIUM2X)) { - $data[] = sprintf(self::INFO_MSG, $num, SizeVariantType::MEDIUM2X->name()); - $data[] = sprintf(self::INFO_LINE, SizeVariantType::MEDIUM2X->name(), $num); + $data[] = DiagnosticData::info( + sprintf(self::INFO_MSG, $num, SizeVariantType::MEDIUM2X->name()), + self::class, + [sprintf(self::INFO_LINE, SizeVariantType::MEDIUM2X->name(), $num)] + ); } return $next($data); diff --git a/app/Actions/Diagnostics/Pipes/Checks/SupporterCheck.php b/app/Actions/Diagnostics/Pipes/Checks/SupporterCheck.php index 8e46a4cc657..db218054242 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/SupporterCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/SupporterCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Models\Configs; use LycheeVerify\Verify; @@ -33,7 +34,7 @@ public function handle(array &$data, \Closure $next): array if (!$this->verify->is_supporter()) { // @codeCoverageIgnoreStart - $data[] = 'Info: Have you considered supporting Lychee? :)'; + $data[] = DiagnosticData::info('Have you considered supporting Lychee? :)', self::class); // @codeCoverageIgnoreEnd } diff --git a/app/Actions/Diagnostics/Pipes/Checks/TimezoneCheck.php b/app/Actions/Diagnostics/Pipes/Checks/TimezoneCheck.php index e2a9a7ccaa3..99a18619c37 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/TimezoneCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/TimezoneCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use Carbon\CarbonTimeZone; /** @@ -18,8 +19,7 @@ public function handle(array &$data, \Closure $next): array $timezone = CarbonTimeZone::create(config('app.timezone')); if ($timezone === null) { // @codeCoverageIgnoreStart - $data[] - = 'Error: Could not retrieve timezone; you might experience strange results when importing photos without explicit EXIF timezone'; + $data[] = DiagnosticData::error('Could not retrieve timezone; you might experience strange results when importing photos without explicit EXIF timezone', self::class); return $next($data); // @codeCoverageIgnoreEnd @@ -29,8 +29,7 @@ public function handle(array &$data, \Closure $next): array $tzArray = explode('/', $timezoneName); if (count($tzArray) !== 2 || $tzArray[0] === 'Etc') { - $data[] - = 'Warning: Default timezone not properly set; you might experience strange results when importing photos without explicit EXIF timezone'; + $data[] = DiagnosticData::warn('Default timezone not properly set; you might experience strange results when importing photos without explicit EXIF timezone', self::class); } return $next($data); diff --git a/app/Actions/Diagnostics/Pipes/Checks/UpdatableCheck.php b/app/Actions/Diagnostics/Pipes/Checks/UpdatableCheck.php index 675f57175b1..f9d76b1f24d 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/UpdatableCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/UpdatableCheck.php @@ -3,6 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Checks; use App\Contracts\DiagnosticPipe; +use App\DTO\DiagnosticData; use App\Exceptions\ConfigurationException; use App\Exceptions\ExternalComponentMissingException; use App\Exceptions\InsufficientFilesystemPermissions; @@ -40,11 +41,11 @@ public function handle(array &$data, \Closure $next): array self::assertUpdatability(); // @codeCoverageIgnoreStart } catch (ExternalComponentMissingException $e) { - $data[] = 'Info: ' . $e->getMessage(); + $data[] = DiagnosticData::info($e->getMessage(), self::class); } catch (ConfigurationException $e) { - $data[] = 'Warning: ' . $e->getMessage(); + $data[] = DiagnosticData::warn($e->getMessage(), self::class); } catch (InsufficientFilesystemPermissions|VersionControlException $e) { - $data[] = 'Error: ' . $e->getMessage(); + $data[] = DiagnosticData::error($e->getMessage(), self::class); } // @codeCoverageIgnoreEnd } diff --git a/app/Actions/Diagnostics/Pipes/Infos/CountForeignKeyInfo.php b/app/Actions/Diagnostics/Pipes/Infos/CountForeignKeyInfo.php index 58f0b82ca7a..bb7e12f5dea 100644 --- a/app/Actions/Diagnostics/Pipes/Infos/CountForeignKeyInfo.php +++ b/app/Actions/Diagnostics/Pipes/Infos/CountForeignKeyInfo.php @@ -3,13 +3,13 @@ namespace App\Actions\Diagnostics\Pipes\Infos; use App\Actions\Diagnostics\Diagnostics; -use App\Contracts\DiagnosticPipe; +use App\Contracts\DiagnosticStringPipe; use Illuminate\Support\Facades\DB; /** * Instead of listing all Foreign key as in the Errors, we just check their number. */ -class CountForeignKeyInfo implements DiagnosticPipe +class CountForeignKeyInfo implements DiagnosticStringPipe { /** * {@inheritDoc} diff --git a/app/Actions/Diagnostics/Pipes/Infos/ExtensionsInfo.php b/app/Actions/Diagnostics/Pipes/Infos/ExtensionsInfo.php index e7f2ea798ea..130a2d7ddfa 100644 --- a/app/Actions/Diagnostics/Pipes/Infos/ExtensionsInfo.php +++ b/app/Actions/Diagnostics/Pipes/Infos/ExtensionsInfo.php @@ -3,14 +3,14 @@ namespace App\Actions\Diagnostics\Pipes\Infos; use App\Actions\Diagnostics\Diagnostics; -use App\Contracts\DiagnosticPipe; +use App\Contracts\DiagnosticStringPipe; use App\Facades\Helpers; use App\Models\Configs; /** * Info on what image processing we have available. */ -class ExtensionsInfo implements DiagnosticPipe +class ExtensionsInfo implements DiagnosticStringPipe { /** * {@inheritDoc} diff --git a/app/Actions/Diagnostics/Pipes/Infos/InstallTypeInfo.php b/app/Actions/Diagnostics/Pipes/Infos/InstallTypeInfo.php index 7632f3702b2..280035a630b 100644 --- a/app/Actions/Diagnostics/Pipes/Infos/InstallTypeInfo.php +++ b/app/Actions/Diagnostics/Pipes/Infos/InstallTypeInfo.php @@ -4,7 +4,7 @@ use App\Actions\Diagnostics\Diagnostics; use App\Assets\Features; -use App\Contracts\DiagnosticPipe; +use App\Contracts\DiagnosticStringPipe; use App\Metadata\Versions\InstalledVersion; use LycheeVerify\Verify; @@ -12,7 +12,7 @@ * What kind of Lychee install we are looking at? * Composer? Dev? Release? */ -class InstallTypeInfo implements DiagnosticPipe +class InstallTypeInfo implements DiagnosticStringPipe { public function __construct( private InstalledVersion $installedVersion, diff --git a/app/Actions/Diagnostics/Pipes/Infos/SystemInfo.php b/app/Actions/Diagnostics/Pipes/Infos/SystemInfo.php index 718b45393e7..e3f9ac40e8c 100644 --- a/app/Actions/Diagnostics/Pipes/Infos/SystemInfo.php +++ b/app/Actions/Diagnostics/Pipes/Infos/SystemInfo.php @@ -3,7 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Infos; use App\Actions\Diagnostics\Diagnostics; -use App\Contracts\DiagnosticPipe; +use App\Contracts\DiagnosticStringPipe; use App\Facades\Helpers; use App\Http\Resources\GalleryConfigs\UploadConfig; use Carbon\CarbonTimeZone; @@ -14,7 +14,7 @@ /** * What system are we running on? */ -class SystemInfo implements DiagnosticPipe +class SystemInfo implements DiagnosticStringPipe { /** * {@inheritDoc} diff --git a/app/Actions/Diagnostics/Pipes/Infos/VersionInfo.php b/app/Actions/Diagnostics/Pipes/Infos/VersionInfo.php index e0bd2586c54..2a68767f3a9 100644 --- a/app/Actions/Diagnostics/Pipes/Infos/VersionInfo.php +++ b/app/Actions/Diagnostics/Pipes/Infos/VersionInfo.php @@ -3,7 +3,7 @@ namespace App\Actions\Diagnostics\Pipes\Infos; use App\Actions\Diagnostics\Diagnostics; -use App\Contracts\DiagnosticPipe; +use App\Contracts\DiagnosticStringPipe; use App\DTO\LycheeGitInfo; use App\Enum\VersionChannelType; use App\Metadata\Versions\FileVersion; @@ -15,7 +15,7 @@ /** * Which version of Lychee are we using? */ -class VersionInfo implements DiagnosticPipe +class VersionInfo implements DiagnosticStringPipe { public function __construct( private InstalledVersion $installedVersion, diff --git a/app/Console/Commands/Diagnostics.php b/app/Console/Commands/Diagnostics.php index 93fc920940a..0c1ab8a04c1 100644 --- a/app/Console/Commands/Diagnostics.php +++ b/app/Console/Commands/Diagnostics.php @@ -7,6 +7,8 @@ use App\Actions\Diagnostics\Info; use App\Console\Commands\Utilities\Colorize; use App\Contracts\Exceptions\ExternalLycheeException; +use App\DTO\DiagnosticData; +use App\Enum\MessageType; use App\Exceptions\Internal\QueryBuilderException; use App\Exceptions\UnexpectedException; use Illuminate\Console\Command; @@ -65,6 +67,30 @@ private function block(string $str, array $array): void } } + /** + * Format the block. + * + * @param string $str + * @param DiagnosticData[] $array + */ + private function blockDiagnostic(string $str, array $array): void + { + $this->line($this->col->cyan($str)); + $this->line($this->col->cyan(str_pad('', strlen($str), '-'))); + + foreach ($array as $elem) { + $prefix = match ($elem->type) { + MessageType::ERROR => $this->col->red('Error: '), + MessageType::WARNING => $this->col->yellow('Warning: '), + default => $this->col->green('Info: '), + }; + $this->line($prefix . $elem->message); + foreach ($elem->details as $detail) { + $this->line(' ' . $detail); + } + } + } + /** * Execute the console command. * @@ -84,7 +110,7 @@ public function handle(): int try { $this->line(''); $this->line(''); - $this->block('Diagnostics', resolve(Errors::class)->get($skip_diagnostics)); + $this->blockDiagnostic('Smart Diagnostics', resolve(Errors::class)->get($skip_diagnostics)); $this->line(''); $this->block('System Information', resolve(Info::class)->get()); $this->line(''); diff --git a/app/Contracts/DiagnosticPipe.php b/app/Contracts/DiagnosticPipe.php index a5e5099258d..64f700fec5c 100644 --- a/app/Contracts/DiagnosticPipe.php +++ b/app/Contracts/DiagnosticPipe.php @@ -2,6 +2,8 @@ namespace App\Contracts; +use App\DTO\DiagnosticData; + /** * Basic definition of a Diagnostic pipe. * @@ -11,10 +13,10 @@ interface DiagnosticPipe { /** - * @param array &$data - * @param \Closure(array $data): array $next + * @param DiagnosticData[] &$data + * @param \Closure(DiagnosticData[] $data): DiagnosticData[] $next * - * @return array + * @return DiagnosticData[] */ public function handle(array &$data, \Closure $next): array; -} \ No newline at end of file +} diff --git a/app/Contracts/DiagnosticStringPipe.php b/app/Contracts/DiagnosticStringPipe.php new file mode 100644 index 00000000000..94f9dc0e0ea --- /dev/null +++ b/app/Contracts/DiagnosticStringPipe.php @@ -0,0 +1,26 @@ +type = self::TYPE_WARNING; - $this->line = \Str::substr($line, 9); - } - - if (\Str::startsWith($line, 'Info: ')) { - $this->type = self::TYPE_INFO; - $this->line = \Str::substr($line, 6); - } - - if (\Str::startsWith($line, 'Error: ')) { - $this->type = self::TYPE_ERROR; - $this->line = \Str::substr($line, 7); - } + /** + * Create a Diagnostic Info. + * + * @param MessageType $type + * @param string $message + * @param string $from + * @param string[] $details + */ + public function __construct( + public MessageType $type, + public string $message, + public string $from, + public array $details = [], + ) { } - public static function fromString(string $line): ErrorLine + public static function fromObject(DiagnosticData $line): ErrorLine { - return new self($line); + return new self( + $line->type, + $line->message, + str_replace('App\\Actions\\Diagnostics\\Pipes\\Checks\\', '', $line->from), + $line->details, + ); } } diff --git a/app/Legacy/V1/Controllers/Administration/DiagnosticsController.php b/app/Legacy/V1/Controllers/Administration/DiagnosticsController.php index d7b9c7a416f..2870b9bbddf 100644 --- a/app/Legacy/V1/Controllers/Administration/DiagnosticsController.php +++ b/app/Legacy/V1/Controllers/Administration/DiagnosticsController.php @@ -9,6 +9,8 @@ use App\Actions\InstallUpdate\CheckUpdate; use App\Constants\AccessPermissionConstants as APC; use App\Contracts\Exceptions\LycheeException; +use App\DTO\DiagnosticData; +use App\Enum\MessageType; use App\Exceptions\Internal\FrameworkException; use App\Exceptions\ModelDBException; use App\Exceptions\UnauthorizedException; @@ -56,13 +58,38 @@ public function get(): DiagnosticInfo $authorized = $this->isAuthorized(); - $errors = $collectErrors->get(config('app.skip_diagnostics_checks') ?? []); + $errors = $this->formatErrors($collectErrors->get(config('app.skip_diagnostics_checks') ?? [])); $infos = $authorized ? $collectInfo->get() : [self::ERROR_MSG]; $configs = $authorized ? $collectConfig->get() : [self::ERROR_MSG]; return new DiagnosticInfo($errors, $infos, $configs, $checkUpdate->getCode()); } + /** + * Format the block. + * + * @param DiagnosticData[] $array + * + * @return string[] list of messages + */ + private function formatErrors(array $array): array + { + $ret = []; + foreach ($array as $elem) { + $prefix = match ($elem->type) { + MessageType::ERROR => 'Error: ', + MessageType::WARNING => 'Warning: ', + default => 'Info: ', + }; + $ret[] = $prefix . $elem->message; + foreach ($elem->details as $detail) { + $ret[] = ' ' . $detail; + } + } + + return $ret; + } + /** * Return the diagnostic information as a page. * diff --git a/resources/js/components/diagnostics/ErrorsDiagnostics.vue b/resources/js/components/diagnostics/ErrorsDiagnostics.vue index 0f0c6a1fb18..893cdd2787e 100644 --- a/resources/js/components/diagnostics/ErrorsDiagnostics.vue +++ b/resources/js/components/diagnostics/ErrorsDiagnostics.vue @@ -1,9 +1,15 @@ @@ -15,13 +21,13 @@ import DiagnosticsService from "@/services/diagnostics-service"; const errors = ref(undefined); const emits = defineEmits<{ - loaded: [data: App.Http.Resources.Diagnostics.ErrorLine[]]; + loaded: [data: string[]]; }>(); function load() { DiagnosticsService.errors().then((response) => { errors.value = response.data; - emits("loaded", response.data); + emits("loaded", toArray(response.data)); }); } @@ -41,5 +47,26 @@ function getCss(type: string): string { return ""; } +function toArray(data: App.Http.Resources.Diagnostics.ErrorLine[]): string[] { + const result: string[] = []; + + data.forEach((error) => { + let prefix = ""; + if (error.type === "error") { + prefix = "Error: "; + } else if (error.type === "warning") { + prefix = "Warning: "; + } else if (error.type === "info") { + prefix = "Info: "; + } + result.push(prefix + error.message); + error.details.forEach((detail) => { + result.push(" " + detail); + }); + }); + + return result; +} + load(); diff --git a/resources/js/lychee.d.ts b/resources/js/lychee.d.ts index 78c22007661..d4caff0fbb5 100644 --- a/resources/js/lychee.d.ts +++ b/resources/js/lychee.d.ts @@ -72,6 +72,7 @@ declare namespace App.Enum { | "CC-BY-NC-SA-3.0" | "CC-BY-NC-SA-4.0"; export type MapProviders = "Wikimedia" | "OpenStreetMap.org" | "OpenStreetMap.de" | "OpenStreetMap.fr" | "RRZE"; + export type MessageType = "info" | "warning" | "error"; export type OauthProvidersType = | "amazon" | "apple" @@ -129,8 +130,10 @@ declare namespace App.Http.Resources.Diagnostics { is_not_empty: boolean; }; export type ErrorLine = { - type: string; - line: string; + type: App.Enum.MessageType; + message: string; + from: string; + details: Array; }; export type Permissions = { left: string; diff --git a/resources/js/views/Diagnostics.vue b/resources/js/views/Diagnostics.vue index 580e8fe2221..16b793cb062 100644 --- a/resources/js/views/Diagnostics.vue +++ b/resources/js/views/Diagnostics.vue @@ -38,13 +38,13 @@ const togglableStore = useTogglablesStateStore(); const toast = useToast(); -const errorLoaded = ref(undefined); +const errorLoaded = ref(undefined); const infoLoaded = ref(undefined); const configurationLoaded = ref(undefined); const canCopy = computed(() => errorLoaded.value !== undefined && infoLoaded.value !== undefined && configurationLoaded.value !== undefined); -function loadError(val: App.Http.Resources.Diagnostics.ErrorLine[]) { +function loadError(val: string[]) { errorLoaded.value = val; } @@ -61,14 +61,11 @@ function copy() { return; } - const errorText = errorLoaded.value - ?.filter((errorLine) => errorLine.type !== undefined) - .map((errorLines) => `${errorLines.type.padEnd(7)}: ${errorLines.line}`) - .join("\n"); - const infoText = infoLoaded.value?.join("\n"); - const configurationText = configurationLoaded.value?.join("\n"); + const errorText: string = errorLoaded.value?.join("\n") ?? ""; + const infoText: string = infoLoaded.value?.join("\n") ?? ""; + const configurationText: string = configurationLoaded.value?.join("\n") ?? ""; - const toClipBoard = `Errors:\n${"-".repeat(20)}\n${errorText}\n\n\nInfo:\n${"-".repeat(20)}\n${infoText}\n\n\nConfig:\n${"-".repeat(20)}\n${configurationText}`; + const toClipBoard = `Self-diagnosis:\n${"-".repeat(20)}\n${errorText}\n\n\nInfo:\n${"-".repeat(20)}\n${infoText}\n\n\nConfig:\n${"-".repeat(20)}\n${configurationText}`; navigator.clipboard .writeText(toClipBoard)