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..c06778f 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
-
+
```
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(
- '',
+ '',
$this->evalMacro(''),
);
Tester\Assert::same(
- 'imagesDir/w1024h1024/$this->fileSubDir/$this->fileName" . '">',
+ 'imagesDir/w1024h1024/$this->fileSubDir/$this->fileName" . '" alt="alt">',
$this->evalMacro(''),
);
Tester\Assert::same(
- 'imagesDir/img-small/$this->fileSubDir/$this->fileName"
- . '">',
+ . '" alt="alt" class="class">',
$this->evalMacro(''),
);
Tester\Assert::same(
- 'imagesDir/w1024h1024/$this->fileSubDir/$this->fileName"
- . '">',
+ . '" alt="alt" class="class">',
$this->evalMacro(''),
);
}
@@ -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',