diff --git a/Makefile b/Makefile index 2c94220..004f41f 100644 --- a/Makefile +++ b/Makefile @@ -11,11 +11,11 @@ VENDOR_DIR := vendor VENDOR_BIN_DIR := $(VENDOR_DIR)/bin CODE_CHECKER := nette/code-checker -CODE_CHECKER_VERSION := ~3.2.3 +CODE_CHECKER_VERSION := ~3.3.0 CODE_CHECKER_DIR := $(TEMP_DIR)/code-checker COVERALLS := php-coveralls/php-coveralls -COVERALLS_VERSION := ^2.5.3 +COVERALLS_VERSION := ^2.5 COVERALLS_DIR := $(TEMP_DIR)/coveralls ifeq ($(CI), 1) diff --git a/README.md b/README.md index 878bf9a..3e8f9ea 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ There is `Image storage` for storing images easily and/or deleting them from the There are also several ways how to resize and/or process images. Then, you can get a stored image path directly, or you can use prepared [Latte](https://latte.nette.org) macros to generate HTML tags. See [Usage](#Usage). -**Requires the PHP version `7.4` or newer and PHP extensions `gd` and `fileinfo`.** +**Requires the PHP version `8.2` or newer and PHP extensions `fileinfo`, `gd`, and `intl`.** ## Installation @@ -32,14 +32,11 @@ composer require harmim/images ## Usage -For working with images, we need `Harmim\Images\ImageStorage`: +For working with images, we need `\Harmim\Images\ImageStorage`: ### Without Nette ```php -use Harmim\Images\DI\ImagesExtension; -use Harmim\Images\ImageStorage; - $customConfig = [ 'wwwDir' => __DIR__ . DIRECTORY_SEPARATOR . 'www', 'compression' => 90, @@ -48,17 +45,16 @@ $customConfig = [ 'img-small' => [ 'width' => 50, 'height' => 50, - 'transform' => ImageStorage::RESIZE_EXACT, + 'transform' => \Harmim\Images\Resize::EXACT, ... ], ... ], ... ]; - -$imageStorage = new ImageStorage(array_merge_recursive( - ImagesExtension::DEFAULTS, +$imageStorage = new \Harmim\Images\ImageStorage(\Nette\Utils\Arrays::mergeTree( $customConfig, + \Harmim\Images\DI\ImagesExtension::DEFAULTS, )); ``` @@ -69,7 +65,7 @@ In `$customConfig`, you can specify a custom configuration. See [Configuration]( You can enable and customise the extension using your NEON config: ```neon extensions: - images: Harmim\Images\DI\ImagesExtension + images: \Harmim\Images\DI\ImagesExtension images: compression: 90 @@ -78,7 +74,7 @@ images: img-small: width: 50 height: 50 - transform: Harmim\Images\ImageStorage::RESIZE_EXACT + transform: ::constant(\Harmim\Images\Resize::EXACT) ... ... ... @@ -86,36 +82,29 @@ images: In the `images` section, you can specify a custom configuration. See [Configuration](#Configuration). -`Harmim\Images\ImageStorage` is now registrated in the DI container. You can get it directly from the container: +`\Harmim\Images\ImageStorage` is now registrated in the DI container. You can get it directly from the container: ```php -use Harmim\Images\ImageStorage; - -/** @var Nette\DI\Container $container */ - +/** @var \Nette\DI\Container $container */ $imageStorage = $container->getService('images.imageStorage'); // or -$imageStorage = $container->getByType(ImageStorage::class); +$imageStorage = $container->getByType(\Harmim\Images\ImageStorage::class); ``` -Of course, you can inject `Harmim\Images\ImageStorage` through a constructor, inject method, inject annotation, or +Of course, you can inject `\Harmim\Images\ImageStorage` through a constructor, inject method, inject annotation, or any other way. -If you want to use `Harmim\Images\ImageStorage` in a presenter or control where inject methods are called, you -can use trait `Harmim\Images\TImageStorage`. In your presenters, controls, and theire templates, there will be +If you want to use `\Harmim\Images\ImageStorage` in a presenter or control where inject methods are called, you +can use trait `\Harmim\Images\TImageStorage`. In your presenters, controls, and theire templates, there will be variable `$imageStorage`. ```php -use Harmim\Images\TImageStorage -use Nette\Application\UI\Control; -use Nette\Application\UI\Presenter; - -abstract class BasePresenter extends Presenter +abstract class BasePresenter extends \Nette\Application\UI\Presenter { - use TImageStorage; + use \Harmim\Images\TImageStorage; } -abstract class BaseControl extends Control +abstract class BaseControl extends \Nette\Application\UI\Control { - use TImageStorage; + use \Harmim\Images\TImageStorage; } ``` @@ -123,8 +112,8 @@ The extension installs images macros to Latte. See [Macros](#Macros). ### Storing Images -You can store an image using method `Harmim\Images\ImageStorage::saveImage(string $name, string $path): string` or -`Harmim\Images\ImageStorage::saveUpload(Nette\Http\FileUpload $file): string`. An original image will be stored; +You can store an image using method `\Harmim\Images\ImageStorage::saveImage(string $name, string $path): string` or +`\Harmim\Images\ImageStorage::saveUpload(\Nette\Http\FileUpload $file): string`. An original image will be stored; then, it will be compresed. Both methods return a stored image file name. You can use this file name to delete, resize, or retrieve the image. @@ -133,9 +122,9 @@ Images are stored with a unique file name and location. ### Deleting Images -Using method `Harmim\Images\ImageStorage::deleteImage(string $fileName, array $excludedTypes = []): void`, -you can delete an image by `$fileName` which should be a file name returned by `Harmim\Images\ImageStorage::saveImage` -or `Harmim\Images\ImageStorage::saveUpload`. +Using method `\Harmim\Images\ImageStorage::deleteImage(string $fileName, array $excludedTypes = []): void`, +you can delete an image by `$fileName` which should be a file name returned by `\Harmim\Images\ImageStorage::saveImage` +or `\Harmim\Images\ImageStorage::saveUpload`. If you pass `$excludedTypes`, only other types will be deleted; otherwise, all types, the original image, and the compressed image will be deleted. @@ -143,22 +132,26 @@ the compressed image will be deleted. ### Getting Stored Images' Paths You can get a stored image path using method -`Harmim\Images\ImageStorage::getImageLink(string $fileName, ?string $type = null, array $options = []): ?Harmim\Images\Image` -or [Macros](#Macros). You can pass a specific type defined in inital options, or you can pass specific options. -See [Configuration](#Configuration). `$fileName` should be a file name returned by -`Harmim\Images\ImageStorage::saveImage`or `Harmim\Images\ImageStorage::saveUpload`. +`\Harmim\Images\ImageStorage::getImageLink(string $fileName, ?string $type = null, array $config = []): ?\Harmim\Images\Image` +or [Macros](#Macros). You can pass a specific type defined in an inital configuration, or you can pass a specific +configuration. See [Configuration](#Configuration). `$fileName` should be a file name returned by +`\Harmim\Images\ImageStorage::saveImage`or `\Harmim\Images\ImageStorage::saveUpload`. If you try to get an image of a size or a type for a first time, this image is not yet created, so it will be created now. Next time, you will get a resized image. If the image does not exist, a placeholder will be returned. +In case you need to get an original/compressed image, in the configuration, you can use the +`\Harmim\Images\ImageStorage::RETURN_ORIG/RETURN_COMPRESSED` constant, respectively. For example, +`[\Harmim\Images\ImageStorage::RETURN_ORIG => true]`. It is also possible to use these options in macros. + ### Macros #### `img` ```latte -{img [$image] [image-type] [options]} +{img [$image] [image-type] [config]} ``` Renders the `img` tag: ```html @@ -189,7 +182,7 @@ Examples: #### `n:img` ```latte -foo +foo ``` Renders the `src` attribute. It can be used, e.g., in the `img` element. @@ -209,7 +202,7 @@ Examples: #### `imgLink` ```latte -{imgLink [$image] [image-type] [options]} +{imgLink [$image] [image-type] [config]} ``` Returns a relative path (from the resource root directory) to a given image. @@ -245,26 +238,16 @@ Examples: * Default: `1024`. - `height`: (`int`) An image height. * Default: `1024`. -- `compression`: (`int`) A compression quality. See `Nette\Utils\Image::save`. +- `compression`: (`int`) A compression quality. See `\Nette\Utils\Image::save`. * Default: `85`. -- `transform`: (`string`) One of `Harmim\Images\ImageStorage::RESIZE_...` constants, or more constants separated by `|`: - -| Option | Description | -|-----------------------|-------------------------------------------------------------------------------| -| `RESIZE_SHRINK_ONLY` | Only shrinking (prevents a small image from being stretched). | -| `RESIZE_STRETCH` | Do not keep the aspect ratio. | -| `RESIZE_FIT` | The resulting dimensions will be smaller or equal to the required dimensions. | -| `RESIZE_FILL` | Fills (and possibly exceeds in one dimension) the target area. | -| `RESIZE_EXACT` | Fills the target area and cuts off what goes beyond. | -| `RESIZE_FILL_EXACT` | Placees a not stretched image to the exact blank area. | - - * Default: ` Harmim\Images\ImageStorage::RESIZE_FIT`. -- `imgTagAttributes`: (`array`) `img` attributes you can use in the `{img}` Latte macro, other attributes are ignored. +- `transform`: (`\Harmim\Images\Resize|list<\Harmim\Images\Resize>`) See [Transform-Options](#Transform-Options). + * Default: `\Harmim\Images\Resize::OR_SMALLER`. +- `imgTagAttributes`: (`list`) `img` attributes you can use in the `{img}` Latte macro, other attributes + are ignored. * Default: `[alt, height, width, class, hidden, id, style, title, data]`. -- `types`: (`array`) A configuration for image types overriding the default configuration. +- `types`: (`array`) A configuration for image types overriding the default configuration. * Default: `[]`. * Example: - ```neon types: img-small: @@ -272,13 +255,25 @@ types: height: 50 img-gallery: lazy: true - transform: Harmim\Images\ImageStorage::RESIZE_STRETCH + transform: + - ::constant(\Harmim\Images\Resize::STRETCH) + - ::constant(\Harmim\Images\Resize::COVER) ``` - - `lazy`: (`bool`) Render the `{img}` Latte macro as a lazy image (with the `data-src` attribute, `lazy` class, and normal `img` tag in the `noscript` tag). * Default: `false`. +### Transform-Options + +| Option | Description | +|--------------------------------------|-------------------------------------------------------------------------------| +| `\Harmim\Images\Resize::SHRINK_ONLY` | Only shrinking (prevents a small image from being stretched). | +| `\Harmim\Images\Resize::STRETCH` | Do not keep the aspect ratio. | +| `\Harmim\Images\Resize::OR_SMALLER` | The resulting dimensions will be smaller or equal to the required dimensions. | +| `\Harmim\Images\Resize::OR_BIGGER` | Fills (and possibly exceeds in one dimension) the target area. | +| `\Harmim\Images\Resize::COVER` | Fills the target area and cuts off what goes beyond. | +| `\Harmim\Images\Resize::EXACT` | Placees a not stretched image to the exact blank area. | + ## License diff --git a/composer.json b/composer.json index 81e806b..a74289d 100644 --- a/composer.json +++ b/composer.json @@ -14,26 +14,30 @@ } ], "require": { - "php": ">=7.4", - "ext-gd": "*", + "php": ">=8.2", "ext-fileinfo": "*", - "nette/di": "^2.4", - "nette/utils": "^2.4", - "latte/latte": "^2.4", - "nette/http": "^2.4", - "nette/finder": "^2.4" + "ext-gd": "*", + "ext-intl": "*", + "latte/latte": "^3.0", + "nette/di": "^3.1", + "nette/http": "^3.2", + "nette/utils": "^4.0" }, "require-dev": { - "nette/tester": "^2.0" + "nette/application": "^3.1", + "nette/tester": "^2.5" + }, + "suggest": { + "nette/application": "To use the 'Harmim\\Images\\TImageStorage' trait." }, "autoload": { "psr-4": { - "Harmim\\Images\\": "src/" + "Harmim\\Images\\": "src" } }, "autoload-dev": { "psr-4": { - "Harmim\\Tests\\": "tests/" + "Harmim\\Tests\\": "tests" } } } diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 9513464..3d42f17 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -1,17 +1,21 @@ # Author: Dominik Harmim -FROM php:7.4-cli +FROM php:8.2-cli RUN apt-get update -y && \ apt-get install -y \ git \ zip \ libjpeg-dev \ - libpng-dev && \ + libpng-dev \ + libicu-dev && \ apt-get clean -y -RUN docker-php-ext-configure gd --with-jpeg -RUN docker-php-ext-install gd +RUN docker-php-ext-configure gd --with-jpeg && \ + docker-php-ext-configure intl +RUN docker-php-ext-install \ + gd \ + intl RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \ php composer-setup.php --install-dir=/usr/bin --filename=composer --quiet && \ diff --git a/src/DI/ImagesExtension.php b/src/DI/ImagesExtension.php index bc826ca..662670e 100644 --- a/src/DI/ImagesExtension.php +++ b/src/DI/ImagesExtension.php @@ -23,35 +23,62 @@ class ImagesExtension extends Nette\DI\CompilerExtension 'width' => 1_024, 'height' => 1_024, 'compression' => 85, - 'transform' => Harmim\Images\ImageStorage::RESIZE_FIT, + 'transform' => Harmim\Images\Resize::OR_SMALLER, 'imgTagAttributes' => ['alt', 'height', 'width', 'class', 'hidden', 'id', 'style', 'title', 'data'], 'types' => [], 'lazy' => false, ]; - public function loadConfiguration(): void + public function getConfigSchema(): Nette\Schema\Schema { - $this->getContainerBuilder() - ->addDefinition($this->prefix('imageStorage')) - ->setFactory(Harmim\Images\ImageStorage::class) - ->setArguments([$this->getSettings()]); + $configItems = [ + 'wwwDir' => Nette\Schema\Expect::string(static::DEFAULTS['wwwDir']), + 'imagesDir' => Nette\Schema\Expect::string(static::DEFAULTS['imagesDir']), + 'origDir' => Nette\Schema\Expect::string(static::DEFAULTS['origDir']), + 'compressionDir' => Nette\Schema\Expect::string(static::DEFAULTS['compressionDir']), + 'placeholder' => Nette\Schema\Expect::string(static::DEFAULTS['placeholder']), + 'width' => Nette\Schema\Expect::int(static::DEFAULTS['width'])->min(1), + 'height' => Nette\Schema\Expect::int(static::DEFAULTS['height'])->min(1), + 'compression' => Nette\Schema\Expect::int(static::DEFAULTS['compression'])->min(0)->max(100), + 'transform' => Nette\Schema\Expect::anyOf( + Nette\Schema\Expect::type(Harmim\Images\Resize::class)->dynamic(), + Nette\Schema\Expect::listOf(Nette\Schema\Expect::type(Harmim\Images\Resize::class)->dynamic()), + )->default(static::DEFAULTS['transform']), + 'imgTagAttributes' => Nette\Schema\Expect::listOf(Nette\Schema\Expect::string())->default( + static::DEFAULTS['imgTagAttributes'], + ), + 'lazy' => Nette\Schema\Expect::bool(static::DEFAULTS['lazy']), + ]; + + return Nette\Schema\Expect::structure($configItems + [ + 'types' => Nette\Schema\Expect::arrayOf( + Nette\Schema\Expect::structure($configItems)->skipDefaults()->castTo('array'), + Nette\Schema\Expect::string(), + )->default(static::DEFAULTS['types']), + ]); } - public function beforeCompile(): void + public function loadConfiguration(): void { + /** @noinspection PhpInternalEntityUsedInspection */ + $this->config->wwwDir = Nette\DI\Helpers::expand( + $this->config->wwwDir, $this->getContainerBuilder()->parameters, + ); + $this->getContainerBuilder() - ->getDefinition('latte.latteFactory') - ->addSetup(Harmim\Images\Template\Macros::class . '::install(?->getCompiler())', ['@self']); + ->addDefinition($this->prefix('imageStorage')) + ->setFactory(Harmim\Images\ImageStorage::class) + ->setArguments([(array) $this->config]); } - private function getSettings(): array + public function beforeCompile(): void { - $config = $this->validateConfig(static::DEFAULTS, $this->config); - $config['wwwDir'] = Nette\DI\Helpers::expand($config['wwwDir'], $this->getContainerBuilder()->parameters); - - return $config; + /** @var Nette\DI\Definitions\FactoryDefinition $latteFactory */ + $latteFactory = $this->getContainerBuilder() + ->getDefinitionByType(Nette\Bridges\ApplicationLatte\LatteFactory::class); + $latteFactory->getResultDefinition()->addSetup('addExtension', [new Harmim\Images\Latte\ImagesExtension()]); } } diff --git a/src/Image.php b/src/Image.php index 3712d23..9e3ba6a 100644 --- a/src/Image.php +++ b/src/Image.php @@ -12,27 +12,17 @@ /** - * @property string $src - * @property int $width - * @property int $height + * @property-read string $src + * @property-read int $width + * @property-read int $height */ -class Image +final readonly class Image implements \Stringable { use Nette\SmartObject; - private string $src; - - private int $width; - - private int $height; - - - public function __construct(string $src, int $width, int $height) + public function __construct(private string $src, private int $width, private int $height) { - $this->src = $src; - $this->width = $width; - $this->height = $height; } @@ -42,42 +32,18 @@ public function getSrc(): string } - public function setSrc(string $src): self - { - $this->src = $src; - - return $this; - } - - public function getWidth(): int { return $this->width; } - public function setWidth(int $width): self - { - $this->width = $width; - - return $this; - } - - public function getHeight(): int { return $this->height; } - public function setHeight(int $height): self - { - $this->height = $height; - - return $this; - } - - public function __toString(): string { return $this->src; diff --git a/src/ImageStorage.php b/src/ImageStorage.php index b32cf89..07173a3 100644 --- a/src/ImageStorage.php +++ b/src/ImageStorage.php @@ -11,32 +11,17 @@ use Nette; -class ImageStorage +readonly class ImageStorage { - use Nette\SmartObject; - - - public const RETURN_ORIG = 'return_orig', + final public const + RETURN_ORIG = 'return_orig', RETURN_COMPRESSED = 'return_compressed'; - public const RESIZE_SHRINK_ONLY = 'shrink_only', - RESIZE_STRETCH = 'stretch', - RESIZE_FIT = 'fit', - RESIZE_FILL = 'fill', - RESIZE_EXACT = 'exact', - RESIZE_FILL_EXACT = 'fill_exact'; - - private const RESIZE_FLAGS = [ - self::RESIZE_SHRINK_ONLY => Nette\Utils\Image::SHRINK_ONLY, - self::RESIZE_STRETCH => Nette\Utils\Image::STRETCH, - self::RESIZE_FIT => Nette\Utils\Image::FIT, - self::RESIZE_FILL => Nette\Utils\Image::FILL, - self::RESIZE_EXACT => Nette\Utils\Image::EXACT, - ]; - - private array $config; - private array $types = []; + /** + * @var array + */ + private array $types; private string $baseDir; @@ -47,12 +32,12 @@ class ImageStorage private string $compressionDir; - public function __construct(array $config) + /** + * @param array $config + */ + public function __construct(private array $config) { - if ($config['types'] && is_array($config['types'])) { - $this->types = $config['types']; - } - $this->config = $config; + $this->types = $config['types'] && is_array($config['types']) ? $config['types'] : []; $this->baseDir = $config['wwwDir'] . DIRECTORY_SEPARATOR . $config['imagesDir']; $this->placeholder = $config['wwwDir'] . DIRECTORY_SEPARATOR . $config['placeholder']; $this->origDir = $this->baseDir . DIRECTORY_SEPARATOR . $config['origDir']; @@ -70,10 +55,10 @@ public function __construct(array $config) public function saveUpload(Nette\Http\FileUpload $file): string { if ($file->isOk()) { - return $this->saveImage((string) $file->getName(), (string) $file->getTemporaryFile()); + return $this->saveImage($file->getSanitizedName(), $file->getTemporaryFile()); } - throw new Nette\IOException($file->getError()); + throw new Nette\IOException("Upload error code: {$file->getError()}."); } @@ -93,7 +78,7 @@ public function saveImage(string $name, string $path): string 'error' => UPLOAD_ERR_OK, ]); - $fileName = $this->getUniqueFileName($file->getName()); + $fileName = $this->getUniqueFileName($file->getSanitizedName()); $origPath = $this->getOrigPath($fileName); $file->move($origPath); @@ -111,7 +96,7 @@ public function saveImage(string $name, string $path): string /** * @param string $file - * @param string[] $excludedTypes + * @param list $excludedTypes * @return void */ public function deleteImage(string $file, array $excludedTypes = []): void @@ -122,7 +107,7 @@ public function deleteImage(string $file, array $excludedTypes = []): void } foreach ($this->types as $key => $value) { - if (!$excludedTypes || !in_array($key, $excludedTypes, true)) { + if (!$excludedTypes || !in_array($key, $excludedTypes, strict: true)) { Nette\Utils\FileSystem::delete($this->getDestPath($file, ['type' => $key])); } } @@ -144,19 +129,19 @@ public function deleteImage(string $file, array $excludedTypes = []): void /** * @param string $file - * @param string|null $type - * @param array $options - * @return string|null + * @param ?string $type + * @param array $config + * @return ?string * * @throws Nette\Utils\ImageException */ - public function getImageLink(string $file, ?string $type = null, array $options = []): ?string + public function getImageLink(string $file, ?string $type = null, array $config = []): ?string { if ($type !== null) { - $options['type'] = $type; + $config['type'] = $type; } - return ($image = $this->getImage($file, $options)) ? (string) $image : null; + return ($image = $this->getImage($file, $config)) ? (string) $image : null; } @@ -164,28 +149,28 @@ public function getImageLink(string $file, ?string $type = null, array $options * @internal * * @param string $file - * @param array $args - * @return Image|null + * @param array $config + * @return ?Image * * @throws Nette\Utils\ImageException */ - public function getImage(string $file, array $args = []): ?Image + final public function getImage(string $file, array &$config = []): ?Image { - $options = $this->getOptions($args); + $config = $this->getConfig($config); $srcPath = $this->getCompressionPath($file); if (!$file || !is_readable($srcPath)) { - return $this->getPlaceholderImage($options); + return $this->getPlaceholderImage($config); } - $destPath = $this->getDestPath($file, $options); + $destPath = $this->getDestPath($file, $config); if (is_readable($destPath)) { [$width, $height] = getimagesize($destPath); - } elseif ($image = $this->createImage($srcPath, $destPath, $options)) { + } elseif ($image = $this->createImage($srcPath, $destPath, $config)) { [$width, $height] = $image; } else { - return $this->getPlaceholderImage($options); + return $this->getPlaceholderImage($config); } return new Image($this->createRelativeWWWPath($destPath), (int) $width, (int) $height); @@ -198,7 +183,7 @@ public function getImage(string $file, array $args = []): ?Image * @param string $fileName * @return string */ - public function getSubDir(string $fileName): string + final public function getSubDir(string $fileName): string { return (string) (ord(substr($fileName, 0, 1)) % 42); } @@ -207,37 +192,37 @@ public function getSubDir(string $fileName): string /** * @internal * - * @param array $args - * @return array + * @param array $config + * @return array */ - public function getOptions(array $args = []): array + final public function getConfig(array $config = []): array { $type = []; if ( - !empty($args['type']) - && array_key_exists($args['type'], $this->types) - && is_array($this->types[$args['type']]) + !empty($config['type']) + && array_key_exists($config['type'], $this->types) + && is_array($this->types[$config['type']]) ) { - $type = $this->types[$args['type']]; + $type = $this->types[$config['type']]; } - return $args + $type + $this->config; + return $config + $type + $this->config; } /** * @param string $srcPath * @param string $destPath - * @param array $options - * @return array + * @param array $config + * @return array{int, int} * * @throws Nette\Utils\ImageException */ - private function createImage(string $srcPath, string $destPath, array $options = []): array + private function createImage(string $srcPath, string $destPath, array $config = []): array { - if (!$options) { - $options = $this->config; + if (!$config) { + $config = $this->config; } try { @@ -246,9 +231,9 @@ private function createImage(string $srcPath, string $destPath, array $options = Nette\Utils\FileSystem::createDir(dirname($destPath)); - $this->transformImage($image, $options, $srcPath, $type); + $this->transformImage($image, $config, $srcPath, $type); - $image->sharpen()->save($destPath, $options['compression'] ?: null, $type); + $image->sharpen()->save($destPath, $config['compression'] ?: null, $type); return [$image->getWidth(), $image->getHeight()]; @@ -258,37 +243,53 @@ private function createImage(string $srcPath, string $destPath, array $options = } - private function transformImage(Nette\Utils\Image &$image, array $options, string $srcPath, ?int &$type): void + /** + * @param Nette\Utils\Image $image + * @param array $config + * @param string $srcPath + * @param ?int $type + * @return void + */ + private function transformImage(Nette\Utils\Image &$image, array $config, string $srcPath, ?int &$type): void { - $resizeFlags = static::RESIZE_FLAGS[static::RESIZE_FIT]; + $resizeFlags = Resize::OR_SMALLER->flag(); - if (!empty($options['transform'])) { - if (strpos($options['transform'], '|') !== false) { - $resizeFlags = 0; - - foreach (explode('|', $options['transform']) as $flag) { - if (isset(static::RESIZE_FLAGS[$flag])) { - $resizeFlags |= static::RESIZE_FLAGS[$flag]; - } elseif ($flag === static::RESIZE_FILL_EXACT) { - $this->transformFillExact($image, $options, $srcPath, $type); + if (!empty($config['transform'])) { + if (is_array($config['transform']) && count($config['transform']) > 1) { + foreach ($config['transform'] as $resize) { + if ($resize === Resize::EXACT) { + $this->transformExact($image, $config, $srcPath, $type); return; + } elseif ($resize instanceof Resize) { + $resizeFlags |= $resize->flag(); } } - } elseif (isset(static::RESIZE_FLAGS[$options['transform']])) { - $resizeFlags = static::RESIZE_FLAGS[$options['transform']]; - } elseif ($options['transform'] === static::RESIZE_FILL_EXACT) { - $this->transformFillExact($image, $options, $srcPath, $type); + } else { + $resize = is_array($config['transform']) ? $config['transform'][0] : $config['transform']; + + if ($resize === Resize::EXACT){ + $this->transformExact($image, $config, $srcPath, $type); - return; + return; + } elseif ($resize instanceof Resize){ + $resizeFlags = $resize->flag(); + } } } - $image->resize($options['width'], $options['height'], $resizeFlags); + $image->resize($config['width'], $config['height'], $resizeFlags); } - private function transformFillExact(Nette\Utils\Image &$image, array $options, string $srcPath, ?int &$type): void + /** + * @param Nette\Utils\Image $image + * @param array $config + * @param string $srcPath + * @param ?int $type + * @return void + */ + private function transformExact(Nette\Utils\Image &$image, array $config, string $srcPath, ?int &$type): void { if ($this->isTransparentPng($srcPath)) { $color = Nette\Utils\Image::rgb(255, 255, 255, 127); @@ -296,13 +297,13 @@ private function transformFillExact(Nette\Utils\Image &$image, array $options, s $color = Nette\Utils\Image::rgb(255, 255, 255); } - $blank = Nette\Utils\Image::fromBlank($options['width'], $options['height'], $color); - $image->resize($options['width'], null); - $image->resize(null, $options['height']); + $blank = Nette\Utils\Image::fromBlank($config['width'], $config['height'], $color); + $image->resize($config['width'], null); + $image->resize(null, $config['height']); $blank->place( $image, - (int) ($options['width'] / 2 - $image->getWidth() / 2), - (int) ($options['height'] / 2 - $image->getHeight() / 2), + (int) ($config['width'] / 2 - $image->getWidth() / 2), + (int) ($config['height'] / 2 - $image->getHeight() / 2), ); $image = $blank; $type = Nette\Utils\Image::PNG; @@ -332,7 +333,11 @@ private function isTransparentPng(string $path): bool } - private function getPlaceholderImage(array $options): ?Image + /** + * @param array $config + * @return ?Image + */ + private function getPlaceholderImage(array $config): ?Image { if (!is_readable($this->placeholder)) { return null; @@ -340,8 +345,8 @@ private function getPlaceholderImage(array $options): ?Image return new Image( $this->createRelativeWWWPath($this->placeholder), - (int) $options['width'], - (int) $options['height'], + (int) $config['width'], + (int) $config['height'], ); } @@ -369,22 +374,27 @@ private function getOrigPath(string $fileName): string } - private function getDestPath(string $fileName, array $options = []): string + /** + * @param string $fileName + * @param array $config + * @return string + */ + private function getDestPath(string $fileName, array $config): string { - if (!$options) { - $options = $this->config; + if (!$config) { + $config = $this->config; } - if (!empty($options[static::RETURN_ORIG])) { + if (!empty($config[self::RETURN_ORIG])) { return $this->getOrigPath($fileName); - } elseif (!empty($options[static::RETURN_COMPRESSED])) { + } elseif (!empty($config[self::RETURN_COMPRESSED])) { return $this->getCompressionPath($fileName); - } elseif (!empty($options['destDir'])) { - $destDir = $options['destDir']; - } elseif (!empty($options['type']) && array_key_exists($options['type'], $this->types)) { - $destDir = $options['type']; + } elseif (!empty($config['destDir'])) { + $destDir = $config['destDir']; + } elseif (!empty($config['type']) && array_key_exists($config['type'], $this->types)) { + $destDir = $config['type']; } else { - $destDir = "w{$options['width']}h{$options['height']}"; + $destDir = "w{$config['width']}h{$config['height']}"; } return diff --git a/src/Latte/ImagesExtension.php b/src/Latte/ImagesExtension.php new file mode 100644 index 0000000..ca81cb2 --- /dev/null +++ b/src/Latte/ImagesExtension.php @@ -0,0 +1,27 @@ + + */ + +namespace Harmim\Images\Latte; + +use Latte; + + +class ImagesExtension extends Latte\Extension +{ + /** + * @return array + */ + public function getTags(): array + { + return [ + 'img' => [ImgNode::class, 'create'], + 'n:img' => [ImgNode::class, 'create'], + 'imgLink' => [ImgNode::class, 'create'], + ]; + } +} diff --git a/src/Latte/ImgNode.php b/src/Latte/ImgNode.php new file mode 100644 index 0000000..bc73018 --- /dev/null +++ b/src/Latte/ImgNode.php @@ -0,0 +1,189 @@ + + */ + +namespace Harmim\Images\Latte; + +use Harmim; +use Latte; +use Nette; + + +class ImgNode extends Latte\Compiler\Nodes\StatementNode +{ + public function __construct( + private readonly ?Latte\Compiler\Nodes\Php\ExpressionNode $file, + private readonly ?Latte\Compiler\Nodes\Php\ExpressionNode $type, + private readonly Latte\Compiler\Nodes\Php\Expression\ArrayNode $config, + private readonly string $tagName, + private readonly bool $isNattribute, + ) { + } + + + public static function create(Latte\Compiler\Tag $tag): ?static + { + $i = $tag->parser->stream->getIndex(); + $file = $tag->parser->isEnd() ? null : $tag->parser->parseUnquotedStringOrExpression(); + if ($tag->parser->stream->tryConsume('=>', ':')) { + $file = null; + $tag->parser->stream->seek($i); + } + + $i = $tag->parser->stream->getIndex(); + $type = $tag->parser->isEnd() ? null : $tag->parser->parseUnquotedStringOrExpression(); + if ($tag->parser->stream->tryConsume('=>', ':')) { + $type = null; + $tag->parser->stream->seek($i); + } + + if ($tag->isNAttribute()) { + $node = new static($file, $type, $tag->parser->parseArguments(), $tag->name, isNattribute: true); + $node->position = $tag->position; + array_unshift($tag->htmlElement->attributes->children, $node); + + return null; + } + + return new static($file, $type, $tag->parser->parseArguments(), $tag->name, isNattribute: false); + } + + + public function print(Latte\Compiler\PrintContext $context): string + { + if ($this->isNattribute) { + $escaper = $context->beginEscape(); + $escaper->enterHtmlAttribute(); + $escaper->enterHtmlAttributeQuote(); + $res = sprintf( + 'echo \' src="\' . %s::imgLink(%s, %s, $imageStorage) . \'"\';', + static::class, + $this->formatFile($context), + $this->formatConfig($context), + ); + $context->restoreEscape(); + + return $res; + } + + return sprintf( + 'echo %s::%s(%s, %s, $imageStorage);', + static::class, + $this->tagName === 'imgLink' ? 'imgLink' : 'img', + $this->formatFile($context), + $this->formatConfig($context), + ); + } + + + /** + * @return \Generator + */ + public function &getIterator(): \Generator + { + if ($this->file) { + yield $this->file; + } + if ($this->type) { + yield $this->type; + } + yield $this->config; + } + + + private function formatFile(Latte\Compiler\PrintContext $context): string + { + return $this->file ? $context->format('%node', $this->file) : "''"; + } + + + private function formatConfig(Latte\Compiler\PrintContext $context): string + { + $config = $this->config->toArguments(); + + return sprintf( + "[%s%s]", + $this->type ? $context->format("'type' => %node, ", $this->type) : '', + $config ? preg_replace('~(^|,\s)([^:]+):\s~', "$1'$2' => ", $context->format('%args', $config)) : '', + ); + } + + + /** + * @param string $file + * @param array $config + * @param Harmim\Images\ImageStorage $imageStorage + * @return ?string + * + * @throws Nette\Utils\ImageException + */ + public static function img(string $file, array $config, Harmim\Images\ImageStorage $imageStorage): ?string + { + if (!($image = $imageStorage->getImage($file, $config))) { + return null; + } + + $lazy = !empty($config['lazy']); + + $attrs = array_filter($config, static function (string $key) use ($config): bool { + foreach ($config['imgTagAttributes'] as $attr) { + if (str_starts_with($key, $attr)) { + return true; + } + } + + return false; + }, ARRAY_FILTER_USE_KEY); + + $classes = explode(' ', $attrs['class'] ?? ''); + unset($attrs['class']); + + $imgTag = Nette\Utils\Html::el('img'); + $imgTag->src = (string) $image; + $imgTag->class = $classes; + $imgTag->addAttributes($attrs); + if (empty($attrs['alt'])) { + $imgTag->alt = (string) $image; + } + + if ($lazy) { + $lazyImgTag = Nette\Utils\Html::el('img'); + $lazyImgTag->data('src', (string) $image); + array_unshift($classes, 'lazy'); + $lazyImgTag->class = $classes; + $lazyImgTag->addAttributes($attrs); + if (empty($attrs['alt'])) { + $lazyImgTag->alt = (string) $image; + } + + $noscriptTag = Nette\Utils\Html::el('noscript'); + $noscriptTag->addHtml($imgTag); + + $wrapper = Nette\Utils\Html::el() + ->addHtml($lazyImgTag) + ->addHtml($noscriptTag); + + return (string) $wrapper; + } + + return (string) $imgTag; + } + + + /** + * @param string $file + * @param array $config + * @param Harmim\Images\ImageStorage $imageStorage + * @return ?string + * + * @throws Nette\Utils\ImageException + */ + public static function imgLink(string $file, array $config, Harmim\Images\ImageStorage $imageStorage): ?string + { + return ($image = $imageStorage->getImage($file, $config)) ? (string) $image : null; + } +} diff --git a/src/Resize.php b/src/Resize.php new file mode 100644 index 0000000..01205e3 --- /dev/null +++ b/src/Resize.php @@ -0,0 +1,40 @@ + + */ + +namespace Harmim\Images; + +use Nette; + + +enum Resize: string +{ + case SHRINK_ONLY = 'shrink_only'; + + case STRETCH = 'stretch'; + + case OR_SMALLER = 'or_smaller'; + + case OR_BIGGER = 'or_bigger'; + + case COVER = 'cover'; + + case EXACT = 'exact'; + + + final public function flag(): ?int + { + return match ($this) { + self::SHRINK_ONLY => Nette\Utils\Image::ShrinkOnly, + self::STRETCH => Nette\Utils\Image::Stretch, + self::OR_SMALLER => Nette\Utils\Image::OrSmaller, + self::OR_BIGGER => Nette\Utils\Image::OrBigger, + self::COVER => Nette\Utils\Image::Cover, + self::EXACT => null, + }; + } +} diff --git a/src/TImageStorage.php b/src/TImageStorage.php index 61c79b1..586d1f2 100644 --- a/src/TImageStorage.php +++ b/src/TImageStorage.php @@ -1,7 +1,6 @@ imageStorage = $imageStorage; } - protected function createTemplate(): Nette\Application\UI\ITemplate + protected function createTemplate(?string $class = null): Nette\Application\UI\Template { - $template = parent::createTemplate(); + $template = parent::createTemplate($class); $template->imageStorage = $this->imageStorage; return $template; diff --git a/src/Template/Macros.php b/src/Template/Macros.php deleted file mode 100644 index 0c2e7c0..0000000 --- a/src/Template/Macros.php +++ /dev/null @@ -1,198 +0,0 @@ - - */ - -namespace Harmim\Images\Template; - -use Harmim; -use Latte; -use Nette; - - -class Macros extends Latte\Macros\MacroSet -{ - public static function install(Latte\Compiler $compiler): void - { - $self = new static($compiler); - - $self->addMacro('img', [$self, 'macroImg'], null, [$self, 'attrMacroImg']); - $self->addMacro('imgLink', [$self, 'macroImgLink']); - } - - - public function macroImg(Latte\MacroNode $node, Latte\PhpWriter $writer): string - { - [$img, $type] = $this->getImageFromNode($node); - - return sprintf( - 'echo %s::img(%s, %s);', - get_class($this), - $writer->formatWord($img), - $this->formatMacroArgs($type, $node, $writer), - ); - } - - - public function attrMacroImg(Latte\MacroNode $node, Latte\PhpWriter $writer): string - { - [$img, $type] = $this->getImageFromNode($node); - - return sprintf( - 'echo \' src="\' . %s::imgLink(%s, %s) . \'"\';', - get_class($this), - $writer->formatWord($img), - $this->formatMacroArgs($type, $node, $writer), - ); - } - - - public function macroImgLink(Latte\MacroNode $node, Latte\PhpWriter $writer): string - { - [$img, $type] = $this->getImageFromNode($node); - - return sprintf( - 'echo %s::imgLink(%s, %s);', - get_class($this), - $writer->formatWord($img), - $this->formatMacroArgs($type, $node, $writer), - ); - } - - - private function formatMacroArgs(string $type, Latte\MacroNode $node, Latte\PhpWriter $writer): string - { - return sprintf( - '[%s\'storage\' => $imageStorage%s]', - $type ? "'type' => '$type', " : '', - $node->args ? ", {$writer->formatArgs()}" : '', - ); - } - - - /** - * @param Latte\MacroNode $node - * @return string[] - */ - private function getImageFromNode(Latte\MacroNode $node): array - { - $img = $node->tokenizer->fetchWord(); - $type = ''; - - if ($node->tokenizer->isNext()) { - $type = $node->tokenizer->fetchWord(); - if (Nette\Utils\Strings::contains($type, '=>')) { - $node->tokenizer->reset(); - $img = $node->tokenizer->fetchWord(); - $type = ''; - } - } - - return [(string) $img, (string) $type]; - } - - - /** - * @param string $file - * @param array $args - * @return string|null - * - * @throws Nette\Utils\ImageException - */ - public static function img(string $file, array $args): ?string - { - if (!($image = static::getImage($file, $args))) { - return null; - } - - $lazy = !empty($args['lazy']); - - $args = array_filter($args, static function (string $key) use ($args): bool { - foreach ($args['imgTagAttributes'] as $attr) { - if (Nette\Utils\Strings::startsWith($key, $attr)) { - return true; - } - } - - return false; - }, ARRAY_FILTER_USE_KEY); - - $classes = explode(' ', $args['class'] ?? ''); - unset($args['class']); - - $imgTag = Nette\Utils\Html::el('img'); - $imgTag->src = (string) $image; - $imgTag->class = $classes; - $imgTag->addAttributes($args); - if (empty($args['alt'])) { - $imgTag->alt = (string) $image; - } - - if ($lazy) { - $lazyImgTag = Nette\Utils\Html::el('img'); - $lazyImgTag->data('src', (string) $image); - array_unshift($classes, 'lazy'); - $lazyImgTag->class = $classes; - $lazyImgTag->addAttributes($args); - if (empty($args['alt'])) { - $lazyImgTag->alt = (string) $image; - } - - $noscriptTag = Nette\Utils\Html::el('noscript'); - $noscriptTag->addHtml($imgTag); - - $wrapper = Nette\Utils\Html::el() - ->addHtml($lazyImgTag) - ->addHtml($noscriptTag); - - return (string) $wrapper; - } - - return (string) $imgTag; - } - - - /** - * @param string $file - * @param array $args - * @return string|null - * - * @throws Nette\Utils\ImageException - */ - public static function imgLink(string $file, array $args): ?string - { - return ($image = static::getImage($file, $args)) ? (string) $image : null; - } - - - /** - * @param string $file - * @param array $args - * @return Harmim\Images\Image|null - * - * @throws Nette\Utils\ImageException - * @throws Nette\InvalidStateException - */ - public static function getImage(string $file, array &$args): ?Harmim\Images\Image - { - if (empty($args['storage']) || !$args['storage'] instanceof Harmim\Images\ImageStorage) { - throw new Nette\InvalidStateException(sprintf( - "The template did not forward an instance of '%s' to macro 'img'/'imgLink'" - . ", it should be in variable '\$imageStorage'.", - Harmim\Images\ImageStorage::class, - )); - } - - $imageStorage = $args['storage']; - unset($args['storage']); - - - $image = $imageStorage->getImage($file, $args); - $args = $imageStorage->getOptions($args); - - return $image; - } -} diff --git a/tests/DI/ImagesExtensionTest.phpt b/tests/DI/ImagesExtensionTest.phpt index 807a465..5a0fed4 100644 --- a/tests/DI/ImagesExtensionTest.phpt +++ b/tests/DI/ImagesExtensionTest.phpt @@ -5,7 +5,7 @@ declare(strict_types=1); /** - * Test: ImagesExtension + * TEST: DI ImagesExtension. * * @author Dominik Harmim */ @@ -14,52 +14,123 @@ declare(strict_types=1); require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'bootstrap.php'; -$compiler = new Nette\DI\Compiler(); -$compiler->addExtension('images', new Harmim\Images\DI\ImagesExtension()); -$compiler->loadConfig(Tester\FileMock::create(' - parameters: - wwwDir: foo - - images: - origDir: _orig - compressionDir: _imgs - types: - img-small: - width: 50 - height: 50 -', 'neon')); -$builder = $compiler->getContainerBuilder(); -$builder->addDefinition('latte.latteFactory')->setFactory(Latte\Engine::class); - -$code = $compiler->setClassName('Container1')->compile(); -eval($code); - -/** @var Nette\DI\Container $container */ -/** @noinspection PhpUndefinedClassInspection */ -$container = new Container1; -Tester\Assert::true($container->hasService('images.imageStorage')); -Tester\Assert::type(Harmim\Images\ImageStorage::class, $container->getService('images.imageStorage')); -Tester\Assert::same(Harmim\Images\ImageStorage::class, $container->getServiceType('images.imageStorage')); -Tester\Assert::type( - Harmim\Images\ImageStorage::class, - $container->getByType(Harmim\Images\ImageStorage::class), -); - -/** @var Harmim\Images\ImageStorage $imageStorage */ -$imageStorage = $container->getService('images.imageStorage'); -Tester\Assert::true($container->isCreated('images.imageStorage')); - -Tester\Assert::same('foo', $imageStorage->getOptions()['wwwDir']); -Tester\Assert::same('_orig', $imageStorage->getOptions()['origDir']); -Tester\Assert::same('_imgs', $imageStorage->getOptions()['compressionDir']); -Tester\Assert::same(Harmim\Images\DI\ImagesExtension::DEFAULTS['width'], $imageStorage->getOptions()['width']); -Tester\Assert::same(50, $imageStorage->getOptions()['types']['img-small']['width']); - -Tester\Assert::same(100, $imageStorage->getOptions(['width' => 100])['width']); -Tester\Assert::same( - Harmim\Images\DI\ImagesExtension::DEFAULTS['height'], - $imageStorage->getOptions(['width' => 100])['height'], -); - -Tester\Assert::same(50, $imageStorage->getOptions(['type' => 'img-small'])['width']); -Tester\Assert::same(100, $imageStorage->getOptions(['width' => 100, 'type' => 'img-small'])['width']); +test('DI ImagesExtension - valid configuration.', static function (): void { + $compiler = (new Nette\DI\Compiler()) + ->addExtension('images', new Harmim\Images\DI\ImagesExtension()) + ->loadConfig(Tester\FileMock::create(' + parameters: + wwwDir: foo + + images: + origDir: _orig + compressionDir: _imgs + types: + img-small: + width: 50 + height: 50 + transform: ::constant(Harmim\Images\Resize::COVER) + foo: + transform: + - ::constant(Harmim\Images\Resize::COVER) + - ::constant(Harmim\Images\Resize::STRETCH) + ', 'neon')); + $compiler->getContainerBuilder()->addFactoryDefinition('latte.latteFactory') + ->setImplement(Nette\Bridges\ApplicationLatte\LatteFactory::class) + ->getResultDefinition() + ->setFactory(Latte\Engine::class); + eval($compiler->compile()); + + /** @var Nette\DI\Container $container */ + /** @noinspection PhpUndefinedClassInspection */ + $container = new Container; + Tester\Assert::true($container->hasService('images.imageStorage')); + Tester\Assert::type(Harmim\Images\ImageStorage::class, $container->getService('images.imageStorage')); + Tester\Assert::same(Harmim\Images\ImageStorage::class, $container->getServiceType('images.imageStorage')); + Tester\Assert::type( + Harmim\Images\ImageStorage::class, + $container->getByType(Harmim\Images\ImageStorage::class), + ); + + /** @var Harmim\Images\ImageStorage $imageStorage */ + $imageStorage = $container->getService('images.imageStorage'); + Tester\Assert::true($container->isCreated('images.imageStorage')); + + Tester\Assert::same('foo', $imageStorage->getConfig()['wwwDir']); + Tester\Assert::same('_orig', $imageStorage->getConfig()['origDir']); + Tester\Assert::same('_imgs', $imageStorage->getConfig()['compressionDir']); + Tester\Assert::same(Harmim\Images\DI\ImagesExtension::DEFAULTS['width'], $imageStorage->getConfig()['width']); + Tester\Assert::same(50, $imageStorage->getConfig()['types']['img-small']['width']); + + Tester\Assert::same(100, $imageStorage->getConfig(['width' => 100])['width']); + Tester\Assert::same( + Harmim\Images\DI\ImagesExtension::DEFAULTS['height'], + $imageStorage->getConfig(['width' => 100])['height'], + ); + + Tester\Assert::same(50, $imageStorage->getConfig(['type' => 'img-small'])['width']); + Tester\Assert::same(100, $imageStorage->getConfig(['width' => 100, 'type' => 'img-small'])['width']); + + Tester\Assert::same(Harmim\Images\Resize::OR_SMALLER, $imageStorage->getConfig()['transform']); + Tester\Assert::same(Harmim\Images\Resize::COVER, $imageStorage->getConfig(['type' => 'img-small'])['transform']); + Tester\Assert::notSame( + [Harmim\Images\Resize::COVER->value, Harmim\Images\Resize::STRETCH->value], + $imageStorage->getConfig(['type' => 'foo'])['transform'], + ); + Tester\Assert::same( + [Harmim\Images\Resize::COVER, Harmim\Images\Resize::STRETCH], + $imageStorage->getConfig(['type' => 'foo'])['transform'], + ); +}); + + +test('DI ImagesExtension - invalid configuration.', static function (): void { + $compile = static fn(string $config): string => + (new Nette\DI\Compiler()) + ->addExtension('images', new Harmim\Images\DI\ImagesExtension()) + ->loadConfig(Tester\FileMock::create($config, 'neon')) + ->compile(); + + Tester\Assert::exception(static fn(): string => $compile(' + images: + foo: bar + '), Nette\DI\InvalidConfigurationException::class); + + Tester\Assert::exception(static fn(): string => $compile(' + images: + compression: 142 + '), Nette\DI\InvalidConfigurationException::class); + + Tester\Assert::exception(static fn(): string => $compile(' + images: + width: 0 + '), Nette\DI\InvalidConfigurationException::class); + + Tester\Assert::exception(static fn(): string => $compile(' + images: + height: -42 + '), Nette\DI\InvalidConfigurationException::class); + + Tester\Assert::exception(static fn(): string => $compile(' + images: + imgTagAttributes: alt + '), Nette\DI\InvalidConfigurationException::class); + + Tester\Assert::exception(static fn(): string => $compile(' + images: + types: + foo: + bar: baz + '), Nette\DI\InvalidConfigurationException::class); + + Tester\Assert::exception(static fn(): string => $compile(' + images: + transform: foo + '), Nette\DI\InvalidConfigurationException::class); + + Tester\Assert::exception(static fn(): string => $compile(' + images: + types: + foo: + transform: [::constant(Harmim\Images\Resize::COVER), 42] + '), Nette\DI\InvalidConfigurationException::class); +}); diff --git a/tests/Macros/MacrosTest.phpt b/tests/Latte/ImagesExtension.phpt similarity index 94% rename from tests/Macros/MacrosTest.phpt rename to tests/Latte/ImagesExtension.phpt index dda562d..1e6cbaa 100644 --- a/tests/Macros/MacrosTest.phpt +++ b/tests/Latte/ImagesExtension.phpt @@ -5,12 +5,12 @@ declare(strict_types=1); /** - * Test: Macros + * TEST: Latte ImagesExtension. * * @author Dominik Harmim */ -namespace Harmim\Tests\Macros; +namespace Harmim\Tests\Latte; use Harmim; use Latte; @@ -24,11 +24,8 @@ require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'bootstrap. /** * @testCase */ -class MacrosTest extends Tester\TestCase +final class ImagesExtension extends Tester\TestCase { - use Nette\SmartObject; - - private Latte\Engine $latteEngine; private Harmim\Images\ImageStorage $imageStorage; @@ -53,7 +50,7 @@ class MacrosTest extends Tester\TestCase protected function setUp(): void { $this->latteEngine = new Latte\Engine(); - Harmim\Images\Template\Macros::install($this->latteEngine->getCompiler()); + $this->latteEngine->addExtension(new Harmim\Images\Latte\ImagesExtension()); $this->imageStorage = new Harmim\Images\ImageStorage(IMAGES_EXTENSION_CONFIG); @@ -80,7 +77,7 @@ class MacrosTest extends Tester\TestCase { try { $rndInt = random_int(0, PHP_INT_MAX); - } catch (\Throwable $e) { + } catch (\Throwable) { $rndInt = 42; } @@ -203,23 +200,23 @@ class MacrosTest extends Tester\TestCase { Tester\Assert::same('', $this->evalMacro('')); Tester\Assert::same( - 'alt', + 'alt', $this->evalMacro('alt'), ); Tester\Assert::same( - 'altimagesDir/w1024h1024/$this->fileSubDir/$this->fileName" . '">', + 'imagesDir/w1024h1024/$this->fileSubDir/$this->fileName" . '" alt="alt">', $this->evalMacro('alt'), ); Tester\Assert::same( - 'altimagesDir/img-small/$this->fileSubDir/$this->fileName" - . '">', + . '" alt="alt" class="class">', $this->evalMacro('alt'), ); Tester\Assert::same( - 'altimagesDir/w1024h1024/$this->fileSubDir/$this->fileName" - . '">', + . '" alt="alt" class="class">', $this->evalMacro('alt'), ); } @@ -266,4 +263,4 @@ class MacrosTest extends Tester\TestCase } -(new MacrosTest())->run(); +run(new ImagesExtension()); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 0849b05..cbcf536 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -10,20 +10,26 @@ require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; +function run(Tester\TestCase $testCase): void +{ + /** @noinspection PhpUnhandledExceptionInspection */ + $testCase->run(); +} + + date_default_timezone_set('Europe/Prague'); Tester\Environment::setup(); - +Tester\Environment::setupFunctions(); try { $rndInt = random_int(0, PHP_INT_MAX); -} catch (\Throwable $e) { +} catch (\Throwable) { $rndInt = 42; } define('__TEMP_DIR__', __DIR__ . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $rndInt); Nette\Utils\FileSystem::createDir(dirname(__TEMP_DIR__)); Tester\Helpers::purge(__TEMP_DIR__); - const IMAGES_EXTENSION_CONFIG = [ 'wwwDir' => __TEMP_DIR__, 'placeholder' => 'noimg.png',