Skip to content

Images

Introduction

Image variations (image aliases) enable you to define and use different versions of the same image. You generate variations based on filters which modify aspects such as size and proportions, quality or effects.

Image variations are generated with LiipImagineBundle, using the underlying Imagine library from avalanche123. This bundle supports GD (default), Imagick or Gmagick PHP extensions, and enables you to define flexible filters in PHP. Image files are stored using the IOService, and are completely independent from the Image Field Type. They are generated only once and cleared on demand (e.g. on content removal).

LiipImagineBundle only works on image blobs (no command line tool is needed). See the bundle's documentation to learn more on that topic.

Configuring image variations

Custom image variations are defined in ezplatform.yml or any imported semantic configuration file. The definition is dynamic, so it can be configured per SiteAccess and all the other scopes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Example image variation definition

ezpublish:
    system:
        my_siteaccess:
            image_variations:
                small:
                    reference: null
                    filters:
                        - { name: geometry/scaledownonly, params: [100, 160] }
                medium:
                    reference: null
                    filters:
                        - { name: geometry/scaledownonly, params: [200, 290] }
                listitem:
                    reference: null
                    filters:
                        - { name: geometry/scaledownonly, params: [130, 190] }
                articleimage:
                    reference: null
                    filters:
                        - { name: geometry/scalewidth, params: [770] }

Note

Each variation name must be unique. It may contain underscores (_), hyphens (-) or numbers, but no spaces.

The following parameters are set for each variation:

  • reference: Name of a reference variation to base the variation on. If set to null (or ~, which means null in YAML), the variation will take the original image for reference. It can be any available variation configured in the ezpublish namespace, or a filter_set defined in the liip_imagine namespace.
  • filters: Array of filter definitions (hashes containing name and params keys). See possible values below.

Caution

If you change image variation properties manually, you need to clear persistence cache:

php bin/console cache:pool:clear <pool_name>

where <pool_name> is cache.app by default, and cache.redis when using Redis.

Built-in image variations

A few basic image variations are included by default in eZ Platform in the default_settings.yml config file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
ezsettings.default.image_variations:
    reference:
        reference: ~
        filters:
            geometry/scaledownonly: [600, 600]
    small:
        reference: reference
        filters:
            geometry/scaledownonly: [100, 100]
    tiny:
        reference: reference
        filters:
            geometry/scaledownonly: [30, 30]
    medium:
        reference: reference
        filters:
            geometry/scaledownonly: [200, 200]
    large:
        reference: reference
        filters:
            geometry/scaledownonly: [300, 300]

Configure image variations

Post-Processors

LiipImagineBundle supports post-processors on image aliases. You can specify them in image variation configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ezpublish:
    system:
        my_siteaccess:
            image_variations:
                articleimage:
                    reference: null
                    filters:
                        - { name: geometry/scalewidth, params: [770] }
                    post_processors:
                        jpegoptim: {}

Please refer to post-processor documentation in LiipImagineBundle for details.

Configuration examples

Scaling with an eZ Platform filter

This configuration defines a medium image variation that is scaled to a width of 700 px.

1
2
3
4
5
6
7
8
9
ezpublish:
    system:
        my_siteaccess:
            image_variations:
                medium:
                    reference: null
                    filters:
                        - geometry/scalewidth:
                            params: [770]

Image quality with a liip filter

This configuration adds a limit to the image quality using a liip filter.

You can use both an eZ Platform and a liip filter for the same image variation, in this case medium.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ezpublish:
    system:
        my_siteaccess:
            image_variations:
                # List of variations

liip_imagine:
    driver: imagick
    filter_sets:
        medium:
            jpeg_quality: 50

Note

Notice that the liip_imagine key is not placed under image_variations, but at the same level as ezpublish.

Rendering image variations

Render within a Twig template

Image variations can be called within a Twig template by passing alias with the parameters:

1
ez_render_field( content, 'image', { parameters: { 'alias': 'medium' } } )

Upgrade

Instantiate LiipImagineBundle in your kernel class

If you were using ImageMagick, install Imagick or Gmagick PHP extensions and activate the driver in liip_imagine(see LiipImagineBundle configuration documentation for more information):

1
2
3
4
# ezplatform.yml or config.yml
liip_imagine:
    # Driver can be either "imagick", "gmagick" or "gd", depending on the PHP extension you're using.
    driver: imagick

GD will be used by default if no driver is specified.

Managing variations

Purging

You can use the Liip Imagine console tool to clear generated variations.

1
2
$ php bin/console liip:imagine:cache:remove --filter=large
$ php bin/console liip:imagine:cache:remove -v

The first example will clear the image files for the large variation. The second will clear all the generated variations (be careful), and list the removed files (-v).

Note

The naming scheme change introduced by this feature wasn't enabled by default on 5.4.x. As part of migration you'll need to adapt to the new schema to get the benefit of this more efficient purge method. More technical information can be found on the pull request.

Code injection in image EXIF

EXIF metadata of an image may contain e.g. HTML, JavaScript, or PHP code. eZ Platform is itself does not parse EXIF metadata, but third-party bundles need to be secured against this eventuality. Images should be treated like any other user-submitted data - make sure the metadata is properly escaped before use.

Resolving image URLs

You can use LiipImagine's liip:image:cache:resolve script to resolve the path to image variations generated from the original image, with one or more paths as arguments. See LiipImagineBundle documentation for more information.

Note that paths to repository images must be relative to the var/<site>/storage/images directory, for example: 7/4/2/0/247-1-eng-GB/test.jpg.

Filter reference

Available filters

In addition to filters exposed by LiipImagineBundle, the following are available:

Filter name Parameters Description
geometry/scaledownonly [width, height] Generates a thumbnail that will not exceed width/height.
geometry/scalewidthdownonly [width] Generates a thumbnail that will not exceed width.
geometry/scaleheightdownonly [height] Generates a thumbnail that will not exceed height.
geometry/scalewidth [width] Alters image width. Proportion will be kept.
geometry/scaleheight [height] Alters image height. Proportion will be kept.
geometry/scale [width, height] Alters image size, not exceeding provided width and height. Proportion will be kept.
geometry/scaleexact [width, height] Alters image size to fit exactly provided width and height. Proportion will not be kept.
geometry/scalepercent [widthPercent, heightPercent] Scales width and height with provided percent values. Proportion will not be kept.
geometry/crop [width, height, startX, startY] Crops the image. Result will have provided width/height, starting at provided startX/startY
border [thickBorderX, thickBorderY, color=#000] Adds a border around the image. Thickness is defined in px. Color is "#000" by default.
filter/noise [radius=0] Smooths the contours of an image (imagick/gmagick only). radius is in px.
filter/swirl [degrees=60] Swirls the pixels of the center of the image (imagick/gmagick only). degrees defaults to 60°.
resize Simple resize filter (provided by LiipImagineBundle).
colorspace/gray N/A Converts the image to grayscale.

LiipImagineBundle supports additional settings, it is possible to combine filters from the list above with the ones provided in LiipImagineBundle or custom ones.

Discarded filters

The following filters exist in the Imagine library but are not used in eZ Platform due to incompatibility:

  • flatten. Obsolete, images are automatically flattened.
  • bordercolor
  • border/width
  • colorspace/transparent
  • colorspace

Custom filters

Please refer to LiipImagineBundle documentation on custom filters. Imagine library documentation may also be useful.

Setting placeholder generator

Placeholder generator enables you to download or use generated image placeholder for any missing image. It might be used when you are working on an existing database and you are not able to download uploaded images to your local development environment because of their large size

PlaceholderAliasGenerator::getVariation method generates placeholder (by delegating it to the implementation of PlaceholderProvider interface) if original image cannot be resolved and saves it under the original path.

In eZ Platform there are two implementations of PlaceholderProvider interface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php

namespace eZ\Bundle\EzPublishCoreBundle\Imagine;

use eZ\Publish\Core\FieldType\Image\Value as ImageValue;

interface PlaceholderProvider
{
    /**
     * Provides a placeholder image path for a given Image FieldType value.
     *
     * @param \eZ\Publish\Core\FieldType\Image\Value $value
     * @param array $options
     * @return string Path to placeholder
     */
    public function getPlaceholder(ImageValue $value, array $options = []): string;
}

GenericProvider

\eZ\Bundle\EzPublishCoreBundle\Imagine\PlaceholderProvider\GenericProvider generates placeholder with basic information about original image - example 1.

Generic image example:

Placeholder image GenericProvider

Full page example:

Placeholder GenericProvider

Option Default value Description
fontpath n/a Path to the font file (.ttf). **This option is required.*
text "IMAGE PLACEHOLDER %width%x%height%\n(%id%)" Text which will be displayed in the image placeholder. %width%, %height%, %id% in it will be replaced with width, height and ID of the original image.
fontsize 20 Size of the font in the image placeholder.
foreground #000000 Foreground color of the placeholder.
secondary #CCCCCC Secondary color of the placeholder.
background #EEEEEE Background color of the placeholder.

RemoteProvider

\eZ\Bundle\EzPublishCoreBundle\Imagine\PlaceholderProvider\RemoteProvider allows you to download placeholders from:

Full page example:

Placeholder RemoteProvider - placekitten.com

Option Default value Description
url_pattern '' URL pattern. %width%, %height%, %id% in it will be replaced with width, height and ID of the original image.
timeout 5 Period of time before timeout, measured in seconds.

Semantic configuration

Placeholder generation can be configured for each binary_handler under the ezpublish.image_placeholder key:

1
2
3
4
5
6
ezpublish:
    # ...
    image_placeholder:
        <BINARY_HANDLER_NAME>:
            provider: <PROVIDER TYPE>
            options:  <CONFIGURATION>

If there is no configuration assigned to binary_handler name, the placeholder generation will be disabled.

Configuration examples:

Example 1 - placeholders with basic information about original image

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ezpublish:
    # ...
    image_placeholder:
        default:
            provider: generic
            options:
                fontpath: '%kernel.root_dir%/Resources/font/font.ttf'
                background: '#EEEEEE'
                foreground: '#FF0000'
                text: 'MISSING IMAGE %%width%%x%%height%%'

Example 2 - placeholders from remote source

1
2
3
4
5
6
7
ezpublish:
    # ...
    image_placeholder:
        default:
            provider: remote
            options:
                url_pattern: 'https://placekitten.com/%%width%%/%%height%%'

Example 3 - placeholders from live version of a site

1
2
3
4
5
6
7
ezpublish:
    # ...
    image_placeholder:
        default:
            provider: remote
            options:
                url_pattern: 'http://example.com/var/site/storage/%%id%%'

Resizing images

You can resize all original images of a chosen Content Type using the ezplatform:images:resize-original command. You need to provide the command with:

  • identifier of the image Content Type
  • identifier of the Field you want to affect
  • name of the image variation to apply to the images

ezplatform:images:resize-original <Content Type identifier> <Field identifier> -f <variation name>

For example:

ezplatform:images:resize-original photo image -f small_image

Additionally you can provide two parameters:

  • iteration-count is the number of images to be recreated in a single iteration, to reduce memory use. Default is 25.
  • user is the identifier of a user with proper permission who will perform the operation (read, versionread, edit and publish). Default is admin.

Caution

This command publishes a new version of each Content item it modifies.

Normalizing images

If you use image files with unprintable UTF-8 characters in file names, you may come across a problem with images not displaying. In that case, run the ezplatform:images:normalize-path command to normalize them:

1
php bin/console ezplatform:images:normalize-path

Next, clear the cache:

1
php bin/console cache:clear

and run the following:

1
php bin/console liip:imagine:cache:remove

Reusing images

You can store images in the media library as independent Content items of a generic Image Content Type to reuse them across the system. It is achieved by uploading images to an ImageAsset Field Type.

For ImageAsset field to be reused you have to publish it. Only then is notification triggered stating image has been published under the Location and can now be reused. After establishing media library you can create object relations between the main Content item and the image content item being used by it.

To learn more about ImageAsset Field Type and its customization see Field Type Reference.

Handling SVG images

Currently, eZ Platform does not allow you to store SVG images by using the Image or ImageAsset Field Type. Until the full support for this MIME type is in place, you can work things around by relying on the File Field Type and implementing a custom extension that lets you display and download files in your templates.

Caution

SVG images may contain JavaScript, so they may introduce XSS or other security vulnerabilities. Make sure end users are not allowed to upload SVG images, and be restrictive about which editors are allowed to do so.

First, you need to add a proper rule in app/config/routing.yml file:

1
2
3
app.svg_download:
    path: /asset/download/{contentId}/{fieldIdentifier}/{filename}
    defaults: { _controller: app.controller.content.svg:downloadSvgAction }

It will point to the custom controller which will handle the action of downloading SVG file. Below you can find its definition (placed in app/config/services.yml) and implementation:

1
2
3
4
5
6
7
app.controller.content.svg:
    class: AppBundle\Controller\SvgController
    public: true
    arguments:
        - "@ezpublish.api.service.content"
        - "@ezpublish.fieldType.ezbinaryfile.io_service"
        - "@ezpublish.translation_helper"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?php

namespace AppBundle\Controller;

use eZ\Publish\API\Repository\ContentService;
use eZ\Publish\API\Repository\Values\Content\Field;
use eZ\Publish\Core\Helper\TranslationHelper;
use eZ\Publish\Core\IO\IOService;
use eZ\Publish\Core\MVC\Symfony\Controller\Controller;
use InvalidArgumentException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;

class SvgController extends Controller
{
    const CONTENT_TYPE_HEADER = 'image/svg+xml';

    /** @var \eZ\Publish\API\Repository\ContentService */
    private $contentService;

    /** @var \eZ\Publish\Core\IO\IOService */
    private $ioService;

    /** @var \eZ\Publish\Core\Helper\TranslationHelper */
    private $translationHelper;

    /**
     * SvgController constructor.
     * @param ContentService $contentService
     * @param IOService $ioService
     * @param TranslationHelper $translationHelper
     */
    public function __construct(ContentService $contentService, IOService $ioService, TranslationHelper $translationHelper)
    {
        $this->contentService = $contentService;
        $this->ioService = $ioService;
        $this->translationHelper = $translationHelper;
    }

    /**
     * @param $contentId
     * @param $fieldIdentifier
     * @param $filename
     * @param Request $request
     * @return Response
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
     */
    public function downloadSvgAction($contentId, $fieldIdentifier, $filename, Request $request)
    {
        $version = null;

        if ($request->query->has('version')) {
            $version = $request->query->get('version');
        }

        $content = $this->contentService->loadContent($contentId, null, $version);
        $language = $request->query->has('inLanguage') ? $request->query->get('inLanguage') : null;
        $field = $this->translationHelper->getTranslatedField($content, $fieldIdentifier, $language);

        if (!$field instanceof Field) {
            throw new InvalidArgumentException("'{$fieldIdentifier}' field not present in content #{$content->contentInfo->id} '{$content->contentInfo->name}'");
        }

        $binaryFile = $this->ioService->loadBinaryFile($field->value->id);
        $response = new Response($this->ioService->getFileContents($binaryFile));
        $disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $filename);

        $response->headers->set('Content-Disposition', $disposition);
        $response->headers->set('Content-Type', self::CONTENT_TYPE_HEADER);

        return $response;
    }
}

To be able to use a proper link in your templates, you also need a dedicated Twig extension:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

namespace AppBundle\Twig;

use Symfony\Component\Routing\Router;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class SvgExtension extends AbstractExtension
{
    /** @var \Symfony\Component\Routing\Router */
    protected $router;

    /**
     * SvgExtension constructor.
     * @param \Symfony\Component\Routing\Router $router
     */
    public function __construct(Router $router)
    {
        $this->router = $router;
    }

    /**
     * @return array|TwigFunction[]
     */
    public function getFunctions()
    {
        return [
            new TwigFunction('ez_svg_link', [
                $this,
                'generateLink'
            ]),
        ];
    }

    /**
     * @param int $contentId
     * @param string $fieldIdentifier
     * @param string $filename
     *
     * @return string|null
     */
    public function generateLink(int $contentId, string $fieldIdentifier, string $filename)
    {
        return $this->router->generate('app.svg_download', [
            'contentId' => $contentId,
            'fieldIdentifier' => $fieldIdentifier,
            'filename' => $filename
        ]);
    }
}

Now you can load SVG files in your templates using generated links and newly created Twig helper:

1
2
{% set svgField = ez_field(content, 'image_identifier') %}
<img src="{{ ez_svg_link(content.versionInfo.contentInfo.id, 'image_identifier', svgField.value.fileName) }}" alt="">