diff --git a/app/Actions/Diagnostics/Errors.php b/app/Actions/Diagnostics/Errors.php index b54186dc43..9a97da6ee4 100644 --- a/app/Actions/Diagnostics/Errors.php +++ b/app/Actions/Diagnostics/Errors.php @@ -14,6 +14,7 @@ use App\Actions\Diagnostics\Pipes\Checks\IniSettingsCheck; use App\Actions\Diagnostics\Pipes\Checks\MigrationCheck; use App\Actions\Diagnostics\Pipes\Checks\PHPVersionCheck; +use App\Actions\Diagnostics\Pipes\Checks\SmallMediumExistsCheck; use App\Actions\Diagnostics\Pipes\Checks\TimezoneCheck; use App\Actions\Diagnostics\Pipes\Checks\UpdatableCheck; use Illuminate\Pipeline\Pipeline; @@ -40,6 +41,7 @@ class Errors UpdatableCheck::class, ForeignKeyListInfo::class, DBIntegrityCheck::class, + SmallMediumExistsCheck::class, ]; /** diff --git a/app/Actions/Diagnostics/Pipes/Checks/SmallMediumExistsCheck.php b/app/Actions/Diagnostics/Pipes/Checks/SmallMediumExistsCheck.php new file mode 100644 index 0000000000..b5973fd2ca --- /dev/null +++ b/app/Actions/Diagnostics/Pipes/Checks/SmallMediumExistsCheck.php @@ -0,0 +1,135 @@ +selectSub( + SizeVariant::query() + ->selectRaw('COUNT(*)') + ->where('type', '=', SizeVariantType::SMALL), + self::NUM_SMALL + ) + ->selectSub( + SizeVariant::query() + ->selectRaw('COUNT(*)') + ->where('type', '=', SizeVariantType::SMALL2X), + self::NUM_SMALL2X + ) + ->selectSub( + SizeVariant::query() + ->selectRaw('COUNT(*)') + ->where('type', '=', SizeVariantType::MEDIUM), + self::NUM_MEDIUM + ) + ->selectSub( + SizeVariant::query() + ->selectRaw('COUNT(*)') + ->where('type', '=', SizeVariantType::MEDIUM2X), + self::NUM_MEDIUM2X + ) + ->selectSub( + SizeVariant::query() + ->selectRaw('COUNT(*)') + ->where(fn ($q) => $q + ->when($svHelpers->getMaxWidth(SizeVariantType::SMALL) !== 0, fn ($q1) => $q1->where('width', '>', $svHelpers->getMaxWidth(SizeVariantType::SMALL))) + ->when($svHelpers->getMaxHeight(SizeVariantType::SMALL) !== 0, fn ($q2) => $q2->orWhere('height', '>', $svHelpers->getMaxHeight(SizeVariantType::SMALL))) + ) + ->where('type', '=', SizeVariantType::ORIGINAL), + self::MAX_NUM_SMALL + ) + ->selectSub( + SizeVariant::query() + ->selectRaw('COUNT(*)') + ->where(fn ($q) => $q + ->when($svHelpers->getMaxWidth(SizeVariantType::SMALL2X) !== 0, fn ($q1) => $q1->where('width', '>', $svHelpers->getMaxWidth(SizeVariantType::SMALL2X))) + ->when($svHelpers->getMaxHeight(SizeVariantType::SMALL2X) !== 0, fn ($q2) => $q2->orWhere('height', '>', $svHelpers->getMaxHeight(SizeVariantType::SMALL2X))) + ) + ->where('type', '=', SizeVariantType::ORIGINAL), + self::MAX_NUM_SMALL2X + ) + ->selectSub( + SizeVariant::query() + ->selectRaw('COUNT(*)') + ->where(fn ($q) => $q + ->when($svHelpers->getMaxWidth(SizeVariantType::MEDIUM) !== 0, fn ($q1) => $q1->where('width', '>', $svHelpers->getMaxWidth(SizeVariantType::MEDIUM))) + ->when($svHelpers->getMaxHeight(SizeVariantType::MEDIUM) !== 0, fn ($q2) => $q2->orWhere('height', '>', $svHelpers->getMaxHeight(SizeVariantType::MEDIUM))) + ) + ->where('type', '=', SizeVariantType::ORIGINAL), + self::MAX_NUM_MEDIUM + ) + ->selectSub( + SizeVariant::query() + ->selectRaw('COUNT(*)') + ->where(fn ($q) => $q + ->when($svHelpers->getMaxWidth(SizeVariantType::MEDIUM2X) !== 0, fn ($q1) => $q1->where('width', '>', $svHelpers->getMaxWidth(SizeVariantType::MEDIUM2X))) + ->when($svHelpers->getMaxHeight(SizeVariantType::MEDIUM2X) !== 0, fn ($q2) => $q2->orWhere('height', '>', $svHelpers->getMaxHeight(SizeVariantType::MEDIUM2X))) + ) + ->where('type', '=', SizeVariantType::ORIGINAL), + self::MAX_NUM_MEDIUM2X + ) + ->first(); + + $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); + } + + $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); + } + + $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); + } + + $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); + } + + return $next($data); + } +} diff --git a/app/Image/SizeVariantDefaultFactory.php b/app/Image/SizeVariantDefaultFactory.php index 021eaa71b8..ee8cbbf7ec 100644 --- a/app/Image/SizeVariantDefaultFactory.php +++ b/app/Image/SizeVariantDefaultFactory.php @@ -19,7 +19,6 @@ use App\Image\Files\TemporaryLocalFile; use App\Image\Handlers\ImageHandler; use App\Image\Handlers\VideoHandler; -use App\Models\Configs; use App\Models\Photo; use App\Models\SizeVariant; use Illuminate\Contracts\Container\BindingResolutionException; @@ -34,12 +33,15 @@ class SizeVariantDefaultFactory implements SizeVariantFactory protected ImageHandlerInterface $referenceImage; protected ?Photo $photo = null; protected ?AbstractSizeVariantNamingStrategy $namingStrategy = null; + protected ?SizeVariantDimensionHelpers $svDimensionHelpers = null; /** * {@inheritDoc} */ public function init(Photo $photo, ?ImageHandlerInterface $referenceImage = null, ?AbstractSizeVariantNamingStrategy $namingStrategy = null): void { + $this->svDimensionHelpers = new SizeVariantDimensionHelpers(); + try { $this->photo = $photo; if ($referenceImage !== null && $referenceImage->isLoaded()) { @@ -130,7 +132,7 @@ public function createSizeVariantCond(SizeVariantType $sizeVariant): ?SizeVarian if ($sizeVariant === SizeVariantType::ORIGINAL) { throw new InvalidSizeVariantException('createSizeVariantCond() must not be used to create original size'); } - if (!$this->isEnabledByConfiguration($sizeVariant)) { + if (!$this->svDimensionHelpers->isEnabledByConfiguration($sizeVariant)) { return null; } // Don't generate medium size variants for videos, because the current web front-end has no use for it. Let's save some storage space. @@ -142,16 +144,10 @@ public function createSizeVariantCond(SizeVariantType $sizeVariant): ?SizeVarian return null; } - $maxDim = $this->getMaxDimensions($sizeVariant); + $maxDim = $this->svDimensionHelpers->getMaxDimensions($sizeVariant); $realDim = $this->referenceImage->getDimensions(); - $isLargeEnough = match ($sizeVariant) { - SizeVariantType::THUMB => true, - SizeVariantType::THUMB2X => $realDim->width >= $maxDim->width && $realDim->height >= $maxDim->height, - default => ($realDim->width >= $maxDim->width && $maxDim->width !== 0) || ($realDim->height >= $maxDim->height && $maxDim->height !== 0) - }; - - return $isLargeEnough ? + return $this->svDimensionHelpers->isLargeEnough($realDim, $maxDim, $sizeVariant) ? $this->createSizeVariantInternal($sizeVariant, $maxDim) : null; } @@ -194,96 +190,4 @@ private function createSizeVariantInternal(SizeVariantType $sizeVariant, ImageDi $svFile->getFilesize() ); } - - /** - * Determines the maximum dimensions of the designated size variant. - * - * @param SizeVariantType $sizeVariant the size variant - * - * @return ImageDimension - * - * @throws InvalidSizeVariantException - */ - protected function getMaxDimensions(SizeVariantType $sizeVariant): ImageDimension - { - $maxWidth = $this->getMaxWidth($sizeVariant); - $maxHeight = $this->getMaxHeight($sizeVariant); - - return new ImageDimension($maxWidth, $maxHeight); - } - - /** - * Checks whether the requested size variant is enabled by configuration. - * - * This function always returns true, for size variants which are not - * configurable and are always enabled (e.g. a thumb). - * Hence, it is safe to call this function for all size variants. - * For size variants which may be enabled/disabled through configuration at - * runtime, the method only returns true, if - * - * 1. the size variant is enabled, and - * 2. the allowed maximum width or maximum height is not zero. - * - * In other words, even if a size variant is enabled, this function - * still returns false, if both the allowed maximum width and height - * equal zero. - * - * @param SizeVariantType $sizeVariant the indicated size variant - * - * @return bool true, if the size variant is enabled and the allowed width - * or height is unequal to zero - * - * @throws InvalidSizeVariantException - */ - protected function isEnabledByConfiguration(SizeVariantType $sizeVariant): bool - { - $maxDim = $this->getMaxDimensions($sizeVariant); - if ($maxDim->width === 0 && $maxDim->height === 0) { - return false; - } - - return match ($sizeVariant) { - SizeVariantType::MEDIUM2X => Configs::getValueAsBool('medium_2x'), - SizeVariantType::SMALL2X => Configs::getValueAsBool('small_2x'), - SizeVariantType::THUMB2X => Configs::getValueAsBool('thumb_2x'), - SizeVariantType::SMALL, SizeVariantType::MEDIUM, SizeVariantType::THUMB => true, - default => throw new InvalidSizeVariantException('unknown size variant: ' . $sizeVariant->value), - }; - } - - /** - * Return the max width for the SizeVariant. - * - * @return int - */ - protected function getMaxWidth(SizeVariantType $sizeVariant): int - { - return match ($sizeVariant) { - SizeVariantType::MEDIUM2X => 2 * Configs::getValueAsInt('medium_max_width'), - SizeVariantType::MEDIUM => Configs::getValueAsInt('medium_max_width'), - SizeVariantType::SMALL2X => 2 * Configs::getValueAsInt('small_max_width'), - SizeVariantType::SMALL => Configs::getValueAsInt('small_max_width'), - SizeVariantType::THUMB2X => self::THUMBNAIL2X_DIM, - SizeVariantType::THUMB => self::THUMBNAIL_DIM, - default => throw new InvalidSizeVariantException('No applicable for original'), - }; - } - - /** - * Return the max height for the SizeVariant. - * - * @return int - */ - protected function getMaxHeight(SizeVariantType $sizeVariant): int - { - return match ($sizeVariant) { - SizeVariantType::MEDIUM2X => 2 * Configs::getValueAsInt('medium_max_height'), - SizeVariantType::MEDIUM => Configs::getValueAsInt('medium_max_height'), - SizeVariantType::SMALL2X => 2 * Configs::getValueAsInt('small_max_height'), - SizeVariantType::SMALL => Configs::getValueAsInt('small_max_height'), - SizeVariantType::THUMB2X => self::THUMBNAIL2X_DIM, - SizeVariantType::THUMB => self::THUMBNAIL_DIM, - default => throw new InvalidSizeVariantException('unknown size variant: ' . $sizeVariant->value), - }; - } } diff --git a/app/Image/SizeVariantDimensionHelpers.php b/app/Image/SizeVariantDimensionHelpers.php new file mode 100644 index 0000000000..367bd7bea6 --- /dev/null +++ b/app/Image/SizeVariantDimensionHelpers.php @@ -0,0 +1,122 @@ +getMaxWidth($sizeVariant); + $maxHeight = $this->getMaxHeight($sizeVariant); + + return new ImageDimension($maxWidth, $maxHeight); + } + + /** + * Checks whether the requested size variant is enabled by configuration. + * + * This function always returns true, for size variants which are not + * configurable and are always enabled (e.g. a thumb). + * Hence, it is safe to call this function for all size variants. + * For size variants which may be enabled/disabled through configuration at + * runtime, the method only returns true, if + * + * 1. the size variant is enabled, and + * 2. the allowed maximum width or maximum height is not zero. + * + * In other words, even if a size variant is enabled, this function + * still returns false, if both the allowed maximum width and height + * equal zero. + * + * @param SizeVariantType $sizeVariant the indicated size variant + * + * @return bool true, if the size variant is enabled and the allowed width + * or height is unequal to zero + * + * @throws InvalidSizeVariantException + */ + public function isEnabledByConfiguration(SizeVariantType $sizeVariant): bool + { + $maxDim = $this->getMaxDimensions($sizeVariant); + if ($maxDim->width === 0 && $maxDim->height === 0) { + return false; + } + + return match ($sizeVariant) { + SizeVariantType::MEDIUM2X => Configs::getValueAsBool('medium_2x'), + SizeVariantType::SMALL2X => Configs::getValueAsBool('small_2x'), + SizeVariantType::THUMB2X => Configs::getValueAsBool('thumb_2x'), + SizeVariantType::SMALL, SizeVariantType::MEDIUM, SizeVariantType::THUMB => true, + default => throw new InvalidSizeVariantException('unknown size variant: ' . $sizeVariant->value), + }; + } + + /** + * Given dimension and SizeVariant type, provide a check whether a SizeVariant should be created + * under the constraints provided. + * + * @param ImageDimension $realDim the dimension of original + * @param ImageDimension $maxDim the max dimension of target size variant + * @param SizeVariantType $sizeVariant type of size variant to be created + * + * @return bool true, if the size is big enough for creation + */ + public function isLargeEnough(ImageDimension $realDim, ImageDimension $maxDim, SizeVariantType $sizeVariant): bool + { + return match ($sizeVariant) { + SizeVariantType::THUMB => true, + SizeVariantType::THUMB2X => $realDim->width >= $maxDim->width && $realDim->height >= $maxDim->height, + default => ($realDim->width >= $maxDim->width && $maxDim->width !== 0) || ($realDim->height >= $maxDim->height && $maxDim->height !== 0) + }; + } + + /** + * Return the max width for the SizeVariant. + * + * @return int + */ + public function getMaxWidth(SizeVariantType $sizeVariant): int + { + return match ($sizeVariant) { + SizeVariantType::MEDIUM2X => 2 * Configs::getValueAsInt('medium_max_width'), + SizeVariantType::MEDIUM => Configs::getValueAsInt('medium_max_width'), + SizeVariantType::SMALL2X => 2 * Configs::getValueAsInt('small_max_width'), + SizeVariantType::SMALL => Configs::getValueAsInt('small_max_width'), + SizeVariantType::THUMB2X => SizeVariantDefaultFactory::THUMBNAIL2X_DIM, + SizeVariantType::THUMB => SizeVariantDefaultFactory::THUMBNAIL_DIM, + default => throw new InvalidSizeVariantException('No applicable for original'), + }; + } + + /** + * Return the max height for the SizeVariant. + * + * @return int + */ + public function getMaxHeight(SizeVariantType $sizeVariant): int + { + return match ($sizeVariant) { + SizeVariantType::MEDIUM2X => 2 * Configs::getValueAsInt('medium_max_height'), + SizeVariantType::MEDIUM => Configs::getValueAsInt('medium_max_height'), + SizeVariantType::SMALL2X => 2 * Configs::getValueAsInt('small_max_height'), + SizeVariantType::SMALL => Configs::getValueAsInt('small_max_height'), + SizeVariantType::THUMB2X => SizeVariantDefaultFactory::THUMBNAIL2X_DIM, + SizeVariantType::THUMB => SizeVariantDefaultFactory::THUMBNAIL_DIM, + default => throw new InvalidSizeVariantException('unknown size variant: ' . $sizeVariant->value), + }; + } +} \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon index 1d63ac31f7..6ac812e628 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -46,6 +46,7 @@ parameters: - '#Dynamic call to static method (Illuminate\\Database\\Query\\Builder|Illuminate\\Database\\Eloquent\\(Builder|Relations\\.*)|App\\Models\\Builders\\.*|App\\Eloquent\\FixedQueryBuilder)(<.*>)?::inRandomOrder\(\).#' - '#Dynamic call to static method (Illuminate\\Database\\Query\\Builder|Illuminate\\Database\\Eloquent\\(Builder|Relations\\.*)|App\\Models\\Builders\\.*|App\\Eloquent\\FixedQueryBuilder)(<.*>)?::groupBy\(\).#' - '#Dynamic call to static method App\\Models\\Builders\\.*::orderByDesc\(\).#' + - '#Dynamic call to static method App\\Models\\Builders\\.*::selectRaw\(\).#' - '#Call to an undefined method Illuminate\\Database\\Eloquent\\.*::update\(\)#' - '#Call to an undefined method Illuminate\\Database\\Eloquent\\.*::with(Only)?\(\)#' - '#Call to an undefined method App\\Relations\\HasManyPhotosRecursively::whereNotNull\(\)#'