Site Module

A base module for all S. Group sites, featuring a few helpers and scaffolding.

Offline switch

There's a hard-offline setting that's useful for when taking a staging site offline. It's more heavy-handed than Craft's offline behaviour, as we want to prevent anyone from logging into Craft (to save confusion between environments).

Control it in your general.php config.

<?php

return [
    'staging' => [
        'isOffline' => true,
    ],
];

Vite & Resources

Adds a @resource alias for use in templates. This will resolve to either @webroot/../resources if the dev server is running, or @webroot/dist if not. The We store our static asset files outside of the webroot.

We also provide a resource() Twig function that does a similar thing, but resolves to your Vite plugin settings. devServerPublic if the dev server is running, or serverPublic if not. In practice this resolves to http://localhost:3000/ and /dist respectively.

Resources in Twig

Through these, you'll be able to refer to resources like JS, CSS, images and fonts - anything in your /resources folder in your templates. For example, a common scenario is wanting to include images in your Twig templates. You can use the following options to acheieve this.

{# Use the `@resources` alias #}
<img src="{{ alias('@resource/img/logo.png') }}">

{# Or use the `resource()` Twig function #}
<img src="{{ resource('/img/logo.png') }}">

If you need to inline an SVG, it's encouraged to use the svg() Twig function. With this, your only option is to use an alias.

{{ svg('@resource/img/logo.svg') }}

Base module

Yii's modules are pretty slim, and we loose a bunch of boilerplating that Craft plugins get for free. We add this in base/Module. Every Yii module for a project you create should extend this not Yii's module class.

use sgroup\sitemodule\base\Module;

class SiteMigration extends Module
{

Twig extensions

We provide a good bunch of Twig extensions for better template development.

svgPlaceholder($width, $height = null)

Given a width and height, this will generate a transparent SVG. This is most commonly used for the src attribute of <img> tags when lazyloading. This creates the correct dimensions for the image, while the real image is being lazyloaded. Doing this prevent a noticable "jump" when going from an image on zero dimensions to the proper one.

getFormattedPhone($value)

An opinionated, AU-based phone formatter. Throw it any phone number and it'll internationalize it, deal with spaces and area codes, ready for use in <a href="tel:"

getVideo($asset, $settings = [])

Given an asset, this will render an <iframe> or <video>. This handles both Embedded Asset plugin videos, or real uploaded videos. For any YouTube-based embedded asset, it'll render an <iframe> and embed the video URL.

You can also pass in attributes to be used either in the URL (as URL-encoded params) or in <video> attributes. You can include any attribute, but some worth mentioning:

OptionDescription
mutedWhether the video should be muted.
autoplayWhether the video should autoplay.
controlsWhether the video controls should be shown.

getImg($image, $transform = null, $lazyload = false, $attributes = [], $sizes = 'default')

This will return an <img> tag, pre-configured with a bunch of options.

OptionDescription
imageThe asset.
transformEither an array (for dynamic transform) or string for the transform.
lazyloadWhether the the image should be lazyloaded.
attributesA collection of attributes, added to the <img> element.
sizesAn array of valid sizes, used for srcset.

By default, we use srcset to provide ['1x', '1.5x', '2x', '3x'] sizes.

{% set asset = craft.assets.id(46).one() %}

{# Renders a small image #}
{{ getImg(asset, 'small') }}

<img src=".../image.jpg" srcset=".../image.jpg, .../image.jpg 1.5x, .../image.jpg 2x, .../image.jpg 3x" width="400" height="400" alt="Title" />
{# Renders a small image, lazyloaded #}
{{ getImg(asset, 'small', true) }}

<img class="lazyload" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgdmlld0JveD0iMCAwIDQwMCA0MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGg0MDB2NDAwSDB6IiBmaWxsPSJub25lIj48L3BhdGg+PC9zdmc+" width="400" height="400" alt="Title" data-src=".../image.jpg" data-srcset=".../image.jpg, .../image.jpg 1.5x, .../image.jpg 2x, .../image.jpg 3x" />
{# Renders a small image with extra classes and attributes #}
{{ getImg(asset, 'small', true, { class: ['testing', 'some'], 'data-attr': 'val' }) }}

<img class="lazyload testing some" data-attr="val" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgdmlld0JveD0iMCAwIDQwMCA0MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGg0MDB2NDAwSDB6IiBmaWxsPSJub25lIj48L3BhdGg+PC9zdmc+" width="400" height="400" alt="Title" data-src=".../image.jpg" />
{# Renders a small image, lazyloaded, no `srcset` #}
{{ getImg(asset, 'small', true, [], false) }}

<img class="lazyload" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgdmlld0JveD0iMCAwIDQwMCA0MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGg0MDB2NDAwSDB6IiBmaWxsPSJub25lIj48L3BhdGg+PC9zdmc+" width="400" height="400" alt="Title" data-src=".../image.jpg" />
{# Renders a small image - no `srcset` #}
{{ getImg(asset, 'small', false, [], false) }}

<img src=".../image.jpg" width="400" height="400" alt="Title" />
{# Using an object instead of a transform handle #}
{{ getImg(asset, { mode: 'crop', width: 100, height: 200 }) }}

<img src=".../image.jpg" width="100" height="200" alt="Title" />

Focal point

If a focal point is defined on the image, it'll be output via inline style rules.

<img src=".../image.jpg" style="object-fit: cover; object-position: 86.7% 85.88%;" />

getImgAttr()

This has the same options as getImg() except the $attributes. This will return an object of attributes for you to apply on your own. Ideally, you'd use the {{ attr() }} function.

{% set asset = craft.assets.id(47).one() %}

{# Renders a small image, lazyloaded #}
<img class="img-cover" {{ attr(getImgAttr(asset, 'small')) }} />

<img class="img-cover" src=".../image.jpg" srcset=".../image.jpg, .../image.jpg 1.5x, .../image.jpg 2x, .../image.jpg 3x" width="400" height="400" alt="Title" />
{# Renders a small image, lazyloaded #}
<img class="lazyload img-cover" {{ attr(getImgAttr(asset, 'small', true)) }} />

<img class="lazyload img-cover" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgdmlld0JveD0iMCAwIDQwMCA0MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGg0MDB2NDAwSDB6IiBmaWxsPSJub25lIj48L3BhdGg+PC9zdmc+" width="400" height="400" alt="Title" data-src=".../image.jpg" data-srcset=".../image.jpg, .../image.jpg 1.5x, .../image.jpg 2x, .../image.jpg 3x" />
{# Renders a small image, lazyloaded, no `srcset` #}
<img class="lazyload img-cover" {{ attr(getImgAttr(asset, 'small', true, false)) }} />

<img class="lazyload img-cover" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgdmlld0JveD0iMCAwIDQwMCA0MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGg0MDB2NDAwSDB6IiBmaWxsPSJub25lIj48L3BhdGg+PC9zdmc+" width="400" height="400" alt="Title" data-src=".../image.jpg" />
{# Renders a small image #}
<img class="img-cover" {{ attr(getImgAttr(asset, 'small', false)) }} />

<img class="img-cover" src=".../image.jpg" srcset=".../image.jpg, .../image.jpg 1.5x, .../image.jpg 2x, .../image.jpg 3x" width="400" height="400" alt="Title" />
{# Renders a small image, no `srcset` #}
<img class="img-cover" {{ attr(getImgAttr(asset, 'small', false, false)) }} />

<img class="img-cover" src=".../image.jpg" width="400" height="400" alt="Title" />

You'll notice on the instances we're using lazyloading the need to manually include the lazyload class because we're rendering the classes on our own. Alternatively, you could do:

{% set attributes = getImgAttr(asset, 'small', false, false) | merge({
    class: 'img-cover',
}) %}

<img {{ attr(attributes) }} />

This would combine the img-cover class in the template and the lazyload class from the function.

getBg()

This behaves in almost the exact same manner as getImg() but returns a <div> element.

{# Renders a banner image #}
{{ getBg(asset, 'banner', false, { class: 'img-cover aspect aspect-21x9' }) }}

<div class="img-cover aspect aspect-21x9" style="background-image: url('../image.jpg');"></div>
{# Renders a banner image, lazyloaded #}
{{ getBg(asset, 'banner', true, { class: 'img-cover aspect aspect-21x9' }) }}

<div class="lazyload img-cover aspect aspect-21x9" data-bgset="../image.jpg, ../image.jpg 1.5x, ../image.jpg 2x, ../image.jpg 3x"></div>
{# Renders a banner image, no `srcset` #}
{{ getBg(asset, 'banner', false, { class: 'img-cover aspect aspect-21x9' }, false) }}

<div class="img-cover aspect aspect-21x9" style="background-image: url('../image.jpg');"></div>
{# Renders a banner image, lazyloaded, no `srcset` #}
{{ getBg(asset, 'banner', true, { class: 'img-cover aspect aspect-21x9' }, false) }}

<div class="lazyload img-cover aspect aspect-21x9" data-bg="../image.jpg"></div>

Focal point

If a focal point is defined on the image, it'll be output via inline style rules.

<div style="background-image: url('../image.jpg'); background-position: 86.7% 85.88%;"></div>

getBgAttr()

This behaves in almost the exact same manner as getImgAttr(), just with different attributes.

{% set attributes = getBgAttr(asset, 'banner', false) %}
<div class="img-cover aspect aspect-21x9" {{ attr(attributes) }}></div>
{% set attributes = getBgAttr(asset, 'banner', true) %}
<div class="lazyload img-cover aspect aspect-21x9" {{ attr(attributes) }}></div>
{% set attributes = getBgAttr(asset, 'banner', false, false) %}
<div class="img-cover aspect aspect-21x9" {{ attr(attributes) }}></div>
{% set attributes = getBgAttr(asset, 'banner', true, false) %}
<div class="lazyload img-cover aspect aspect-21x9" {{ attr(attributes) }}></div>