thelia / thelia-library-module
Add a media library to Thelia
Package info
github.com/thelia-modules/TheliaLibrary
Type:thelia-module
pkg:composer/thelia/thelia-library-module
Requires
- enshrined/svg-sanitize: ^1.1.1
- liip/imagine-bundle: ^2.9
- thelia/core: ^3.2
- thelia/installer: ^1.6
Requires (Dev)
- phpunit/phpunit: ^11.5
- roave/security-advisories: dev-latest
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 2.7.0
- 2.6.0
- 2.5.0
- 2.4.0
- 2.3.0
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.0
- 2.0.10
- 2.0.9
- 2.0.8
- 2.0.7
- 2.0.6
- 2.0.5
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.4.0
- 1.3.1
- 1.3.0
- 1.2.10
- 1.2.9
- 1.2.7
- 1.2.6
- 1.2.5
- 1.2.4
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.14
- 1.1.13
- 1.1.12
- 1.1.11
- 1.1.10
- 1.1.9
- 1.1.8
- 1.1.7
- 1.1.6
- 1.1.5
- 1.1.4
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.0
- dev-feat/video-conversion
- dev-feat/mp4-videos
- dev-thelia2
- dev-feature/liip_imagine
- dev-feature/interface
This package is auto-updated.
Last update: 2026-11-10 15:32:51 UTC
README
Add a media library on Thelia.
Composer
Add it in your main thelia composer.json file
composer require thelia/thelia-library-module:~1.0.0
Usage
For now this module doesn't have an interface, only an api documented on {your_website_url}/open_api/doc
Stored files
Uploaded pictures are stored in local/library/images (configurable through the
image_directory module configuration). The module only stores JPEG, PNG, GIF,
WebP, BMP and SVG pictures, and MP4, QuickTime (MOV) and WebM videos: the format is read from the content
of the file, the extension is derived from that format, and the stored name is
generated, so neither the name nor the extension sent by the browser is reused.
Videos
An MP4 file sent to LibraryImageService is stored as a library row whose
mime_type is video/mp4 (LibraryImage::isVideo()), with no width or height.
It is served whole by /video-library/{id}.mp4, which answers the byte ranges a
browser asks for as the visitor plays and seeks; getImagePublicUrl() gives
that address. The image routes (/image-library/…) answer a video with a 404:
it is never decoded nor resized. The API upload still accepts pictures only.
A QuickTime (video/quicktime, stored as .mov) or WebM (video/webm, .webm)
file is stored as sent and is a video of the library like an MP4. The library
converts nothing: a module that does (a phone's HEVC .mov into a web MP4)
puts the converted MP4 back under the same id with
LibraryImageService::updateImage(), which removes the raw file, records the
new type and weight, and leaves /video-library/{id}.mp4 unchanged. Until
then that address answers with the raw file.
The module also writes an .htaccess in that directory at activation, so that
Apache serves it as static files only. nginx has no per-directory configuration;
add the equivalent to your server block if the directory is under your web root:
location ~* ^/local/library/ { location ~* \.(php[0-9]?|phtml|phar|cgi|pl|py)$ { deny all; } location ~* \.svg$ { add_header Content-Security-Policy "sandbox" always; add_header X-Content-Type-Options "nosniff" always; } }
An add_header in a location drops every add_header that location would
inherit: repeat there the headers your server block sets for all responses.
Using Plugins
You can use the getImages and getImagesData Twig functions.
getImagesDatareturn an array of processed images, depends of your liip_imagine configuration.getImagesuse the same service thatgetImagesDataand render image(s) in a html tag
Here’s the params :
| Param | Required | Description | Exemple |
|---|---|---|---|
| img_id | false | if you have the id of the current image, you can use this param with source_type |
/ |
| source_type | true | source | content, product, category, library, page or folder |
| source_id | false | The identifier of the object provided in the "source" parameter | / |
| filters | true | the liip_imagine filter’ identifier you want to use on the image | could be a simple value like product_filter or a array, if you want to render several images in a picture html tag *filters=*['1440px' =>'hero_large_desktop', 'default' => 'hero_default' |
| img_attrs | false | / | img_attrs=['width' => '768px','height' => '660px','class' => "test-class-img"] |
| alt | false | string to render as alt an title attribute. If you have a alt entry in your img_attrs this one will be overrided. |
/ |
| wrapper | false | An html wrapper tag for the rendered image. If filters | count > 1, image will be wrapped by a picture html tag. |
| wrapper_attrs | false | html attributes for wrapper | wrapper_attrs=['class' => "test-class-picture"] |
| img_style | false | css propreties rended in the image style attribute | img_style=['display' => 'block','margin' => '10px'] |
| placeholder | false | fallback image to return. if no set, plugin will return null |
/dist/images/placeholder.webp |
| position | false | position of the image | 1 |
| limit | false | limited number of result | 1 |
| offset | false | / | 1 |
| container | An html wrapper tag for wrappe the list of rendered images | / | ul, div, section |
| container_attrs | same as wrapper_attrs | / | container_attrs=['class' => "test-class-container"] |
Here’s the exemple :
{{ getImages({
source_type: 'content',
source_id: 2,
filters: {
'1440px': 'hero_large_desktop',
'1280px': 'hero_small_desktop',
'1024px': 'hero_tablette',
'768px': 'hero_mobile',
'default': 'hero_default'
},
img_attrs: {
width: '768px',
height: '660px',
class: 'test-class-img'
},
wrapper: 'picture',
wrapper_attrs: {
class: 'test-class-picture'
},
img_style: {
display: 'block'
},
limit: 1,
placeholder: '/dist/images/placeholder.webp'
})|raw }}
Upgrading to 2.5.0
The public image addresses (/image-library/{id}/{region}/{size}/{rotation}/{quality}.{format}
and /legacy-image-library/…) render and store any well-formed combination. A shop can now
list the renderings it uses; every other address answers 404 before the source is opened or a
file is written. Without the parameter nothing changes.
# config/services.yaml parameters: thelia_library.allowed_variants: regions: [full] # default [full]; `square`; `registered` (see below); `crop` for any x,y,w,h and pct:x,y,w,h region rotations: ['0'] # default ['0'] sizes: - max - '480,!' # a size, spelled as in the URL (modifiers in any order) - '320,*' # a width, at the height of the proportions of the region asked (1 px tolerance) - native # the own width and height of the region asked
W,* and native are measured on the region of the address: the whole image for full, the zone
(cut at the edges of the image) for x,y,w,h, the smaller side for square.
registered lets a module that frames its images say which frames it printed: a service
implementing TheliaLibrary\Service\ImageRegionRegistryInterface (tagged automatically) is
asked, for an image rendering that is not on disk yet, whether it knows the x,y,w,h region of
the address. An unknown zone answers 404, so the zones of an image are the ones its pages print,
not the ones a visitor invents. Without a registry nothing is registered; crop opens every zone.
sizes is required once the parameter is set. A W,* entry reads the stored width and height
of the image only when the copy is not on disk yet; a copy already stored is served as before.
Upgrading to 2.4.0
The library now stores SVG drawings. A submitted SVG goes through enshrined/svg-sanitize behind checks of the module's own:
- refused, with the reason, when it cannot be read safely: a DOCTYPE that declares
something (an internal subset) or an entity, bytes that are not UTF-8, broken XML,
a root that is not
<svg>, more than 5 MiB or 50,000 elements, or no size at all (neither aviewBoxnor an absolutewidthandheight); - otherwise cleaned: scripts, event handlers, animations,
foreignObjectand remote references leave the file, so does a DOCTYPE that declares nothing (the "SVG 1.1" line some editors write), a drawing copied out of an HTML page gets the SVG namespace, and a missingviewBoxis added fromwidthandheight. Comments leave too, CDATA sections become escaped text, and atitleor adesckeeps its text only: an HTML page that inlines the drawing reads all three differently from an XML parser. When the cleaning removed nothing, the submitted bytes are stored as they are, unless they hold a comment, a CDATA section or a tab or line break inside an attribute value.
SVG files stored before 2.4.0 were never cleaned: they keep answering 400 at the SVG address until they are uploaded again.
TheliaLibrary\Service\Svg\SvgSanitizerInterface gives the same cleaning to other
modules, with the list of what it removed, so an editor can be told before storing.
An SVG is served as it is stored, never through Imagine:
/image-library/{id}/{region}/{size}/{rotation}/{quality}.svg answers the file with
Content-Type: image/svg+xml and Content-Security-Policy: sandbox, so that a file
opened on its own gets no script, cookie or storage of the site. If the image
directory is under your web root, the .htaccess the module writes sends the same
policy and X-Content-Type-Options: nosniff for .svg files when mod_headers is
enabled; on nginx, add the .svg location of the block above. Region, size, rotation
and quality do not apply; asking for an SVG in another format answers 400. Its
info.json gives the size of its viewBox. Public URLs and the image plugin point at
that address whatever format or filter a template asks for.
LibraryImageFileNamer and LibraryImageService take the sanitizer in their
constructor: code that builds them by hand has to pass it.
Upgrading to 2.0.9
The module now renders the modern image formats a shop activates, as <picture> sources
beside the source-format <img>. A shop that activates none renders exactly what it did
before.
The formats and their encoder quality are shop settings read from the core
(image_formats, image_quality_webp, image_quality_avif), so this version needs
Thelia 3.2 or later. Variants are written on first display, next to the file they derive
from and suffixed with the format extension, and addressed directly: no image is ever
served through a resolve URL and a redirect.
Upgrading to 1.4.0
library_image now carries what the stored file is — file_name, mime_type,
width, height, file_size — plus a decorative flag, and created_at /
updated_at. Each translation gains an alt and a caption.
file_name moved from library_image_i18n to library_image: a stored file is
not a translation of anything, and reading it through the visitor locale
returned nothing for an image uploaded in another language. Names already stored
are carried over when the module updates. The old column is left in place, so
the migration can be checked and re-run if it was interrupted; nothing reads it
any more and it can be dropped once you are satisfied:
ALTER TABLE `library_image_i18n` DROP COLUMN `file_name`;
Code that read the name through a locale — $image->setLocale($x)->getFileName()
— keeps working and now returns the same value for every locale. Code that
queried LibraryImageI18nQuery::filterByFileName() has to read
library_image.file_name instead.