silvershop-suite / product-gallery
Sortable media galleries for SilverShop products and variations: images plus consent-gated YouTube/Vimeo video embeds, in one sortable grid, with drag-sort + bulk upload, template accessors, and an overridable frontend include.
Package info
github.com/silvershop-suite/silvershop-product-gallery
Type:silverstripe-vendormodule
pkg:composer/silvershop-suite/product-gallery
Requires
Requires (Dev)
- cambis/silverstan: ^2.1
- phpunit/phpunit: ^11.3
- silverstripe/sqlite3: ^4
- squizlabs/php_codesniffer: ^3.7
Suggests
- silvershop-suite/cookieconsent: Consent-gates embedded YouTube/Vimeo videos by reusing its click-to-load [consent_embed] runtime; without it the gallery falls back to a built-in click-to-load so no third-party content loads before a click.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-19 19:35:22 UTC
README
Sortable media galleries for SilverShop products and
variations. Core gives each product and each variation a single has_one Image; this adds an ordered set of
additional media on top of it — images and embedded YouTube/Vimeo videos, interleaved in one sortable grid
(the Shopify "product media" model).
The module ships the data model, the CMS admin (drag-to-sort + per-item caption + bulk upload), template accessors, and an overridable frontend include. The bundled include carries small inline CSS and JavaScript (a thumbnail → main-stage swap and the video click-to-load); it is meant to be dropped in or copied into your theme and restyled. Rendering it yourself (see below) is pure markup — you size the images and lay out the videos however you like.
Requirements
silvershop/core^6 (SilverStripe CMS 6)- PHP ^8.3
symbiote/silverstripe-gridfieldextensions^5 (drag-to-sort; pulled in by core)colymba/gridfield-bulk-editing-tools^5.1 (multi-file bulk upload)silvershop-suite/cookieconsent(suggested, not required) — consent-gates the video embeds through its click-to-load runtime. Without it the gallery uses a built-in click-to-load fallback so no third-party content is fetched before an explicit click.
Installation
composer require silvershop-suite/product-gallery
Then run dev/build?flush=1.
How it works
- A
GalleryItemDataObject is one media item: anImageor an embedded video, plus aTitle(caption / alt text / video title) and aSortposition. ItsKindis one of:image— an uploadedImage(the original behaviour; existing rows default here, so upgrading is fully backward-compatible).video_embed— an external YouTube/Vimeo video: anEmbedUrlplus a requiredPosterimage.video_file— self-hosted upload. Reserved for a later release; the enum value exists so the schema is forward-compatible, but the authoring UI for it is intentionally deferred.
- It attaches to its owner through a polymorphic
Parenthas_one, so the same class serves both products and variations — and per-variation video comes for free. - A
GalleryExtensionis applied toSilverShop\Page\ProductandSilverShop\Model\Variation\Variation, adding aGalleryItemshas_many edited in a GridField with:- drag-to-reorder rows (
GridFieldOrderableRows, writesSort), - multi-file bulk upload — drag a batch of images onto the grid and each becomes an
imageGalleryItem(colymbaBulkUploader); videos are added via the add-new form (pick the type, paste the URL, add a poster), - add/edit/delete single records with a caption.
- On a product the grid is on the Images tab; on a variation it's in its edit form.
- drag-to-reorder rows (
- Core's existing single
Imagestays as the primary / featured image; the gallery is additional.
Video embeds, privacy and consent
Only YouTube and Vimeo are allow-listed. Author input (watch URL, share URL, embed URL or a bare id) is
normalised to the privacy-enhanced embed URL — https://www.youtube-nocookie.com/embed/{id} or
https://player.vimeo.com/video/{id}?dnt=1 — and an arbitrary iframe src is never produced.
Embeds are consent-gated. The template shows the poster with a play badge and never loads the third-party iframe until the visitor consents / clicks:
- When
silvershop-suite/cookieconsentis installed, the item emits that module's[consent_embed]placeholder contract (data-consent-embed/data-consent-category/data-embed-src+ Load / Always buttons), and its runtime builds the iframe on consent. - When it is absent, the module falls back to a built-in click-to-load: the iframe is created only on an explicit click, so still no third-party request is made before then.
$CookieConsentAvailable on the product / variation reports which path is in use.
Rendering (your theme)
Each owner gets:
$GalleryItems— the sorted gallery items (each has$Kind,$Title,$Image,$Poster,$EmbedSrc,$Thumbnail, and the$IsImage/$IsVideo/$IsEmbedflags).$AllImages— anArrayListofImageobjects: the primaryImage(if set) followed by the gallery images only. Unchanged — image-only, so existing loops and integrations keep working.$AllMedia— anArrayListof uniform view-model rows (the primary image folded in first, then every gallery item of every kind, in sort order; a variation with no media of its own falls back to its product's media). Each row:Kind,Title,Image,Poster,Thumbnail,EmbedSrc,Provider,IsImage/IsVideo/IsEmbed,CookieConsentAvailable.
Ready-made include
The module ships a default template, SilverShop\Includes\GalleryImages, that renders one large stage with a
thumbnail grid below it (images swap the main stage; video thumbs carry a play badge and open the consent-gated
player). Drop it into your product template in place of the standard image:
<div class="silvershop-product__media">
<% include SilverShop\Includes\GalleryImages %>
</div>
Override the include in your theme to restyle it (lightbox / zoom / variation-swap with your own JS).
Or render it yourself
<%-- Primary + gallery media, branching by kind --%> <% loop $AllMedia %> <% if $IsEmbed %> <%-- consent-gate $EmbedSrc yourself, e.g. via silvershop-suite/cookieconsent's [consent_embed] contract --%> <img src="$Poster.Fill(800,800).URL" alt="$Title"> <% else %> <img src="$Image.Fill(800,800).URL" alt="$Title"> <% end_if %> <% end_loop %> <%-- Or, images only (back-compat): --%> <% loop $AllImages %> <img src="$Fill(800,800).URL" alt="$Owner.Title"> <% end_loop %>
For a variation's media on the product page, loop $SelectedVariation.AllMedia (or whichever variation your theme has
selected).
License
BSD-3-Clause.
Credits
Built and maintained by Remy Vaartjes (remy@xd.nl) — part of the SilverShop-Suite.