src/dataresolver
CustomImageCmsElementResolver
<?php
declare(strict_types=1);
namespace CmsTheme2\DataResolver;
use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Cms\DataResolver\Element\AbstractCmsElementResolver;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;
use Shopware\Core\Content\Media\MediaDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Content\Cms\DataResolver\FieldConfig;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
use Shopware\Core\Content\Cms\SalesChannel\Struct\ImageStruct;
use Shopware\Core\Content\Media\MediaEntity;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\Framework\Uuid\Uuid;
class CustomImageCmsElementResolver extends AbstractCmsElementResolver
{
public function getType(): string
{
return 'custom-image';
}
public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
{
$config = $slot->getFieldConfig();
$imageConfig = $config->get('media');
$backgroundImageConfig = $config->get('mobileMedia');
$ids = [];
if (!$imageConfig || $imageConfig->isMapped() || $imageConfig->getValue() === null) {
//
} else {
array_push($ids, $imageConfig->getValue());
}
if (!$backgroundImageConfig || $backgroundImageConfig->isMapped() || $backgroundImageConfig->getValue() === null) {
//
} else {
array_push($ids, $backgroundImageConfig->getValue());
}
if (count($ids) === 0) {
return null;
}
$criteria = new Criteria($ids);
$criteriaCollection = new CriteriaCollection();
$criteriaCollection->add('media_' . $slot->getUniqueIdentifier(), MediaDefinition::class, $criteria);
return $criteriaCollection;
}
public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
{
$config = $slot->getFieldConfig();
$data = new ArrayEntity();
$data->setUniqueIdentifier(Uuid::randomHex());
$slot->setData($data);
$image = new ImageStruct();
$backgroundImage = new ImageStruct();
$imageConfig = $config->get('media');
$backgroundImageConfig = $config->get('mobileMedia');
if ($imageConfig && $imageConfig->getValue()) {
$this->addMediaEntity($slot, $image, $result, $imageConfig, $resolverContext);
}
$data->set('media', $image);
if ($backgroundImageConfig && $backgroundImageConfig->getValue()) {
$this->addMediaEntity($slot, $backgroundImage, $result, $backgroundImageConfig, $resolverContext);
}
$data->set('mobileMedia', $backgroundImage);
}
private function addMediaEntity(
CmsSlotEntity $slot,
ImageStruct $image,
ElementDataCollection $result,
FieldConfig $config,
ResolverContext $resolverContext
): void
{
if ($config->isMapped() && $resolverContext instanceof EntityResolverContext) {
/** @var MediaEntity|null $media */
$media = $this->resolveEntityValue($resolverContext->getEntity(), $config->getValue());
if ($media !== null) {
$image->setMediaId($media->getUniqueIdentifier());
$image->setMedia($media);
}
}
if ($config->isStatic()) {
$image->setMediaId($config->getValue());
$searchResult = $result->get('media_' . $slot->getUniqueIdentifier());
if (!$searchResult) {
return;
}
/** @var MediaEntity|null $media */
$media = $searchResult->get($config->getValue());
if (!$media) {
return;
}
$image->setMedia($media);
}
}
}
res
app
admin/src
module/sw-cms
blocks/text-image
custom-image-text-gallery
component
index
import template from './sw-cms-block-custom-image-text-gallery.html.twig';
import './sw-cms-block-custom-image-text-gallery.scss';
Shopware.Component.register('sw-cms-block-custom-image-text-gallery', template, {
template,
compatConfig: Shopware.compatConfig,
});
twig
{% block sw_cms_block_custom_image_text_gallery %}
<div class="sw-cms-block-image-text-gallery">
<div class="sw-cms-block-image-text-gallery__left">
<slot name="left-image"></slot>
<slot name="left-text"></slot>
<slot name="left-button"></slot>
</div>
<div class="sw-cms-block-image-text-gallery__center-left">
<slot name="center-left-image"></slot>
<slot name="center-left-text"></slot>
<slot name="center-left-button"></slot>
</div>
<div class="sw-cms-block-image-text-gallery__center-right">
<slot name="center-right-image"></slot>
<slot name="center-right-text"></slot>
<slot name="center-right-button"></slot>
</div>
<div class="sw-cms-block-image-text-gallery__right">
<slot name="right-image"></slot>
<slot name="right-text"></slot>
<slot name="right-button"></slot>
</div>
</div>
{% endblock %}
scss
.sw-cms-block-image-text-gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(195px, 1fr));
align-content: stretch;
align-items: start;
grid-gap: 40px;
.sw-cms-el-text {
padding: 20px;
}
.sw-cms-el-image.is--cover {
min-height: 100px;
}
.sw-cms-block-image-text-gallery__left,
.sw-cms-block-image-text-gallery__center-left,
.sw-cms-block-image-text-gallery__center-right,
.sw-cms-block-image-text-gallery__right {
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 20%);
}
}
.sw-cms-block-image-text-gallery-shop-button {
display: grid;
align-items: center;
}
preview
index
import template from './sw-cms-preview-custom-image-text-gallery.html.twig';
import './sw-cms-preview-custom-image-text-gallery.scss';
Shopware.Component.register("sw-cms-preview-custom-image-text-gallery", {
template,
compatConfig: Shopware.compatConfig,
computed: {
assetFilter() {
return Shopware.Filter.getByName('asset');
},
},
});
twig
{% block sw_cms_block_custom_image_text_gallery_preview %}
<div class="sw-cms-preview-custom-image-text-gallery">
<div class="sw-cms-preview-custom-image-text-gallery__left">
<div class="sw-cms-preview-custom-image-text-gallery__image">
<img :src="assetFilter('/administration/static/img/cms/preview_camera_small.jpg')" alt="">
</div>
<div class="sw-cms-preview-custom-image-text-gallery__text">
<h2>block 1</h2>
<p>this is block1</p>
</div>
<div class="sw-cms-slot sw-cms-slot-left-button">
<div class="sw-cms-el-buy-button">
<div class="sw-cms-el-buy-button__actions">
<a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small">Shop</a>
</div>
</div>
<div class="sw-cms-slot__preview-overlay"></div>
</div>
</div>
<div class="sw-cms-preview-custom-image-text-gallery__center-left">
<div class="sw-cms-preview-custom-image-text-gallery__image">
<img :src="assetFilter('/administration/static/img/cms/preview_plant_small.jpg')" alt="">
</div>
<div class="sw-cms-preview-custom-image-text-gallery__text">
<h2>block 2</h2>
<p>this is block2</p>
</div>
<div class="sw-cms-slot sw-cms-slot-center-left-button">
<div class="sw-cms-el-buy-button">
<div class="sw-cms-el-buy-button__actions">
<a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small">Shop</a>
</div>
</div>
<div class="sw-cms-slot__preview-overlay"></div>
</div>
</div>
<div class="sw-cms-preview-custom-image-text-gallery__center-right">
<div class="sw-cms-preview-custom-image-text-gallery__image">
<img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')" alt="">
</div>
<div class="sw-cms-preview-custom-image-text-gallery__text">
<h2>block 3</h2>
<p>this is block3</p>
</div>
<div class="sw-cms-slot sw-cms-slot-center-right-button">
<div class="sw-cms-el-buy-button">
<div class="sw-cms-el-buy-button__actions">
<a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small">Shop</a>
</div>
</div>
<div class="sw-cms-slot__preview-overlay"></div>
</div>
</div>
<div class="sw-cms-preview-custom-image-text-gallery__right">
<div class="sw-cms-preview-custom-image-text-gallery__image">
<img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')" alt="">
</div>
<div class="sw-cms-preview-custom-image-text-gallery__text">
<h2>block 4</h2>
<p>this is block4</p>
</div>
<div class="sw-cms-slot sw-cms-slot-right-button">
<div class="sw-cms-el-buy-button">
<div class="sw-cms-el-buy-button__actions">
<a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small">Shop</a>
</div>
</div>
<div class="sw-cms-slot__preview-overlay"></div>
</div>
</div>
</div>
{% endblock %}
scss
.sw-cms-preview-custom-image-text-gallery {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 10px;
padding: 15px;
.sw-cms-preview-custom-image-text-gallery__left,
.sw-cms-preview-custom-image-text-gallery__center-left,
.sw-cms-preview-custom-image-text-gallery__center-right,
.sw-cms-preview-custom-image-text-gallery__right {
box-shadow: 0 0 3px 0 rgba(0, 0, 0, 20%);
}
.sw-cms-preview-custom-image-text-gallery__image {
height: 60px;
}
.sw-cms-preview-custom-image-text-gallery__text {
padding: 8px;
text-align: center;
}
img {
display: block;
object-fit: cover;
width: 100%;
height: 100%;
}
}
.sw-cms-block-image-text-gallery-shop-button {
display: grid;
align-items: center;
.sw-button {
height: 30px;
width: auto;
padding: 25px;
}
.sw-cms-el-buy-button__buy-action{
font-size: smaller;
}
}
index.js
import CMS from '../../../constant/sw-cms.constant';
import "./preview";
import "./component";
Shopware.Service("cmsService").registerCmsBlock({
name: "custom-image-text-gallery",
label: "Custom Image Text Gallery Block",
category: "text-image",
component: "sw-cms-block-custom-image-text-gallery",
previewComponent: "sw-cms-preview-custom-image-text-gallery",
defaultConfig: {
marginBottom: "20px",
marginTop: "20px",
marginLeft: "20px",
marginRight: "20px",
sizingMode: "boxed",
},
slots: {
"left-image": {
type: "image",
default: {
config: {
displayMode: { source: "static", value: "cover" },
},
data: {
media: {
value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
source: "default",
},
},
},
},
"left-text": {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">block 1</h2>
<p style="text-align: center;">this is block1</p>
`.trim(),
},
},
},
},
"left-button": {
type: "buy-button",
default: {
config: {
name: {
source: "static",
value: "Shop",
required: true,
},
link: {
source: "static",
value: null,
},
},
},
},
"center-left-image": {
type: "image",
default: {
config: {
displayMode: { source: "static", value: "cover" },
},
data: {
media: {
value: "bundles/administration/static/img/cms/preview_glasses_large.jpg",
source: "default",
},
},
},
},
"center-left-text": {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">block 2</h2>
<p style="text-align: center;">this is block2</p>
`.trim(),
},
},
},
},
"center-left-button": {
type: "buy-button",
default: {
config: {
name: {
source: "static",
value: "Shop",
required: true,
},
link: {
source: "static",
value: null,
},
},
},
},
"center-right-image": {
type: "image",
default: {
config: {
displayMode: { source: "static", value: "cover" },
},
data: {
media: {
value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
source: "default",
},
},
},
},
"center-right-text": {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">block3</h2>
<p style="text-align: center;">this is block3</p>
`.trim(),
},
},
},
},
"center-right-button": {
type: "buy-button",
default: {
config: {
name: {
source: "static",
value: "Shop",
required: true,
},
link: {
source: "static",
value: null,
},
},
},
},
"right-image": {
type: "image",
default: {
config: {
displayMode: { source: "static", value: "cover" },
},
data: {
media: {
value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
source: "default",
},
},
},
},
"right-text": {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">block 4</h2>
<p style="text-align: center;">this is block4</p>
`.trim(),
},
},
},
},
"right-button": {
type: "buy-button",
default: {
config: {
name: {
source: "static",
value: "Shop",
required: true,
},
link: {
source: "static",
value: null,
},
},
},
},
},
});
image-text-gallery/custom-image-text-gallery
index.js
import CMS from '../../../constant/sw-cms.constant';
import "./preview";
import "./component";
Shopware.Service("cmsService").registerCmsBlock({
name: "custom-image-text-gallery",
label: "Custom Image Text Gallery Block",
category: "text-image",
component: "sw-cms-block-custom-image-text-gallery",
previewComponent: "sw-cms-preview-custom-image-text-gallery",
defaultConfig: {
marginBottom: "20px",
marginTop: "20px",
marginLeft: "20px",
marginRight: "20px",
sizingMode: "boxed",
},
slots: {
"left-image": {
type: "image",
default: {
config: {
displayMode: { source: "static", value: "cover" },
},
data: {
media: {
value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
source: "default",
},
},
},
},
"left-text": {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">block 1</h2>
<p style="text-align: center;">this is block1</p>
`.trim(),
},
},
},
},
"left-button": {
type: "buy-button",
default: {
config: {
name: {
source: "static",
value: "Shop",
required: true,
},
link: {
source: "static",
value: null,
},
},
},
},
"center-left-image": {
type: "image",
default: {
config: {
displayMode: { source: "static", value: "cover" },
},
data: {
media: {
value: "bundles/administration/static/img/cms/preview_glasses_large.jpg",
source: "default",
},
},
},
},
"center-left-text": {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">block 2</h2>
<p style="text-align: center;">this is block2</p>
`.trim(),
},
},
},
},
"center-left-button": {
type: "buy-button",
default: {
config: {
name: {
source: "static",
value: "Shop",
required: true,
},
link: {
source: "static",
value: null,
},
},
},
},
"center-right-image": {
type: "image",
default: {
config: {
displayMode: { source: "static", value: "cover" },
},
data: {
media: {
value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
source: "default",
},
},
},
},
"center-right-text": {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">block 3</h2>
<p style="text-align: center;">this is block3</p>
`.trim(),
},
},
},
},
"center-right-button": {
type: "buy-button",
default: {
config: {
name: {
source: "static",
value: "Shop",
required: true,
},
link: {
source: "static",
value: null,
},
},
},
},
"right-image": {
type: "image",
default: {
config: {
displayMode: { source: "static", value: "cover" },
},
data: {
media: {
value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
source: "default",
},
},
},
},
"right-text": {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">block 4</h2>
<p style="text-align: center;">this is block4</p>
`.trim(),
},
},
},
},
"right-button": {
type: "buy-button",
default: {
config: {
name: {
source: "static",
value: "Shop",
required: true,
},
link: {
source: "static",
value: null,
},
},
},
},
},
});
text-image-text
component
index.js
import template from './sw-cms-block-text-image-text.html.twig';
import './sw-cms-block-text-image-text.scss';
Shopware.Component.register('sw-cms-block-text-image-text', {
template,
compatConfig: Shopware.compatConfig,
});
twig
{% block sw_cms_block_text_image_text %}
<div class="sw-cms-block-text-image-text">
<div class="sw-cms-block-text-image-text__text-left">
<slot name="left"></slot>
</div>
<div class="sw-cms-block-text-image-text__image">
<slot name="center"></slot>
</div>
<div class="sw-cms-block-text-image-text__text-right">
<slot name="right"></slot>
</div>
</div>
{% endblock %}
scss
.sw-cms-block-text-image-text {
display: flex;
flex-wrap: wrap;
.sw-cms-block-text-image-text__text-left,
.sw-cms-block-text-image-text__text-right {
flex-grow: 2;
flex-shrink: 0;
flex-basis: 300px;
}
.sw-cms-block-text-image-text__image {
flex-grow: 1;
flex-shrink: 0;
flex-basis: 240px;
}
.sw-cms-el-image.is--cover {
min-height: 340px;
}
.sw-cms-el-text {
padding: 30px;
}
}
preview
inddex.js
import template from './sw-cms-preview-text-image-text.html.twig';
import './sw-cms-preview-text-image-text.scss';
Shopware.Component.register('sw-cms-preview-text-image-text', {
template,
compatConfig: Shopware.compatConfig,
computed: {
assetFilter() {
return Shopware.Filter.getByName('asset');
},
},
});
twig
{% block sw_cms_block_text_image_text_preview %}
<div class="sw-cms-preview-text-image-text">
<div class="sw-cms-preview-text-image-text__text-left">
<h2>Hello</h2>
<p>world</p>
</div>
<img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')"
alt="">
<div class="sw-cms-preview-text-image-text__text-right">
<h2>Hello</h2>
<p>world</p>
</div>
</div>
{% endblock %}
scss
.sw-cms-preview-text-image-text {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
padding: 15px;
.sw-cms-preview-text-image-text__text-left,
.sw-cms-preview-text-image-text__text-right {
padding: 15px;
text-align: center;
height: 120px;
}
img {
display: block;
object-fit: cover;
width: 100%;
height: 100%;
}
}
index.js
import CMS from "../../../constant/sw-cms.constant";
import "./preview";
import "./component";
Shopware.Service("cmsService").registerCmsBlock({
name: "text-image-text",
label: "Custom text-image-text",
category: "text-image",
component: "sw-cms-block-text-image-text",
previewComponent: "sw-cms-preview-text-image-text",
defaultConfig: {
marginBottom: "20px",
marginTop: "20px",
marginLeft: "20px",
marginRight: "20px",
sizingMode: "boxed",
},
slots: {
left: {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">Hello</h2>
<p style="text-align: center;">world</p>
`.trim(),
},
},
},
},
center: {
type: "custom-image",
default: {
config: {
displayMode: { source: "static", value: "cover" },
},
data: {
media: {
value: CMS.MEDIA.previewGlasses,
source: "default",
},
},
},
},
right: {
type: "text",
default: {
config: {
content: {
source: "static",
value: `
<h2 style="text-align: center;">hello</h2>
<p style="text-align: center;">world</p>
`.trim(),
},
},
},
},
},
});
constant
sw-cms.constant.ts
const uniqueSlotsKebab = [
'buy-box',
'product-description-reviews',
'cross-selling',
];
/**
* @sw-package discovery
*/
// eslint-disable-next-line sw-deprecation-rules/private-feature-declarations
export default Object.freeze({
REQUIRED_FIELD_ERROR_CODE: 'c1051bb4-d103-4f74-8988-acbcafc7fdc3',
PAGE_TYPES: {
SHOP: 'page',
LANDING: 'landingpage',
LISTING: 'product_list',
PRODUCT_DETAIL: 'product_detail',
},
TYPE_MAPPING_ENTITIES: {
product_detail: {
entity: 'product',
mode: 'single',
},
product_list: {
entity: 'category',
mode: 'single',
},
},
UNIQUE_SLOTS: uniqueSlotsKebab.map((slotName) => slotName.replace(/-./g, (char) => char.toUpperCase()[1])),
UNIQUE_SLOTS_KEBAB: uniqueSlotsKebab,
SLOT_POSITIONS: {
left: 0,
'left-image': 100,
'left-top': 200,
'left-text': 300,
'left-bottom': 400,
'center-left': 1000,
center: 1100,
'center-image': 1200,
'center-top': 1300,
'center-text': 1400,
'center-bottom': 1500,
'center-right': 1600,
right: 2000,
'right-image': 2100,
'right-top': 2200,
'right-text': 2300,
'right-bottom': 2400,
content: 3000,
image: 3100,
video: 3200,
imageSlider: 3300,
default: 5000,
},
MEDIA: {
previewCamera: 'bundles/administration/static/img/cms/preview_camera_large.jpg',
previewMountain: 'bundles/administration/static/img/cms/preview_mountain_large.jpg',
previewPlant: 'bundles/administration/static/img/cms/preview_plant_large.jpg',
previewGlasses: 'bundles/administration/static/img/cms/preview_glasses_large.jpg',
SMALL:{
previewCamera: 'framework/assets/default/cms/preview_camera_small.jpg',
previewMountain: 'framework/assets/default/cms/preview_mountain_small.jpg',
previewPlant: 'framework/assets/default/cms/preview_plant_small.jpg',
previewGlasses: 'framework/assets/default/cms/preview_glasses_small.jpg',
}
},
});
elements
custom-image
componeny
index.js
import CMS from '../../../constant/sw-cms.constant';
import template from './sw-cms-el-custom-image.html.twig';
import './sw-cms-el-custom-image.scss';
const { Component, Mixin, Filter } = Shopware;
Component.register('sw-cms-el-custom-image', {
template,
mixins: [
Mixin.getByName('cms-element'),
],
computed: {
mediaUrl() {
const fallBackImageFileName = CMS.MEDIA.previewPlant.slice(CMS.MEDIA.previewPlant.lastIndexOf('/') + 1);
const staticFallBackImage = this.assetFilter(`administration/static/img/cms/${fallBackImageFileName}`);
const elemData = this.element?.data?.media || {};
const elemConfig = this.element?.config?.media || {};
if (elemConfig.source === 'mapped') {
const demoMedia = this.getDemoValue(elemConfig.value);
if (demoMedia?.url) {
return demoMedia.url;
}
return staticFallBackImage;
}
if (elemConfig.source === 'default' && typeof elemConfig.value === 'string') {
const fileName = elemConfig.value.slice(elemConfig.value.lastIndexOf('/') + 1);
return this.assetFilter(`/administration/static/img/cms/${fileName}`);
}
if (elemData?.id) {
return this.element.data.media.url;
}
if (elemData?.url) {
return elemData.url;
}
return staticFallBackImage;
},
assetFilter() {
return Filter.getByName('asset');
},
mediaConfigValue() {
return this.element?.config?.media?.value;
},
},
watch: {
'cmsPageState.currentDemoEntity': {
handler() {
this.updateDemoValue(this.mediaConfigValue);
},
},
mediaConfigValue(value) {
this.updateDemoValue(value);
},
},
created() {
this.createdComponent();
},
methods: {
createdComponent() {
this.initElementConfig('custom-image');
this.initElementData('custom-image');
},
updateDemoValue(value) {
const mediaId = this.element?.data?.media?.id;
const isSourceStatic = this.element?.config?.media?.source === 'static';
if (isSourceStatic && mediaId && value !== mediaId) {
this.element.config.media.value = mediaId;
}
},
},
});
twig
{% block sw_cms_element_custom_image %}
<div class="sw-cms-el-custom-image">
{% block sw_cms_element_custom_image_content %}
<img :src="mediaUrl" alt="">
{% endblock %}
</div>
{% endblock %}
scss
.sw-cms-el-custom-image {
display: flex;
max-width: 100%;
img {
display: block;
max-width: 100%;
}
&.is--cover {
height: 100%;
width: 100%;
position: relative;
margin: auto;
border-radius: 50%;
background-clip: padding-box;
overflow: hidden;
img {
object-fit: cover;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
&.is--stretch {
img {
width: 100%;
}
}
}
config
index.js
import template from './sw-cms-el-config-custom-image.html.twig';
import './sw-cms-el-config-custom-image.scss';
const { Component, Mixin } = Shopware;
Component.register('sw-cms-el-config-custom-image', {
template,
compatConfig: Shopware.compatConfig,
inject: ['repositoryFactory'],
emits: ['element-update'],
mixins: [
Mixin.getByName('cms-element'),
],
data() {
return {
mediaModalIsOpen: false,
initialFolderId: null,
};
},
computed: {
mediaRepository() {
return this.repositoryFactory.create('media');
},
uploadTag() {
return `cms-element-media-config-${this.element.id}`;
},
previewSource() {
if (this.element?.data?.media?.id) {
return this.element.data.media;
}
return this.element.config.media.value;
},
},
created() {
this.createdComponent();
},
methods: {
createdComponent() {
this.initElementConfig('image');
},
async onImageUpload({ targetId }) {
const mediaEntity = await this.mediaRepository.get(targetId);
this.element.config.media.value = mediaEntity.id;
this.element.config.media.source = 'static';
this.updateElementData(mediaEntity);
this.$emit('element-update', this.element);
},
onImageRemove() {
this.element.config.media.value = null;
this.updateElementData();
this.$emit('element-update', this.element);
},
onCloseModal() {
this.mediaModalIsOpen = false;
},
onSelectionChanges(mediaEntity) {
const media = mediaEntity[0];
this.element.config.media.value = media.id;
this.element.config.media.source = 'static';
this.updateElementData(media);
this.$emit('element-update', this.element);
},
updateElementData(media = null) {
const mediaId = media === null ? null : media.id;
if (!this.element.data) {
if (this.isCompatEnabled('INSTANCE_SET')) {
this.$set(this.element, 'data', { mediaId, media });
} else {
this.element.data = { mediaId, media };
}
return;
}
if (this.isCompatEnabled('INSTANCE_SET')) {
this.$set(this.element.data, 'mediaId', mediaId);
this.$set(this.element.data, 'media', media);
} else {
this.element.data.mediaId = mediaId;
this.element.data.media = media;
}
},
onOpenMediaModal() {
this.mediaModalIsOpen = true;
},
onChangeMinHeight(value) {
this.element.config.minHeight.value = value === null ? '' : value;
this.$emit('element-update', this.element);
},
onChangeDisplayMode() {
this.$emit('element-update', this.element);
},
onChangeIsDecorative(value) {
this.element.config.isDecorative.value = value;
this.$emit('element-update', this.element);
},
},
});
twig
{% block sw_cms_element_image_config %}
<div class="sw-cms-el-config-image">
{% block sw_cms_element_image_config_media_upload %}
<sw-cms-mapping-field v-model:config="element.config.media" :label="$tc('sw-cms.elements.image.label')" value-types="entity" entity="media">
<sw-media-upload-v2
variant="regular"
:upload-tag="uploadTag"
:source="previewSource"
:allow-multi-select="false"
:default-folder="cmsPageState.pageEntityName"
:caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
@media-upload-sidebar-open="onOpenMediaModal"
@media-upload-remove-image="onImageRemove"/>
<template #preview="{ demoValue }">
<div class="sw-cms-el-config-image__mapping-preview">
<img v-if="demoValue.url" :src="demoValue.url" alt="">
<sw-alert v-else class="sw-cms-el-config-image__preview-info" variant="info">
{{ $tc('sw-cms.detail.label.mappingEmptyPreview') }}
</sw-alert>
</div>
</template>
</sw-cms-mapping-field>
<sw-upload-listener
:upload-tag="uploadTag"
auto-upload @media-upload-finish="onImageUpload"/>
{% endblock %}
{% block sw_cms_element_image_config_display_mode %}
<sw-select-field v-model:value="element.config.displayMode.value"
class="sw-cms-el-config-image__display-mode"
:label="$tc('sw-cms.elements.general.config.label.displayMode')"
:help-text="$tc('sw-cms.elements.general.config.helpText.displayMode')"
@update:value="onChangeDisplayMode">
<option value="standard">
{{ $tc('sw-cms.elements.general.config.label.displayModeStandard') }}
</option>
<option value="stretch">
{{ $tc('sw-cms.elements.general.config.label.displayModeStretch') }}
</option>
<option value="cover">
{{ $tc('sw-cms.elements.general.config.label.displayModeCover') }}
</option>
</sw-select-field>
{% endblock %}
<template v-if="element.config.displayMode.value === 'cover'">
{% block sw_cms_element_image_config_min_height %}
<sw-text-field v-model:value="element.config.minHeight.value" :label="$tc('sw-cms.elements.image.config.label.minHeight')" :placeholder="$tc('sw-cms.elements.image.config.placeholder.enterMinHeight')" @update:value="onChangeMinHeight"/>
{% endblock %}
</template>
{% block sw_cms_element_image_config_vertical_align %}
<sw-select-field v-model:value="element.config.verticalAlign.value" :label="$tc('sw-cms.elements.general.config.label.verticalAlign')" :placeholder="$tc('sw-cms.elements.general.config.label.verticalAlign')" :disabled="['cover'].includes(element.config.displayMode.value)">
<option value="flex-start">
{{ $tc('sw-cms.elements.general.config.label.verticalAlignTop') }}
</option>
<option value="center">
{{ $tc('sw-cms.elements.general.config.label.verticalAlignCenter') }}
</option>
<option value="flex-end">
{{ $tc('sw-cms.elements.general.config.label.verticalAlignBottom') }}
</option>
</sw-select-field>
{% endblock %}
{% block sw_cms_element_image_config_horizontal_align %}
<sw-select-field
v-model:value="element.config.horizontalAlign.value"
:label="$tc('sw-cms.elements.general.config.label.horizontalAlign')"
:placeholder="$tc('sw-cms.elements.general.config.label.horizontalAlign')"
:disabled="element.config.displayMode.value === 'cover'">
<option value="flex-start">
{{ $tc('sw-cms.elements.general.config.label.horizontalAlignLeft') }}
</option>
<option value="center">
{{ $tc('sw-cms.elements.general.config.label.horizontalAlignCenter') }}
</option>
<option value="flex-end">
{{ $tc('sw-cms.elements.general.config.label.horizontalAlignRight') }}
</option>
</sw-select-field>
{% endblock %}
{% block sw_cms_element_image_config_link %}
<div class="sw-cms-el-config-image__link">
<sw-dynamic-url-field
v-model:value="element.config.url.value"/>
<sw-switch-field
v-model:value="element.config.newTab.value"
class="sw-cms-el-config-image__link-tab"
:label="$tc('sw-cms.elements.image.config.label.linkNewTab')"/>
</div>
{% endblock %}
<mt-switch class="sw-cms-el-config-image__is-decorative" :checked="element.config.isDecorative.value" :label="$tc('sw-cms.elements.image.config.label.isDecorative')" @change="onChangeIsDecorative"/>
{% block sw_cms_element_image_config_media_modal %}
<sw-media-modal-v2
v-if="mediaModalIsOpen"
variant="full"
:caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
:entity-context="cmsPageState.entityName"
:allow-multi-select="false"
:initial-folder-id="cmsPageState.defaultMediaFolderId"
@media-upload-remove-image="onImageRemove"
@media-modal-selection-change="onSelectionChanges"
@modal-close="onCloseModal"/>
{% endblock %}
</div>
{% endblock %}
scs
.sw-cms-el-config-image {
.sw-media-upload-v2 {
margin-bottom: 22px;
.sw-media-upload-v2__header {
display: none;
}
}
.sw-cms-el-config-image__mapping-preview {
display: grid;
width: 100%;
height: 200px;
justify-items: center;
align-items: start;
img {
width: 100%;
height: 100%;
object-fit: contain;
overflow: hidden;
}
.sw-cms-el-config-image__preview-info {
width: 100%;
}
}
}
preview
index.js
import template from "./sw-cms-el-preview-custom-image.html.twig";
import "./sw-cms-el-preview-custom-image.scss";
const { Component } = Shopware;
Component.register("sw-cms-el-preview-custom-image", {
template,
computed: {
assetFilter() {
return Shopware.Filter.getByName("asset");
},
},
});
twig
{% block sw_cms_element_image_preview %}
<div class="sw-cms-el-preview-image">
<img :src="assetFilter('/administration/static/img/cms/preview_mountain_small.jpg')" alt="">
</div>
{% endblock %}
scss
.sw-cms-el-preview-image {
width: 100%;
height: 100%;
overflow: hidden;
img {
margin: 0;
display: block;
width: 110%;
}
}
index.js
import './component';
import './config';
import './preview';
Shopware.Service('cmsService').registerCmsElement({
name: 'custom-image',
label: 'Custom Image',
component: 'sw-cms-el-custom-image',
configComponent: 'sw-cms-el-config-custom-image',
previewComponent: 'sw-cms-el-preview-custom-image',
defaultConfig: {
media: {
source: 'static',
value: null,
required: true,
entity: {
name: 'media',
},
},
url: {
source: 'static',
value: null,
},
},
});
snippet
de
{
"sw-cms": {
"elements": {
"general": {
"config": {
"caption": {
"mediaUpload": "Upload an image"
},
"label": {
"verticalAlign": "Vertical align",
"alignment": "Alignment",
"verticalAlignTop": "Top",
"verticalAlignCenter": "Center",
"verticalAlignBottom": "Bottom",
"displayMode": "Display mode",
"displayModeStandard": "Original",
"displayModeCover": "Cropped",
"displayModeStretch": "Scaled",
"displayModeContain": "Fixed height",
"autoSlide": "Automatic transition",
"autoplayTimeout": "Delay",
"speed": "Animation duration",
"horizontalAlign": "Horizontal align",
"horizontalAlignLeft": "Left",
"horizontalAlignCenter": "Center",
"horizontalAlignRight": "Right"
},
"helpText": {
"autoSlide": "Temporarily disabled due to lack of accessibility.",
"autoplayTimeout": "The millisecond duration for how long the slide should stay still.",
"speed": "The millisecond duration of the sliding animation.",
"displayMode": "<strong>Original:</strong> The image is displayed in its original resolution or shrunk down to current CMS page's width.<br><br><strong>Scaled:</strong> The image is scaled to the CMS page's width without any height restrictions.<br><br><strong>Cropped:</strong> The image is scaled to the current CMS page's width. Any vertical overflow is cropped to the configured maximum height."
},
"tab": {
"content": "Content",
"settings": "Settings",
"options": "Options"
},
"infoText": {
"listingElement": "This element's product data is automatically loaded by the category associated with this layout. The content just shows a sample preview."
}
},
"switch": {
"groups": {
"all": "All elements",
"favorites": "Favourite elements"
}
}
},
"buyButton": {
"label": "Buy Button",
"infoText": {
"tooltipSettingDisabled": "This element's product data is automatically loaded by the product associated with this layout. The content just shows a sample preview."
},
"config": {
"label": {
"buttonText": "Button Text",
"redirectLink": "Redirect Link"
},
"placeholder": {
"buttonText": "Enter Button Text ...",
"redirectLink": "/#"
}
},
"component": {
"label": {
"actionBuy": "Add to cart",
"taxInfo": "Prices incl. VAT plus shipping costs",
"deliveryShippingFree": "Free shipping",
"deliveryTime": "Available, delivery time {name}",
"variants": "Variants",
"productNumber": "Product number:"
}
}
}
},
"blocks": {
"textImage": {
"centerText": {
"label": "Three columns, boxed images & text"
},
"imageText": {
"label": "Two columns, boxed image & text"
},
"imageTextBubble": {
"label": "Three columns, captioned & rounded"
},
"imageTextCover": {
"label": "Two columns, full-sized image & text"
},
"imageTextGallery": {
"label": "Three columns, image/text cards"
},
"imageTextRow": {
"label": "Three columns, captioned images"
},
"textOnImage": {
"label": "Hero image"
}
}
}
}
}
en
{
"sw-cms": {
"elements": {
"general": {
"config": {
"caption": {
"mediaUpload": "Upload an image"
},
"label": {
"verticalAlign": "Vertical align",
"alignment": "Alignment",
"verticalAlignTop": "Top",
"verticalAlignCenter": "Center",
"verticalAlignBottom": "Bottom",
"displayMode": "Display mode",
"displayModeStandard": "Original",
"displayModeCover": "Cropped",
"displayModeStretch": "Scaled",
"displayModeContain": "Fixed height",
"autoSlide": "Automatic transition",
"autoplayTimeout": "Delay",
"speed": "Animation duration",
"horizontalAlign": "Horizontal align",
"horizontalAlignLeft": "Left",
"horizontalAlignCenter": "Center",
"horizontalAlignRight": "Right"
},
"helpText": {
"autoSlide": "Temporarily disabled due to lack of accessibility.",
"autoplayTimeout": "The millisecond duration for how long the slide should stay still.",
"speed": "The millisecond duration of the sliding animation.",
"displayMode": "<strong>Original:</strong> The image is displayed in its original resolution or shrunk down to current CMS page's width.<br><br><strong>Scaled:</strong> The image is scaled to the CMS page's width without any height restrictions.<br><br><strong>Cropped:</strong> The image is scaled to the current CMS page's width. Any vertical overflow is cropped to the configured maximum height."
},
"tab": {
"content": "Content",
"settings": "Settings",
"options": "Options"
},
"infoText": {
"listingElement": "This element's product data is automatically loaded by the category associated with this layout. The content just shows a sample preview."
}
},
"switch": {
"groups": {
"all": "All elements",
"favorites": "Favourite elements"
}
}
},
"buyButton": {
"label": "Buy Button",
"infoText": {
"tooltipSettingDisabled": "This element's product data is automatically loaded by the product associated with this layout. The content just shows a sample preview."
},
"config": {
"label": {
"buttonText": "Button Text",
"redirectLink": "Redirect Link"
},
"placeholder": {
"buttonText": "Enter Button Text ...",
"redirectLink": "/#"
}
},
"component": {
"label": {
"actionBuy": "Add to cart",
"taxInfo": "Prices incl. VAT plus shipping costs",
"deliveryShippingFree": "Free shipping",
"deliveryTime": "Available, delivery time {name}",
"variants": "Variants",
"productNumber": "Product number:"
}
}
}
},
"blocks": {
"textImage": {
"centerText": {
"label": "Three columns, boxed images & text"
},
"imageText": {
"label": "Two columns, boxed image & text"
},
"imageTextBubble": {
"label": "Three columns, captioned & rounded"
},
"imageTextCover": {
"label": "Two columns, full-sized image & text"
},
"imageTextGallery": {
"label": "Three columns, image/text cards"
},
"imageTextRow": {
"label": "Three columns, captioned images"
},
"textOnImage": {
"label": "Hero image"
}
}
}
}
}
main.js
import './module/sw-cms/blocks/text-image/text-image-text';
import './module/sw-cms/elements/custom-image';
import './module/sw-cms/blocks/text-image/custom-image-text-gallery';
import './module/sw-cms/elements/buy-button';
storefront keep it as it is
config
services.xmml
<?xml version="1.0"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="CmsTheme2\DataResolver\CustomImageCmsElementResolver"
class="CmsTheme2\DataResolver\CustomImageCmsElementResolver">
<tag name="shopware.cms.data_resolver" />
</service>
<service id="CmsTheme2\DataResolver\CustomImageTextGalleryCmsElementResolver"
class="CmsTheme2\DataResolver\CustomImageTextGalleryCmsElementResolver">
<tag name="shopware.cms.data_resolver" />
</service>
</services>
</container>
public/admin keep it as it is
views/storefront
block
custom-block-text-image-text.html.twig
{% block block_text_image_text %}
{# {{ dump('test') }}#}
{% set columns = 3 %}
{% block block_left %}
{% set element = block.slots.getSlot('left') %}
<div class="col-md-4" data-cms-element-id="{{ element.id }}">
{% block block_left_inner %}
{% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
{% endblock %}
</div>
{% endblock %}
{% block block_center %}
{% set element = block.slots.getSlot('center') %}
<div class="col-md-4" data-cms-element-id="{{ element.id }}">
{% block block_center_center_inner %}
{% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
{% endblock %}
</div>
{% endblock %}
{% block block_right %}
{% set element = block.slots.getSlot('right') %}
<div class="col-md-4" data-cms-element-id="{{ element.id }}">
{% block block_right_inner %}
{% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
{% endblock %}
</div>
{% endblock %}
{% endblock %}
element
cms-element-custorm-image.html.twig
{% block sw_cms_el_custom_image %}
{# {{ dump(element.data.media.media.url) }}#}
<div class="cms-element-custom-image">
<a href="{{ element.config.url.value }}">
<img src="{{ element.data.media.media.url }}"/>
</a>
</div>
{% endblock %}
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)