diff --git a/changelog/_unreleased/2021-01-15-add-layout-config-for-layout-tab-of-product-detail.md b/changelog/_unreleased/2021-01-15-add-layout-config-for-layout-tab-of-product-detail.md
new file mode 100644
index 00000000000..e6048d564cb
--- /dev/null
+++ b/changelog/_unreleased/2021-01-15-add-layout-config-for-layout-tab-of-product-detail.md
@@ -0,0 +1,42 @@
+---
+title: Add layout config for layout tab of product detail
+issue: NEXT-12986
+flag: FEATURE_NEXT_10078
+---
+# Core
+* Added `slot_config` fields to `product_translation` table
+___
+# Administration
+* Added computed props `isProductPage` in `module/sw-cms/elements/buy-box/config/index.js`
+* Added block `sw_cms_element_buy_box_config_content_warning` in `module/sw-cms/elements/buy-box/config/sw-cms-el-config-buy-box.html.twig` to show loaded automatically data information
+* Deprecated block `sw_cms_toolbar_slot_language_swtich` in `module/sw-cms/component/sw-cms-toolbar/sw-cms-toolbar.html.twig`
+* Added block `sw_cms_toolbar_slot_language_switch` in `module/sw-cms/component/sw-cms-toolbar/sw-cms-toolbar.html.twig`
+* Added computed props `assetFilter` in `module/sw-cms/elements/image-gallery/component/index.js`
+* Changed method `getPlaceholderItems` in `module/sw-cms/elements/image-gallery/component/index.js` to fix image broken
+* Changed computed props `element.config.sliderItems.value` in `module/sw-cms/elements/image-gallery/component/index.js` to reset sliderItems config value of image gallery element
+* Changed method `createdComponent` in `module/sw-cms/elements/image-gallery/component/index.js` to initialize config for image gallery element if layout is product detail page
+* Added computed props `isProductPage` in `module/sw-cms/elements/image-gallery/config/index.js`
+* Added method `initConfig` in `module/sw-cms/elements/image-gallery/config/index.js` to initialize config for image gallery element if layout is product detail page
+* Changed method `createdComponent` in `module/sw-cms/elements/image-gallery/config/index.js` to initialize config for image gallery element if layout is product detail page
+* Changed handler of watcher `sliderItemsConfigValue` in `module/sw-cms/elements/image-gallery/config/index.js` to fix update `mediaItems` and `element.config.sliderItems.value` when `element.data.sliderItems` is empty
+* Changed method `updateMediaDataValue` in `module/sw-cms/elements/image-gallery/config/index.js` to fix update `element.data.sliderItems`
+* Added computed props `isProductPage` in `module/sw-cms/elements/manufacturer-logo/config/index.js`
+* Changed method `createdComponent` in `module/sw-cms/elements/manufacturer-logo/config/index.js` to initialize config for image gallery element if layout is product detail page
+* Added computed props `isProductPage` in `module/sw-cms/elements/product-description-reviews/config/index.js`
+* Changed method `createdComponent` in `module/sw-cms/elements/product-description-reviews/config/index.js` to initialize config for image gallery element if layout is product detail page
+* Added block `sw_cms_element_product_description_reviews_warning` in `module/sw-cms/elements/product-description-reviews/config/sw-cms-el-config-product-description-reviews.html.twig` to show loaded automatically data information
+* Added computed props `isProductPage` in `module/sw-cms/elements/product-name/config/index.js`
+* Changed method `createdComponent` in `module/sw-cms/elements/product-name/config/index.js` to initialize config for image gallery element if layout is product detail page
+* Changed block `sw_cms_detail_stage_form_view` in `module/sw-cms/page/sw-cms-detail/sw-cms-detail.html.twig` to fix showing incorrect config data when reloading layout with form view
+* Changed template in `module/sw-product/component/sw-product-layout-assignment/sw-product-layout-assignment.html.twig` to disabled layout assignment if user only has `product.viewer` permission
+* Changed method `onSave` in `module/sw-product/page/sw-product-detail/index.js` to save layout config of a product
+* Added method `getCmsPageOverrides` in `module/sw-product/page/sw-product-detail/index.js` to get layout config
+* Added computed props `cmsPageId` in `module/sw-product/view/sw-product-detail-layout/index.js`
+* Added computed props `cmsPageCriteria` in `module/sw-product/view/sw-product-detail-layout/index.js`
+* Added computed props `showCmsForm` in `module/sw-product/view/sw-product-detail-layout/index.js`
+* Added method `onOpenLayoutModal` in `module/sw-product/view/sw-product-detail-layout/index.js` to prevent open layout modal if user does not have `product.editor` permission
+* Added method `handleGetCmsPage` in `module/sw-product/view/sw-product-detail-layout/index.js` to get cms page
+* Added method `updateCmsPageDataMapping` in `module/sw-product/view/sw-product-detail-layout/index.js` to update config data of cms page
+* Added watcher `cmsPageId` in `module/sw-product/view/sw-product-detail-layout/index.js` to update cms page
+* Changed method `onSelectLayout` in `module/sw-product/view/sw-product-detail-layout/index.js`
+* Added block `sw_product_detail_layout_cms_config` in `module/sw-product/view/sw-product-detail-layout/sw-product-detail-layout.html.twig` to show layout config
diff --git a/src/Administration/Resources/app/administration/src/module/sw-cms/component/sw-cms-toolbar/sw-cms-toolbar.html.twig b/src/Administration/Resources/app/administration/src/module/sw-cms/component/sw-cms-toolbar/sw-cms-toolbar.html.twig
index 47f6bb7d23e..3df3babb666 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-cms/component/sw-cms-toolbar/sw-cms-toolbar.html.twig
+++ b/src/Administration/Resources/app/administration/src/module/sw-cms/component/sw-cms-toolbar/sw-cms-toolbar.html.twig
@@ -19,7 +19,9 @@
{% block sw_cms_toolbar_language_switch %}
+ {% block sw_cms_element_buy_box_config_content_warning %}
+
+ {{ $tc('sw-cms.elements.buyBox.infoText.tooltipSettingDisabled') }}
+
+ {% endblock %}
{% block sw_cms_element_buy_box_config_product_select %}
{
this.setGalleryLimit();
});
@@ -98,10 +107,17 @@ Component.register('sw-cms-el-image-gallery', {
this.initElementConfig('image-gallery');
this.initElementData('image-gallery');
- if (this.isProductPage && !this.element.data.sliderItems) {
- this.element.config.sliderItems.source = 'mapped';
- this.element.config.sliderItems.value = 'product.media';
+ if (!this.isProductPage || Utils.get(this.element, 'translated.config')) {
+ return;
}
+
+ this.element.config.sliderItems.source = 'mapped';
+ this.element.config.sliderItems.value = 'product.media';
+ this.element.config.navigationDots.value = 'inside';
+ this.element.config.zoom.value = true;
+ this.element.config.fullScreen.value = true;
+ this.element.config.displayMode.value = 'contain';
+ this.element.config.minHeight.value = '430px';
},
mountedComponent() {
@@ -110,9 +126,9 @@ Component.register('sw-cms-el-image-gallery', {
getPlaceholderItems() {
return [
- { url: '/administration/static/img/cms/preview_mountain_large.jpg' },
- { url: '/administration/static/img/cms/preview_glasses_large.jpg' },
- { url: '/administration/static/img/cms/preview_plant_large.jpg' }
+ { url: this.assetFilter('administration/static/img/cms/preview_mountain_large.jpg') },
+ { url: this.assetFilter('administration/static/img/cms/preview_glasses_large.jpg') },
+ { url: this.assetFilter('administration/static/img/cms/preview_plant_large.jpg') }
];
},
diff --git a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/image-gallery/config/index.js b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/image-gallery/config/index.js
index 62c745c13d0..5d815b8b34f 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/image-gallery/config/index.js
+++ b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/image-gallery/config/index.js
@@ -51,6 +51,10 @@ Component.register('sw-cms-el-config-image-gallery', {
gridAutoRows() {
return `grid-auto-rows: ${this.columnWidth}`;
+ },
+
+ isProductPage() {
+ return Utils.get(this.cmsPageState, 'currentPage.type', '') === 'product_detail';
}
},
@@ -60,10 +64,15 @@ Component.register('sw-cms-el-config-image-gallery', {
},
sliderItemsConfigValue(value) {
+ if (!value) {
+ this.element.config.sliderItems.value = [];
+ return;
+ }
+
const isSourceMapped = Utils.get(this.element, 'config.sliderItems.source') === 'mapped';
const isSliderLengthValid = value && value.length === this.sliderItems.length;
- if (isSourceMapped || isSliderLengthValid) {
+ if (isSourceMapped || isSliderLengthValid || !this.sliderItems.length) {
return;
}
@@ -109,12 +118,28 @@ Component.register('sw-cms-el-config-image-gallery', {
return searchResult.get(mediaId);
});
}
+
+ this.initConfig();
},
mountedComponent() {
this.updateColumnWidth();
},
+ initConfig() {
+ if (!this.isProductPage || Utils.get(this.element, 'translated.config')) {
+ return;
+ }
+
+ this.element.config.sliderItems.source = 'mapped';
+ this.element.config.sliderItems.value = 'product.media';
+ this.element.config.navigationDots.value = 'inside';
+ this.element.config.zoom.value = true;
+ this.element.config.fullScreen.value = true;
+ this.element.config.displayMode.value = 'contain';
+ this.element.config.minHeight.value = '430px';
+ },
+
updateColumnWidth() {
if (!this.$refs.demoMediaGrid) {
return;
@@ -190,7 +215,12 @@ Component.register('sw-cms-el-config-image-gallery', {
}
});
});
- this.$set(this.element.data, 'sliderItems', sliderItems);
+
+ if (!this.element.data) {
+ this.$set(this.element, 'data', { sliderItems });
+ } else {
+ this.$set(this.element.data, 'sliderItems', sliderItems);
+ }
}
},
diff --git a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/manufacturer-logo/component/index.js b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/manufacturer-logo/component/index.js
index 61f1a2ac877..8bc1b5b15ae 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/manufacturer-logo/component/index.js
+++ b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/manufacturer-logo/component/index.js
@@ -27,7 +27,7 @@ Component.extend('sw-cms-el-manufacturer-logo', 'sw-cms-el-image', {
this.initElementConfig('manufacturer-logo');
this.initElementData('manufacturer-logo');
- if (this.isProductPage && !this.element.data.media) {
+ if (this.isProductPage && !Utils.get(this.element, 'data.media')) {
this.element.config.media.source = 'mapped';
this.element.config.media.value = 'product.manufacturer.media';
}
diff --git a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/manufacturer-logo/config/index.js b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/manufacturer-logo/config/index.js
index ed11948ffd5..69f1d234fa8 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/manufacturer-logo/config/index.js
+++ b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/manufacturer-logo/config/index.js
@@ -1,9 +1,20 @@
-const { Component } = Shopware;
+const { Component, Utils } = Shopware;
Component.extend('sw-cms-el-config-manufacturer-logo', 'sw-cms-el-config-image', {
+ computed: {
+ isProductPage() {
+ return Utils.get(this.cmsPageState, 'currentPage.type', '') === 'product_detail';
+ }
+ },
+
methods: {
createdComponent() {
this.initElementConfig('manufacturer-logo');
+
+ if (this.isProductPage && !Utils.get(this.element, 'data.media')) {
+ this.element.config.media.source = 'mapped';
+ this.element.config.media.value = 'product.manufacturer.media';
+ }
}
}
});
diff --git a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/product-description-reviews/config/index.js b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/product-description-reviews/config/index.js
index 68f15627e0c..c685e59729b 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/product-description-reviews/config/index.js
+++ b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/product-description-reviews/config/index.js
@@ -2,7 +2,7 @@ import Criteria from 'src/core/data-new/criteria.data';
import template from './sw-cms-el-config-product-description-reviews.html.twig';
import './sw-cms-el-config-product-description-reviews.scss';
-const { Component, Mixin } = Shopware;
+const { Component, Mixin, Utils } = Shopware;
Component.register('sw-cms-el-config-product-description-reviews', {
template,
@@ -37,6 +37,10 @@ Component.register('sw-cms-el-config-product-description-reviews', {
criteria.addAssociation('properties');
return criteria;
+ },
+
+ isProductPage() {
+ return Utils.get(this.cmsPageState, 'currentPage.type') === 'product_detail';
}
},
diff --git a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/product-description-reviews/config/sw-cms-el-config-product-description-reviews.html.twig b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/product-description-reviews/config/sw-cms-el-config-product-description-reviews.html.twig
index 3550dc125fd..94dc19fc020 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-cms/elements/product-description-reviews/config/sw-cms-el-config-product-description-reviews.html.twig
+++ b/src/Administration/Resources/app/administration/src/module/sw-cms/elements/product-description-reviews/config/sw-cms-el-config-product-description-reviews.html.twig
@@ -25,8 +25,17 @@
+ {% block sw_cms_element_product_description_reviews_warning %}
+
+ {{ $tc('sw-cms.elements.productDescriptionReviews.infoText.descriptionAndReviewsElement') }}
+
+ {% endblock %}
+
{% block sw_cms_element_product_description_reviews_config_product_select %}
{% endblock %}
-
+
{% endblock %}
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/acl/index.js b/src/Administration/Resources/app/administration/src/module/sw-product/acl/index.js
index 8a733d89297..d10c22c9b08 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-product/acl/index.js
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/acl/index.js
@@ -44,10 +44,10 @@ Shopware.Service('privileges')
'shipping_method:read',
'product_tag:read',
'product_feature_set:read',
- 'cms_page:read',
'user_config:read',
'user_config:create',
- 'user_config:update'
+ 'user_config:update',
+ Shopware.Service('privileges').getPrivileges('cms.viewer')
],
dependencies: []
},
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/index.js b/src/Administration/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/index.js
index 5d2e080d012..4a013dbc1e5 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/index.js
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/index.js
@@ -6,6 +6,8 @@ const { Component } = Shopware;
Component.register('sw-product-layout-assignment', {
template,
+ inject: ['acl'],
+
props: {
cmsPage: {
type: Object,
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/sw-product-layout-assignment.html.twig b/src/Administration/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/sw-product-layout-assignment.html.twig
index 34c4e313fe9..27f4040bdc7 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/sw-product-layout-assignment.html.twig
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/sw-product-layout-assignment.html.twig
@@ -3,7 +3,10 @@
{% block sw_product_layout_assignment_firgure %}
{% block sw_product_layout_assignment_firgure_content %}
-
+
+
{% endblock %}
{% endblock %}
@@ -32,6 +35,7 @@
{{ cmsPage
? $tc('sw-product.layoutAssignment.textChangeLayout')
@@ -44,6 +48,7 @@
{{ cmsPage
? $tc('sw-product.layoutAssignment.textEditInDesigner')
@@ -58,6 +63,7 @@
class="sw-product-layout-assignment__button"
square
size="small"
+ :disabled="!acl.can('product.editor')"
@click="onLayoutReset">
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/page/sw-product-detail/index.js b/src/Administration/Resources/app/administration/src/module/sw-product/page/sw-product-detail/index.js
index 0436aac7051..e9b42bad81c 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-product/page/sw-product-detail/index.js
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/page/sw-product-detail/index.js
@@ -4,9 +4,10 @@ import errorConfiguration from './error.cfg.json';
import './sw-product-detail.scss';
const { Component, Mixin } = Shopware;
-const { Criteria } = Shopware.Data;
-const { hasOwnProperty } = Shopware.Utils.object;
+const { Criteria, ChangesetGenerator } = Shopware.Data;
+const { hasOwnProperty, cloneDeep } = Shopware.Utils.object;
const { mapPageErrors, mapState, mapGetters } = Shopware.Component.getComponentHelper();
+const type = Shopware.Utils.types;
Component.register('sw-product-detail', {
template,
@@ -67,6 +68,10 @@ Component.register('sw-product-detail', {
...mapPageErrors(errorConfiguration),
+ ...mapState('cmsPageState', [
+ 'currentPage'
+ ]),
+
identifier() {
return this.productTitle;
},
@@ -462,6 +467,12 @@ Component.register('sw-product-detail', {
this.isSaveSuccessful = false;
+ const pageOverrides = this.getCmsPageOverrides();
+
+ if (type.isPlainObject(pageOverrides)) {
+ this.product.slotConfig = cloneDeep(pageOverrides);
+ }
+
return this.saveProduct().then(this.onSaveFinished);
},
@@ -698,6 +709,54 @@ Component.register('sw-product-detail', {
}
return true;
+ },
+
+ getCmsPageOverrides() {
+ if (this.currentPage === null) {
+ return null;
+ }
+
+ const changesetGenerator = new ChangesetGenerator();
+ const { changes } = changesetGenerator.generate(this.currentPage);
+
+ const slotOverrides = {};
+ if (changes === null || !type.isArray(changes.sections)) {
+ return slotOverrides;
+ }
+
+ changes.sections.forEach((section) => {
+ if (!type.isArray(section.blocks)) {
+ return;
+ }
+
+ section.blocks.forEach((block) => {
+ if (!type.isArray(block.slots)) {
+ return;
+ }
+
+ block.slots.forEach((slot) => {
+ if (!type.isPlainObject(slot.config)) {
+ return;
+ }
+
+ const slotConfig = {};
+
+ Object.keys(slot.config).forEach((key) => {
+ if (!slot.config[key].value) {
+ return;
+ }
+
+ slotConfig[key] = slot.config[key];
+ });
+
+ if (Object.keys(slotConfig).length > 0) {
+ slotOverrides[slot.id] = slotConfig;
+ }
+ });
+ });
+ });
+
+ return slotOverrides;
}
}
});
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/page/sw-product-detail/sw-product-detail.html.twig b/src/Administration/Resources/app/administration/src/module/sw-product/page/sw-product-detail/sw-product-detail.html.twig
index a8f7c0333d3..f6328a48bd9 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-product/page/sw-product-detail/sw-product-detail.html.twig
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/page/sw-product-detail/sw-product-detail.html.twig
@@ -171,7 +171,7 @@
{% block sw_product_detail_content_tabs_layout %}
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/snippet/de-DE.json b/src/Administration/Resources/app/administration/src/module/sw-product/snippet/de-DE.json
index 822651f94e4..f516788cc8e 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-product/snippet/de-DE.json
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/snippet/de-DE.json
@@ -359,7 +359,9 @@
},
"layout": {
"title": "Layout-Zuweisung",
- "subtitle": "Du kannst die Anzeige der Produkt-Detailseite mit eigenen Layouts anpassen. Wenn Du kein eigenes Layout zuweisen möchtest, wird das Standard-Layout für Produkt-Detailseiten verwendet."
+ "subtitle": "Du kannst die Anzeige der Produkt-Detailseite mit eigenen Layouts anpassen. Wenn Du kein eigenes Layout zuweisen möchtest, wird das Standard-Layout für Produkt-Detailseiten verwendet.",
+ "textNoConfig": "Das aktuell ausgewählte Layout hat keine bearbeitbaren Elemente.",
+ "textContentInfo": "Standard-Produktblöcke werden automatisch mit Produktdaten befüllt. Diese Inhalte werden daher hier nicht aufgelistet."
},
"crossselling": {
"cardTitleCrossSelling": "Cross Selling",
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/snippet/en-GB.json b/src/Administration/Resources/app/administration/src/module/sw-product/snippet/en-GB.json
index cdcd15a4d34..256fe338dc4 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-product/snippet/en-GB.json
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/snippet/en-GB.json
@@ -359,7 +359,9 @@
},
"layout": {
"title": "Layout assignment",
- "subtitle": "You may change product detail pages by assigning custom layouts. If no custom layout is assigned here, the default layout will be used."
+ "subtitle": "You may change product detail pages by assigning custom layouts. If no custom layout is assigned here, the default layout will be used.",
+ "textNoConfig": "The current selected layout does not have any editable elements",
+ "textContentInfo": "Default product blocks are filled with product data automatically. The actual contents are thus not listed here."
},
"crossselling": {
"cardTitleCrossSelling": "Cross Selling",
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/index.js b/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/index.js
index 1174bc0382b..b2db3021ae8 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/index.js
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/index.js
@@ -1,16 +1,20 @@
import template from './sw-product-detail-layout.html.twig';
+import './sw-product-detail-layout.scss';
-const { Component, State, Context } = Shopware;
+const { Component, State, Context, Utils } = Shopware;
+const { Criteria } = Shopware.Data;
const { mapState, mapGetters } = Component.getComponentHelper();
+const { cloneDeep, merge, get } = Utils.object;
Component.register('sw-product-detail-layout', {
template,
- inject: ['repositoryFactory', 'feature'],
+ inject: ['repositoryFactory', 'cmsService', 'feature', 'acl'],
data() {
return {
- showLayoutModal: false
+ showLayoutModal: false,
+ isConfigLoading: false
};
},
@@ -19,6 +23,14 @@ Component.register('sw-product-detail-layout', {
return this.repositoryFactory.create('cms_page');
},
+ cmsPageId() {
+ return get(this.product, 'cmsPageId', null);
+ },
+
+ showCmsForm() {
+ return (!this.isLoading || !this.isConfigLoading) && !this.currentPage.locked;
+ },
+
...mapState('swProductDetail', [
'product'
]),
@@ -29,17 +41,42 @@ Component.register('sw-product-detail-layout', {
...mapState('cmsPageState', [
'currentPage'
- ])
+ ]),
+
+ cmsPageCriteria() {
+ const criteria = new Criteria();
+ criteria.addAssociation('previewMedia');
+ criteria.addAssociation('sections');
+ criteria.getAssociation('sections').addSorting(Criteria.sort('position'));
+
+ criteria.addAssociation('sections.blocks');
+ criteria.getAssociation('sections.blocks')
+ .addSorting(Criteria.sort('position', 'ASC'))
+ .addAssociation('slots');
+
+ return criteria;
+ }
},
watch: {
- product({ cmsPageId }) {
- this.onSelectLayout(cmsPageId);
+ cmsPageId() {
+ this.product.slotConfig = null;
+ State.commit('swProductDetail/setProduct', this.product);
+ State.dispatch('cmsPageState/resetCmsPageState');
+ this.handleGetCmsPage();
}
},
+ mounted() {
+ this.handleGetCmsPage();
+ },
+
methods: {
onOpenLayoutModal() {
+ if (!this.acl.can('product.editor')) {
+ return;
+ }
+
this.showLayoutModal = true;
},
@@ -56,16 +93,51 @@ Component.register('sw-product-detail-layout', {
},
onSelectLayout(cmsPageId) {
- if (this.product) {
- this.product.cmsPageId = cmsPageId;
- State.commit('swProductDetail/setProduct', this.product);
+ if (!this.product) {
+ return;
+ }
+
+ this.product.cmsPageId = cmsPageId;
+ },
+
+ handleGetCmsPage() {
+ if (!this.cmsPageId) {
+ return;
}
- this.cmsPageRepository.get(cmsPageId, Context.api).then((cmsPage) => {
+ this.isConfigLoading = true;
+
+ this.cmsPageRepository.get(this.cmsPageId, Context.api, this.cmsPageCriteria).then((cmsPage) => {
+ if (this.product.slotConfig && cmsPage) {
+ cmsPage.sections.forEach((section) => {
+ section.blocks.forEach((block) => {
+ block.slots.forEach((slot) => {
+ if (!this.product.slotConfig[slot.id]) {
+ return;
+ }
+
+ slot.config = slot.config || {};
+ merge(slot.config, cloneDeep(this.product.slotConfig[slot.id]));
+ });
+ });
+ });
+ }
+
State.commit('cmsPageState/setCurrentPage', cmsPage);
+ this.updateCmsPageDataMapping();
+ this.isConfigLoading = false;
});
},
+ updateCmsPageDataMapping() {
+ Shopware.State.commit('cmsPageState/setCurrentMappingEntity', 'product');
+ Shopware.State.commit(
+ 'cmsPageState/setCurrentMappingTypes',
+ this.cmsService.getEntityMappingTypes('product')
+ );
+ Shopware.State.commit('cmsPageState/setCurrentDemoEntity', this.product);
+ },
+
onResetLayout() {
this.onSelectLayout(null);
}
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/sw-product-detail-layout.html.twig b/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/sw-product-detail-layout.html.twig
index 26ceb4417b8..9ec492a8ea7 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/sw-product-detail-layout.html.twig
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/sw-product-detail-layout.html.twig
@@ -1,28 +1,52 @@
{% block sw_product_detail_layout %}
-
+
+ {% block sw_product_detail_layout_assignment %}
+
- {% block sw_product_detail_layout_content %}
-
-
- {% endblock %}
+ {% block sw_product_detail_layout_content %}
+
+
+ {% endblock %}
- {% block sw_product_detail_layout_modal %}
-
-
+ {% block sw_product_detail_layout_modal %}
+
+
+ {% endblock %}
+
{% endblock %}
-
+ {% block sw_product_detail_layout_cms_config %}
+
+ {% block sw_product_detail_layout_cms_config_form %}
+
+
+
+
+ {{ $tc('sw-product.layout.textNoConfig') }}
+
+ {% endblock %}
+
+ {% block sw_product_detail_layout_cms_content_info %}
+
+
+ {{ $tc('sw-product.layout.textContentInfo') }}
+
+
+ {% endblock %}
+
+ {% endblock %}
+
{% endblock %}
diff --git a/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/sw-product-detail-layout.scss b/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/sw-product-detail-layout.scss
new file mode 100644
index 00000000000..eade8e39d1b
--- /dev/null
+++ b/src/Administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-layout/sw-product-detail-layout.scss
@@ -0,0 +1,13 @@
+@import "~scss/variables";
+
+.sw-product-detail-layout {
+ &__no-config.sw-card {
+ text-align: center;
+ font-size: $font-size-small;
+ }
+
+ &__content-info {
+ color: $color-gray-500;
+ font-size: $font-size-small;
+ }
+}
diff --git a/src/Administration/Resources/app/administration/static/img/cms/preview_product_detail_sidebar.png b/src/Administration/Resources/app/administration/static/img/cms/preview_product_detail_sidebar.png
index 337c5d1dea9..827c9889640 100644
Binary files a/src/Administration/Resources/app/administration/static/img/cms/preview_product_detail_sidebar.png and b/src/Administration/Resources/app/administration/static/img/cms/preview_product_detail_sidebar.png differ
diff --git a/src/Administration/Resources/app/administration/test/module/sw-cms/elements/buy-box/config/sw-cms-el-config-buy-box.spec.js b/src/Administration/Resources/app/administration/test/module/sw-cms/elements/buy-box/config/sw-cms-el-config-buy-box.spec.js
new file mode 100644
index 00000000000..c8eb63e37fc
--- /dev/null
+++ b/src/Administration/Resources/app/administration/test/module/sw-cms/elements/buy-box/config/sw-cms-el-config-buy-box.spec.js
@@ -0,0 +1,105 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import 'src/module/sw-cms/mixin/sw-cms-element.mixin';
+import 'src/module/sw-cms/elements/buy-box/config';
+
+const productMock = {
+ name: 'Lorem Ipsum dolor',
+ productNumber: '1234',
+ minPurchase: 1,
+ deliveryTime: {
+ name: '1-3 days'
+ },
+ price: [
+ { gross: 100 }
+ ]
+};
+
+function createWrapper() {
+ const localVue = createLocalVue();
+
+ return shallowMount(Shopware.Component.build('sw-cms-el-config-buy-box'), {
+ localVue,
+ sync: false,
+ propsData: {
+ element: {
+ data: {},
+ config: {}
+ },
+ defaultConfig: {
+ product: {
+ value: null
+ },
+ alignment: {
+ value: null
+ }
+ }
+ },
+ data() {
+ return {
+ cmsPageState: {
+ currentPage: {
+ type: 'ladingpage'
+ }
+ }
+ };
+ },
+ stubs: {
+ 'sw-tabs': {
+ template: '
'
+ },
+ 'sw-tabs-item': true,
+ 'sw-entity-single-select': true,
+ 'sw-alert': true
+ },
+ mocks: {
+ $tc: (value) => value
+ },
+ provide: {
+ cmsService: {
+ getCmsBlockRegistry: () => {
+ return {};
+ },
+ getCmsElementRegistry: () => {
+ return { 'buy-box': {} };
+ }
+ },
+ repositoryFactory: {
+ create: () => {
+ return {
+ get: () => Promise.resolve(productMock),
+ search: () => Promise.resolve(productMock)
+ };
+ }
+ }
+ }
+ });
+}
+
+describe('module/sw-cms/elements/buy-box/config', () => {
+ it('should show product selector if page type is not product detail', async () => {
+ const wrapper = createWrapper();
+ const productSelector = wrapper.find('sw-entity-single-select-stub');
+ const alert = wrapper.find('sw-alert-stub');
+
+ expect(productSelector.exists()).toBeTruthy();
+ expect(alert.exists()).toBeFalsy();
+ });
+
+ it('should show alert information if page type is product detail', async () => {
+ const wrapper = createWrapper();
+
+ await wrapper.setData({
+ cmsPageState: {
+ currentPage: {
+ type: 'product_detail'
+ }
+ }
+ });
+
+ const productSelector = wrapper.find('sw-entity-single-select-stub');
+ const alert = wrapper.find('sw-alert-stub');
+
+ expect(productSelector.exists()).toBeFalsy();
+ expect(alert.exists()).toBeTruthy();
+ });
+});
diff --git a/src/Administration/Resources/app/administration/test/module/sw-cms/elements/image-gallery/component/sw-cms-el-image-gallery.spec.js b/src/Administration/Resources/app/administration/test/module/sw-cms/elements/image-gallery/component/sw-cms-el-image-gallery.spec.js
index f3a86c4c73d..579eadc8f6c 100644
--- a/src/Administration/Resources/app/administration/test/module/sw-cms/elements/image-gallery/component/sw-cms-el-image-gallery.spec.js
+++ b/src/Administration/Resources/app/administration/test/module/sw-cms/elements/image-gallery/component/sw-cms-el-image-gallery.spec.js
@@ -60,12 +60,7 @@ function createWrapper(propsOverride, dataOverride) {
},
propsData: {
element: {
- config: {
- sliderItems: {
- source: 'static',
- value: []
- }
- },
+ config: {},
data: {}
},
defaultConfig: {
@@ -80,6 +75,30 @@ function createWrapper(propsOverride, dataOverride) {
verticalAlign: {
source: 'static',
value: null
+ },
+ displayMode: {
+ source: 'static',
+ value: 'standard'
+ },
+ minHeight: {
+ source: 'static',
+ value: '340px'
+ },
+ zoom: {
+ source: 'static',
+ value: false
+ },
+ fullScreen: {
+ source: 'static',
+ value: false
+ },
+ navigationArrows: {
+ source: 'static',
+ value: 'inside'
+ },
+ navigationDots: {
+ source: 'static'
+
}
},
...propsOverride
@@ -120,8 +139,13 @@ describe('src/module/sw-cms/elements/image-gallery/component', () => {
value: sliderItemsConfigMock
}
},
- data: {
- sliderItems: sliderItemsDataMock
+ translated: {
+ config: {
+ sliderItems: {
+ source: 'static',
+ value: sliderItemsConfigMock
+ }
+ }
}
}
}, {
diff --git a/src/Administration/Resources/app/administration/test/module/sw-cms/elements/product-description-reviews/config/sw-cms-el-config-product-description-reviews.spec.js b/src/Administration/Resources/app/administration/test/module/sw-cms/elements/product-description-reviews/config/sw-cms-el-config-product-description-reviews.spec.js
new file mode 100644
index 00000000000..ac86aed55ba
--- /dev/null
+++ b/src/Administration/Resources/app/administration/test/module/sw-cms/elements/product-description-reviews/config/sw-cms-el-config-product-description-reviews.spec.js
@@ -0,0 +1,103 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import 'src/module/sw-cms/mixin/sw-cms-element.mixin';
+import 'src/module/sw-cms/elements/product-description-reviews/config';
+
+const productMock = {
+ name: 'Awesome Product',
+ description: 'This product is awesome'
+};
+
+function createWrapper() {
+ const localVue = createLocalVue();
+ return shallowMount(Shopware.Component.build('sw-cms-el-config-product-description-reviews'), {
+ localVue,
+ sync: false,
+ mocks: {
+ $tc: v => v
+ },
+ stubs: {
+ 'sw-tabs': {
+ template: '
'
+ },
+ 'sw-container': {
+ template: '
'
+ },
+ 'sw-tabs-item': true,
+ 'sw-entity-single-select': true,
+ 'sw-alert': true
+ },
+ provide: {
+ feature: {
+ isActive: () => true
+ },
+ cmsService: {
+ getCmsBlockRegistry: () => {
+ return {};
+ },
+ getCmsElementRegistry: () => {
+ return { 'product-description-reviews': {} };
+ }
+ },
+ repositoryFactory: {
+ create: () => {
+ return {
+ get: () => Promise.resolve(productMock),
+ search: () => Promise.resolve(productMock)
+ };
+ }
+ }
+ },
+ propsData: {
+ element: {
+ config: {},
+ data: {}
+ },
+ defaultConfig: {
+ product: {
+ value: null
+ },
+ alignment: {
+ value: null
+ }
+ }
+ },
+ data() {
+ return {
+ cmsPageState: {
+ currentPage: {
+ type: 'ladingpage'
+ }
+ }
+ };
+ }
+ });
+}
+
+describe('src/module/sw-cms/elements/product-description-reviews/config', () => {
+ it('should show product selector if page type is not product detail', async () => {
+ const wrapper = createWrapper();
+ const productSelector = wrapper.find('sw-entity-single-select-stub');
+ const alert = wrapper.find('sw-alert-stub');
+
+ expect(productSelector.exists()).toBeTruthy();
+ expect(alert.exists()).toBeFalsy();
+ });
+
+ it('should show alert information if page type is product detail', async () => {
+ const wrapper = createWrapper();
+
+ await wrapper.setData({
+ cmsPageState: {
+ currentPage: {
+ type: 'product_detail'
+ }
+ }
+ });
+
+ const productSelector = wrapper.find('sw-entity-single-select-stub');
+ const alert = wrapper.find('sw-alert-stub');
+
+ expect(productSelector.exists()).toBeFalsy();
+ expect(alert.exists()).toBeTruthy();
+ });
+});
diff --git a/src/Administration/Resources/app/administration/test/module/sw-product/component/sw-product-layout-assignment.spec.js b/src/Administration/Resources/app/administration/test/module/sw-product/component/sw-product-layout-assignment.spec.js
index 202e441b4e8..b959b9579b2 100644
--- a/src/Administration/Resources/app/administration/test/module/sw-product/component/sw-product-layout-assignment.spec.js
+++ b/src/Administration/Resources/app/administration/test/module/sw-product/component/sw-product-layout-assignment.spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import 'src/module/sw-product/component/sw-product-layout-assignment';
-function createWrapper() {
+function createWrapper(privileges = []) {
return shallowMount(Shopware.Component.build('sw-product-layout-assignment'), {
mocks: {
$t: key => key,
@@ -10,6 +10,15 @@ function createWrapper() {
stubs: {
'sw-cms-list-item': true,
'sw-button': true
+ },
+ provide: {
+ acl: {
+ can: (identifier) => {
+ if (!identifier) { return true; }
+
+ return privileges.includes(identifier);
+ }
+ }
}
});
}
@@ -45,4 +54,30 @@ describe('module/sw-product/component/sw-product-layout-assignment', () => {
const pageChangeEvents = wrapper.emitted()['button-delete-click'];
expect(pageChangeEvents.length).toBe(1);
});
+
+ it('should not be able to edit layout assignment', () => {
+ wrapper = createWrapper(['product.viewer']);
+
+ const cmsItem = wrapper.find('sw-cms-list-item-stub');
+ const buttons = wrapper.findAll('sw-button-stub');
+
+ expect(cmsItem.attributes('disabled')).toBeTruthy();
+
+ buttons.wrappers.forEach(button => {
+ expect(button.attributes('disabled')).toBeTruthy();
+ });
+ });
+
+ it('should be able to edit layout assignment', () => {
+ wrapper = createWrapper(['product.editor']);
+
+ const cmsItem = wrapper.find('sw-cms-list-item-stub');
+ const buttons = wrapper.findAll('sw-button-stub');
+
+ expect(cmsItem.attributes('disabled')).toBeFalsy();
+
+ buttons.wrappers.forEach(button => {
+ expect(button.attributes('disabled')).toBeFalsy();
+ });
+ });
});
diff --git a/src/Administration/Resources/app/administration/test/module/sw-product/view/sw-product-detail-layout.spec.js b/src/Administration/Resources/app/administration/test/module/sw-product/view/sw-product-detail-layout.spec.js
index fc91aa88145..718ce88c656 100644
--- a/src/Administration/Resources/app/administration/test/module/sw-product/view/sw-product-detail-layout.spec.js
+++ b/src/Administration/Resources/app/administration/test/module/sw-product/view/sw-product-detail-layout.spec.js
@@ -1,10 +1,10 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { createLocalVue, shallowMount, enableAutoDestroy } from '@vue/test-utils';
import Vuex from 'vuex';
import 'src/module/sw-product/view/sw-product-detail-layout';
const { Component, State } = Shopware;
-function createWrapper() {
+function createWrapper(privileges = []) {
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -23,22 +23,52 @@ function createWrapper() {
if (!id) {
return Promise.resolve(null);
}
- return Promise.resolve({ id });
+ return Promise.resolve({
+ id,
+ sections: [{
+ blocks: [{
+ slots: [{
+ id: 'slot1',
+ config: {
+ content: {
+ value: 'product.name',
+ source: 'mapped'
+ }
+ }
+ }]
+ }]
+ }]
+ });
}
})
},
feature: {
isActive: () => true
+ },
+ cmsService: {
+ getEntityMappingTypes: () => {}
+ },
+ acl: {
+ can: (identifier) => {
+ if (!identifier) { return true; }
+
+ return privileges.includes(identifier);
+ }
}
},
stubs: {
- 'sw-card': true,
+ 'sw-card': {
+ template: '
'
+ },
'sw-product-layout-assignment': true,
- 'sw-cms-layout-modal': true
+ 'sw-cms-layout-modal': true,
+ 'sw-cms-page-form': true
}
});
}
+enableAutoDestroy(afterEach);
+
describe('src/module/sw-product/view/sw-product-detail-layout', () => {
beforeAll(() => {
State.registerModule('swProductDetail', {
@@ -63,6 +93,43 @@ describe('src/module/sw-product/view/sw-product-detail-layout', () => {
mutations: {
setCurrentPage(state, currentPage) {
state.currentPage = currentPage;
+ },
+
+ removeCurrentPage(state) {
+ state.currentPage = null;
+ },
+
+ setCurrentMappingEntity(state, entity) {
+ state.currentMappingEntity = entity;
+ },
+
+ removeCurrentMappingEntity(state) {
+ state.currentMappingEntity = null;
+ },
+
+ setCurrentMappingTypes(state, types) {
+ state.currentMappingTypes = types;
+ },
+
+ removeCurrentMappingTypes(state) {
+ state.currentMappingTypes = {};
+ },
+
+ setCurrentDemoEntity(state, entity) {
+ state.currentDemoEntity = entity;
+ },
+
+ removeCurrentDemoEntity(state) {
+ state.currentDemoEntity = null;
+ }
+ },
+
+ actions: {
+ resetCmsPageState({ commit }) {
+ commit('removeCurrentPage');
+ commit('removeCurrentMappingEntity');
+ commit('removeCurrentMappingTypes');
+ commit('removeCurrentDemoEntity');
}
}
});
@@ -118,17 +185,87 @@ describe('src/module/sw-product/view/sw-product-detail-layout', () => {
it('should be able to select a product page layout', async () => {
const wrapper = createWrapper();
+ wrapper.vm.$store.commit('swProductDetail/setProduct', { id: '1' });
- await wrapper.vm.onSelectLayout('cmsPageId');
+ wrapper.vm.onSelectLayout('cmsPageId');
+ await wrapper.vm.$nextTick();
- expect(wrapper.vm.currentPage).toEqual({ id: 'cmsPageId' });
+ expect(wrapper.vm.product.cmsPageId).toEqual('cmsPageId');
+ expect(wrapper.vm.currentPage.id).toEqual('cmsPageId');
});
it('should be able to reset a product page layout', async () => {
const wrapper = createWrapper();
-
await wrapper.vm.onResetLayout();
- expect(wrapper.vm.currentPage).toEqual(null);
+ expect(wrapper.vm.product.cmsPageId).toEqual(null);
+ });
+
+ it('should be able to overwrite product config to selected layout config', async () => {
+ Shopware.State.commit('swProductDetail/setProduct', {
+ id: '1',
+ cmsPageId: 'cmsPageId',
+ slotConfig: {
+ slot1: {
+ content: {
+ value: 'Hello World',
+ source: 'static'
+ }
+ }
+ }
+ });
+
+ const wrapper = createWrapper();
+ await wrapper.vm.handleGetCmsPage();
+
+ expect(wrapper.vm.currentPage.sections[0].blocks[0].slots[0].config).toEqual({
+ content: {
+ value: 'Hello World',
+ source: 'static'
+ }
+ });
+ });
+
+ it('onOpenLayoutModal: should be able to open layout assignment', () => {
+ const wrapper = createWrapper(['product.editor']);
+ wrapper.vm.onOpenLayoutModal();
+
+ expect(wrapper.vm.showLayoutModal).toBeTruthy();
+ });
+
+ it('onOpenLayoutModal: should not be able to open layout assignment', () => {
+ const wrapper = createWrapper(['product.viewer']);
+ wrapper.vm.onOpenLayoutModal();
+
+ expect(wrapper.vm.showLayoutModal).toBeFalsy();
+ });
+
+ it('should not be able to view layout config', () => {
+ const wrapper = createWrapper(['product.viewer']);
+ const cmsForm = wrapper.find('sw-cms-page-form-stub');
+ const infoNoConfig = wrapper.find('.sw-product-detail-layout__no-config');
+
+ expect(cmsForm.exists()).toBeFalsy();
+ expect(infoNoConfig.exists()).toBeFalsy();
+ });
+
+ it('should be able to view layout config', () => {
+ const wrapper = createWrapper(['product.editor']);
+ const cmsForm = wrapper.find('sw-cms-page-form-stub');
+ const infoNoConfig = wrapper.find('.sw-product-detail-layout__no-config');
+
+ expect(cmsForm.exists()).toBeTruthy();
+ expect(infoNoConfig.exists()).toBeFalsy();
+ });
+
+ it('should not be able to view layout config if cms page is locked', () => {
+ Shopware.State.commit('cmsPageState/setCurrentPage', { id: 'id', locked: true });
+
+ const wrapper = createWrapper(['product.editor']);
+ const cmsForm = wrapper.find('sw-cms-page-form-stub');
+ const infoNoConfig = wrapper.find('.sw-product-detail-layout__no-config');
+
+ expect(cmsForm.exists()).toBeFalsy();
+ expect(infoNoConfig.exists()).toBeTruthy();
});
});
diff --git a/src/Core/Content/Product/Aggregate/ProductTranslation/ProductTranslationDefinition.php b/src/Core/Content/Product/Aggregate/ProductTranslation/ProductTranslationDefinition.php
index f91ef97d1dc..9600b3516c9 100644
--- a/src/Core/Content/Product/Aggregate/ProductTranslation/ProductTranslationDefinition.php
+++ b/src/Core/Content/Product/Aggregate/ProductTranslation/ProductTranslationDefinition.php
@@ -7,10 +7,12 @@
use Shopware\Core\Framework\DataAbstractionLayer\Field\CustomFields;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\AllowHtml;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
+use Shopware\Core\Framework\DataAbstractionLayer\Field\JsonField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ListField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\LongTextField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
+use Shopware\Core\Framework\Feature;
class ProductTranslationDefinition extends EntityTranslationDefinition
{
@@ -48,7 +50,7 @@ protected function getParentDefinitionClass(): string
protected function defineFields(): FieldCollection
{
- return new FieldCollection([
+ $collection = new FieldCollection([
new StringField('meta_description', 'metaDescription'),
(new StringField('name', 'name'))->addFlags(new Required()),
new LongTextField('keywords', 'keywords'),
@@ -60,5 +62,13 @@ protected function defineFields(): FieldCollection
new CustomFields(),
]);
+
+ if (Feature::isActive('FEATURE_NEXT_10078')) {
+ $collection->add(
+ new JsonField('slot_config', 'slotConfig')
+ );
+ }
+
+ return $collection;
}
}
diff --git a/src/Core/Content/Product/Aggregate/ProductTranslation/ProductTranslationEntity.php b/src/Core/Content/Product/Aggregate/ProductTranslation/ProductTranslationEntity.php
index 45a5627badf..6c9d2946bd1 100644
--- a/src/Core/Content/Product/Aggregate/ProductTranslation/ProductTranslationEntity.php
+++ b/src/Core/Content/Product/Aggregate/ProductTranslation/ProductTranslationEntity.php
@@ -57,6 +57,13 @@ class ProductTranslationEntity extends TranslationEntity
*/
protected $customFields;
+ /**
+ * @internal (flag:FEATURE_NEXT_10078)
+ *
+ * @var array|null
+ */
+ protected $slotConfig;
+
/**
* @var string[]|null
*/
@@ -152,6 +159,22 @@ public function setCustomFields(?array $customFields): void
$this->customFields = $customFields;
}
+ /**
+ * @internal (flag:FEATURE_NEXT_10078)
+ */
+ public function getSlotConfig(): ?array
+ {
+ return $this->slotConfig;
+ }
+
+ /**
+ * @internal (flag:FEATURE_NEXT_10078)
+ */
+ public function setSlotConfig(array $slotConfig): void
+ {
+ $this->slotConfig = $slotConfig;
+ }
+
public function getMetaDescription(): ?string
{
return $this->metaDescription;
diff --git a/src/Core/Content/Product/ProductDefinition.php b/src/Core/Content/Product/ProductDefinition.php
index e3a194e6b72..b0f65690aef 100644
--- a/src/Core/Content/Product/ProductDefinition.php
+++ b/src/Core/Content/Product/ProductDefinition.php
@@ -291,6 +291,10 @@ protected function defineFields(): FieldCollection
$collection->add(
(new ManyToOneAssociationField('cmsPage', 'cms_page_id', CmsPageDefinition::class, 'id', false))->addFlags(new Inherited())
);
+ $collection->add(
+ (new TranslatedField('slotConfig'))
+ ->addFlags(new Inherited())
+ );
}
$collection->add(
diff --git a/src/Core/Content/Product/ProductEntity.php b/src/Core/Content/Product/ProductEntity.php
index 9d5f761d56d..7f4bf432de4 100644
--- a/src/Core/Content/Product/ProductEntity.php
+++ b/src/Core/Content/Product/ProductEntity.php
@@ -341,6 +341,13 @@ class ProductEntity extends Entity
*/
protected $cmsPage;
+ /**
+ * @internal (flag:FEATURE_NEXT_10078)
+ *
+ * @var array|null
+ */
+ protected $slotConfig;
+
/**
* @var ProductSearchKeywordCollection|null
*/
@@ -990,6 +997,22 @@ public function setCmsPageId(string $cmsPageId): void
$this->cmsPageId = $cmsPageId;
}
+ /**
+ * @internal (flag:FEATURE_NEXT_10078)
+ */
+ public function getSlotConfig(): ?array
+ {
+ return $this->slotConfig;
+ }
+
+ /**
+ * @internal (flag:FEATURE_NEXT_10078)
+ */
+ public function setSlotConfig(array $slotConfig): void
+ {
+ $this->slotConfig = $slotConfig;
+ }
+
public function getParent(): ?ProductEntity
{
return $this->parent;
diff --git a/src/Core/Migration/Migration1610337444AddSlotConfigToProductTranslationTable.php b/src/Core/Migration/Migration1610337444AddSlotConfigToProductTranslationTable.php
new file mode 100644
index 00000000000..226eb841611
--- /dev/null
+++ b/src/Core/Migration/Migration1610337444AddSlotConfigToProductTranslationTable.php
@@ -0,0 +1,28 @@
+executeUpdate($sql);
+ }
+
+ public function updateDestructive(Connection $connection): void
+ {
+ }
+}