Skip to content

Latest commit

 

History

History
414 lines (318 loc) · 13.7 KB

File metadata and controls

414 lines (318 loc) · 13.7 KB

Template Overrides

.. versionadded:: 13.1.5

   The new Fluid-based rendering architecture allows complete customization
   of image output via template overrides.

Override the default Fluid templates to customize image rendering output for your site's design requirements.

The extension provides six Fluid templates for different rendering contexts:

Resources/Private/
├── Templates/Image/
│   ├── Standalone.html       # Basic image without wrapper
│   ├── WithCaption.html      # Image with <figure>/<figcaption>
│   ├── Link.html             # Image wrapped in <a> tag
│   ├── LinkWithCaption.html  # Linked image with caption
│   ├── Popup.html            # Image with lightbox/popup link
│   └── PopupWithCaption.html # Popup image with caption
└── Partials/Image/
    ├── Tag.html              # <img> element partial
    ├── TagInFigure.html      # <img> without class (for figures)
    ├── Link.html             # <a> wrapper partial
    └── Figure.html           # <figure> wrapper partial

Note

Partials ship in the standard TYPO3 location Resources/Private/Partials/Image/ (see #547). Templates live in Resources/Private/Templates/Image/ and layouts in Resources/Private/Templates/Layouts/. When overriding, point your partialRootPaths at your site package's Resources/Private/Partials/.

Warning

In EXT: paths, use the extension key rte_ckeditor_image (underscores) — not the Composer package name rte-ckeditor-image (hyphens). TYPO3 resolves EXT: references by extension key only, so a hyphenated path silently resolves to nothing and the override is ignored.

The :php:`ImageRenderingService` automatically selects the appropriate template:

Template selection matrix
Condition Template
No link, no caption :file:`Standalone.html`
No link, has caption :file:`WithCaption.html`
Has link, no popup, no caption :file:`Link.html`
Has link, no popup, has caption :file:`LinkWithCaption.html`
Has popup, no caption :file:`Popup.html`
Has popup, has caption :file:`PopupWithCaption.html`

Inline vs block rendering

Images are rendered differently depending on whether they appear as block elements (<figure>) or inline elements (<img> within text flow). Understanding this distinction is important when overriding templates:

Block images (<figure>):
Processed by renderFigure(). The template handles everything including the link wrapper. Uses :file:`Link.html`, :file:`LinkWithCaption.html`, :file:`Popup.html`, or :file:`PopupWithCaption.html`.
Inline images (<img> in text flow):
Processed by renderImageAttributes() for the <img> element. The template only renders the <img> tag — always :file:`Standalone.html`. The surrounding <a> wrapper is rebuilt by the default tags.a typolink in parseFunc_RTE (resolves t3:// URLs, applies rel="noreferrer", etc.); it is not rendered through a Fluid template.
Popup images:
Both inline and block use :file:`Popup.html` or :file:`PopupWithCaption.html` because popup attributes (lightbox data, click handler) require special template handling.
Handler and template by context
Context Link Caption Handler Template
Block No No renderFigure() :file:`Standalone.html`
Block No Yes renderFigure() :file:`WithCaption.html`
Block Yes No renderFigure() :file:`Link.html`
Block Yes Yes renderFigure() :file:`LinkWithCaption.html`
Block Popup No renderFigure() :file:`Popup.html`
Block Popup Yes renderFigure() :file:`PopupWithCaption.html`
Inline No renderImageAttributes() :file:`Standalone.html`
Inline Yes renderImageAttributes() (link rebuilt by default tags.a typolink) :file:`Standalone.html`
Inline Popup renderImageAttributes() :file:`Popup.html`

In your site package, create the override directory:

mkdir -p packages/my_sitepackage/Resources/Private/Templates/Image/

Add the template path to your TypoScript setup. The settings. block must live inside preUserFunc. — that is the only sub-array TYPO3 forwards to the rendering callable (see :php:`ContentObjectRenderer::stdWrap_preUserFunc()`, which passes only $conf['preUserFunc.'] to the user function). Placing settings.templateRootPaths as a sibling of preUserFunc has no effect — the configuration never reaches :php:`ImageRenderingService::buildTemplatePaths()`.

lib.parseFunc_RTE.tags.img.preUserFunc {
    # Add your templates with higher priority (higher number = higher priority)
    settings {
        templateRootPaths {
            10 = EXT:my_sitepackage/Resources/Private/Templates/
        }
        partialRootPaths {
            10 = EXT:my_sitepackage/Resources/Private/Partials/
        }
        layoutRootPaths {
            10 = EXT:my_sitepackage/Resources/Private/Layouts/
        }
    }
}

# Captioned images render as <figure> blocks and are processed via
# externalBlocks.figure.stdWrap.preUserFunc (renderFigure). The same
# settings. block has to be duplicated here for figure rendering.
lib.parseFunc_RTE.externalBlocks.figure.stdWrap.preUserFunc {
    settings {
        templateRootPaths {
            10 = EXT:my_sitepackage/Resources/Private/Templates/
        }
        partialRootPaths {
            10 = EXT:my_sitepackage/Resources/Private/Partials/
        }
        layoutRootPaths {
            10 = EXT:my_sitepackage/Resources/Private/Layouts/
        }
    }
}

Note

Figures are processed via externalBlocks, not tags.figure — placing the configuration under tags.figure has no effect. Inline images wrapped in <a> are rendered by tags.img (recursively, inside the link wrapper), so they already pick up the configuration defined under tags.img.preUserFunc.

Copy and modify only the templates you need to customize.

All templates receive the image variable containing an :php:`ImageRenderingDto`:

<!-- Core properties -->
{image.src}                    <!-- Processed image URL -->
{image.width}                  <!-- Display width in pixels -->
{image.height}                 <!-- Display height in pixels -->
{image.alt}                    <!-- Alternative text -->
{image.title}                  <!-- Title attribute -->
{image.caption}                <!-- Caption text (XSS-sanitized) -->
{image.isMagicImage}           <!-- Whether TYPO3 processing applied -->

<!-- HTML attributes -->
{image.htmlAttributes.class}   <!-- CSS classes -->
{image.htmlAttributes.style}   <!-- Inline styles -->
{image.htmlAttributes.loading} <!-- lazy/eager -->

<!-- Link properties (when linked) -->
{image.link.url}               <!-- Link URL -->
{image.link.target}            <!-- Link target (_blank, etc.) -->
{image.link.class}             <!-- Link CSS classes -->
{image.link.isPopup}           <!-- Whether popup/lightbox -->

See :ref:`api-imagerenderingdto` for complete property documentation.

Override :file:`Standalone.html` for Bootstrap 5 responsive images:

<img src="{image.src}"
     alt="{image.alt}"
     width="{image.width}"
     height="{image.height}"
     class="img-fluid {image.htmlAttributes.class}"
     {f:if(condition: image.title, then: 'title="{image.title}"')}
     {f:if(condition: image.htmlAttributes.style, then: 'style="{image.htmlAttributes.style}"')}
     loading="lazy"
     decoding="async" />

Override :file:`WithCaption.html` for custom figure styling:

<figure class="content-image{f:if(condition: image.htmlAttributes.class, then: ' {image.htmlAttributes.class}')}">
    <img src="{image.src}"
         alt="{image.alt}"
         width="{image.width}"
         height="{image.height}"
         class="content-image__img"
         {f:if(condition: image.title, then: 'title="{image.title}"')}
         loading="lazy"
         decoding="async" />
    <figcaption class="content-image__caption">
        {image.caption}
    </figcaption>
</figure>

Override :file:`Popup.html` for PhotoSwipe v5 integration:

<a href="{image.link.url}"
   class="pswp-gallery__item {image.link.class}"
   data-pswp-width="{image.width}"
   data-pswp-height="{image.height}"
   {f:if(condition: image.link.target, then: 'target="{image.link.target}"')}>
    <img src="{image.src}"
         alt="{image.alt}"
         width="{image.width}"
         height="{image.height}"
         {f:if(condition: image.title, then: 'title="{image.title}"')}
         {f:if(condition: image.htmlAttributes.class, then: 'class="{image.htmlAttributes.class}"')}
         loading="lazy"
         decoding="async" />
</a>

Override :file:`Standalone.html` for progressive image loading:

<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 {image.width} {image.height}'%3E%3C/svg%3E"
     data-src="{image.src}"
     alt="{image.alt}"
     width="{image.width}"
     height="{image.height}"
     class="lazyload {image.htmlAttributes.class}"
     {f:if(condition: image.title, then: 'title="{image.title}"')}
     {f:if(condition: image.htmlAttributes.style, then: 'style="{image.htmlAttributes.style}"')}
     decoding="async" />
  1. Only override what you need: Copy only templates requiring changes.
  2. Preserve accessibility: Always include alt attribute and maintain semantic HTML.
  3. Keep security intact: The DTO properties are pre-sanitized. Do not apply additional encoding that could double-escape content.
  4. Test all contexts: Verify overrides work with captions, links, and popups.
  5. Use native lazy loading: Prefer loading="lazy" over JavaScript solutions.
  6. CSS classes move to figure: When images have captions, CSS classes defined on the <img> element are applied to the <figure> wrapper instead. This ensures valid HTML5 semantics. If you need classes specifically on the <img> within a figure, create a custom :file:`WithCaption.html` template override.
  7. Decoding attribute: The default templates include decoding="async" on all images to improve rendering performance by allowing the browser to decode images off the main thread. This is a modern best practice that does not affect visual output.
  8. Whitespace is stripped: The rendering service removes whitespace between HTML tags to prevent parseFunc_RTE from creating <p>&nbsp;</p> artifacts. Templates can use readable multi-line formatting; it will be normalized. However, deliberate spacing between inline elements will be removed.

Enable Fluid debugging to inspect available variables:

<f:debug>{_all}</f:debug>

Or in TypoScript:

lib.parseFunc_RTE.settings.debug = 1