feat(back-end front-end): shop feature
This commit is contained in:
@@ -27,7 +27,15 @@ public class AdminShopProductDto {
|
||||
private String descriptionDe;
|
||||
private String descriptionFr;
|
||||
private String seoTitle;
|
||||
private String seoTitleIt;
|
||||
private String seoTitleEn;
|
||||
private String seoTitleDe;
|
||||
private String seoTitleFr;
|
||||
private String seoDescription;
|
||||
private String seoDescriptionIt;
|
||||
private String seoDescriptionEn;
|
||||
private String seoDescriptionDe;
|
||||
private String seoDescriptionFr;
|
||||
private String ogTitle;
|
||||
private String ogDescription;
|
||||
private Boolean indexable;
|
||||
@@ -215,6 +223,38 @@ public class AdminShopProductDto {
|
||||
this.seoTitle = seoTitle;
|
||||
}
|
||||
|
||||
public String getSeoTitleIt() {
|
||||
return seoTitleIt;
|
||||
}
|
||||
|
||||
public void setSeoTitleIt(String seoTitleIt) {
|
||||
this.seoTitleIt = seoTitleIt;
|
||||
}
|
||||
|
||||
public String getSeoTitleEn() {
|
||||
return seoTitleEn;
|
||||
}
|
||||
|
||||
public void setSeoTitleEn(String seoTitleEn) {
|
||||
this.seoTitleEn = seoTitleEn;
|
||||
}
|
||||
|
||||
public String getSeoTitleDe() {
|
||||
return seoTitleDe;
|
||||
}
|
||||
|
||||
public void setSeoTitleDe(String seoTitleDe) {
|
||||
this.seoTitleDe = seoTitleDe;
|
||||
}
|
||||
|
||||
public String getSeoTitleFr() {
|
||||
return seoTitleFr;
|
||||
}
|
||||
|
||||
public void setSeoTitleFr(String seoTitleFr) {
|
||||
this.seoTitleFr = seoTitleFr;
|
||||
}
|
||||
|
||||
public String getSeoDescription() {
|
||||
return seoDescription;
|
||||
}
|
||||
@@ -223,6 +263,38 @@ public class AdminShopProductDto {
|
||||
this.seoDescription = seoDescription;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionIt() {
|
||||
return seoDescriptionIt;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionIt(String seoDescriptionIt) {
|
||||
this.seoDescriptionIt = seoDescriptionIt;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionEn() {
|
||||
return seoDescriptionEn;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionEn(String seoDescriptionEn) {
|
||||
this.seoDescriptionEn = seoDescriptionEn;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionDe() {
|
||||
return seoDescriptionDe;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionDe(String seoDescriptionDe) {
|
||||
this.seoDescriptionDe = seoDescriptionDe;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionFr() {
|
||||
return seoDescriptionFr;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionFr(String seoDescriptionFr) {
|
||||
this.seoDescriptionFr = seoDescriptionFr;
|
||||
}
|
||||
|
||||
public String getOgTitle() {
|
||||
return ogTitle;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,15 @@ public class AdminUpsertShopProductRequest {
|
||||
private String descriptionDe;
|
||||
private String descriptionFr;
|
||||
private String seoTitle;
|
||||
private String seoTitleIt;
|
||||
private String seoTitleEn;
|
||||
private String seoTitleDe;
|
||||
private String seoTitleFr;
|
||||
private String seoDescription;
|
||||
private String seoDescriptionIt;
|
||||
private String seoDescriptionEn;
|
||||
private String seoDescriptionDe;
|
||||
private String seoDescriptionFr;
|
||||
private String ogTitle;
|
||||
private String ogDescription;
|
||||
private Boolean indexable;
|
||||
@@ -175,6 +183,38 @@ public class AdminUpsertShopProductRequest {
|
||||
this.seoTitle = seoTitle;
|
||||
}
|
||||
|
||||
public String getSeoTitleIt() {
|
||||
return seoTitleIt;
|
||||
}
|
||||
|
||||
public void setSeoTitleIt(String seoTitleIt) {
|
||||
this.seoTitleIt = seoTitleIt;
|
||||
}
|
||||
|
||||
public String getSeoTitleEn() {
|
||||
return seoTitleEn;
|
||||
}
|
||||
|
||||
public void setSeoTitleEn(String seoTitleEn) {
|
||||
this.seoTitleEn = seoTitleEn;
|
||||
}
|
||||
|
||||
public String getSeoTitleDe() {
|
||||
return seoTitleDe;
|
||||
}
|
||||
|
||||
public void setSeoTitleDe(String seoTitleDe) {
|
||||
this.seoTitleDe = seoTitleDe;
|
||||
}
|
||||
|
||||
public String getSeoTitleFr() {
|
||||
return seoTitleFr;
|
||||
}
|
||||
|
||||
public void setSeoTitleFr(String seoTitleFr) {
|
||||
this.seoTitleFr = seoTitleFr;
|
||||
}
|
||||
|
||||
public String getSeoDescription() {
|
||||
return seoDescription;
|
||||
}
|
||||
@@ -183,6 +223,38 @@ public class AdminUpsertShopProductRequest {
|
||||
this.seoDescription = seoDescription;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionIt() {
|
||||
return seoDescriptionIt;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionIt(String seoDescriptionIt) {
|
||||
this.seoDescriptionIt = seoDescriptionIt;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionEn() {
|
||||
return seoDescriptionEn;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionEn(String seoDescriptionEn) {
|
||||
this.seoDescriptionEn = seoDescriptionEn;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionDe() {
|
||||
return seoDescriptionDe;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionDe(String seoDescriptionDe) {
|
||||
this.seoDescriptionDe = seoDescriptionDe;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionFr() {
|
||||
return seoDescriptionFr;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionFr(String seoDescriptionFr) {
|
||||
this.seoDescriptionFr = seoDescriptionFr;
|
||||
}
|
||||
|
||||
public String getOgTitle() {
|
||||
return ogTitle;
|
||||
}
|
||||
|
||||
@@ -86,9 +86,33 @@ public class ShopProduct {
|
||||
@Column(name = "seo_title", length = Integer.MAX_VALUE)
|
||||
private String seoTitle;
|
||||
|
||||
@Column(name = "seo_title_it", length = Integer.MAX_VALUE)
|
||||
private String seoTitleIt;
|
||||
|
||||
@Column(name = "seo_title_en", length = Integer.MAX_VALUE)
|
||||
private String seoTitleEn;
|
||||
|
||||
@Column(name = "seo_title_de", length = Integer.MAX_VALUE)
|
||||
private String seoTitleDe;
|
||||
|
||||
@Column(name = "seo_title_fr", length = Integer.MAX_VALUE)
|
||||
private String seoTitleFr;
|
||||
|
||||
@Column(name = "seo_description", length = Integer.MAX_VALUE)
|
||||
private String seoDescription;
|
||||
|
||||
@Column(name = "seo_description_it", length = Integer.MAX_VALUE)
|
||||
private String seoDescriptionIt;
|
||||
|
||||
@Column(name = "seo_description_en", length = Integer.MAX_VALUE)
|
||||
private String seoDescriptionEn;
|
||||
|
||||
@Column(name = "seo_description_de", length = Integer.MAX_VALUE)
|
||||
private String seoDescriptionDe;
|
||||
|
||||
@Column(name = "seo_description_fr", length = Integer.MAX_VALUE)
|
||||
private String seoDescriptionFr;
|
||||
|
||||
@Column(name = "og_title", length = Integer.MAX_VALUE)
|
||||
private String ogTitle;
|
||||
|
||||
@@ -319,6 +343,70 @@ public class ShopProduct {
|
||||
this.seoDescription = seoDescription;
|
||||
}
|
||||
|
||||
public String getSeoTitleIt() {
|
||||
return seoTitleIt;
|
||||
}
|
||||
|
||||
public void setSeoTitleIt(String seoTitleIt) {
|
||||
this.seoTitleIt = seoTitleIt;
|
||||
}
|
||||
|
||||
public String getSeoTitleEn() {
|
||||
return seoTitleEn;
|
||||
}
|
||||
|
||||
public void setSeoTitleEn(String seoTitleEn) {
|
||||
this.seoTitleEn = seoTitleEn;
|
||||
}
|
||||
|
||||
public String getSeoTitleDe() {
|
||||
return seoTitleDe;
|
||||
}
|
||||
|
||||
public void setSeoTitleDe(String seoTitleDe) {
|
||||
this.seoTitleDe = seoTitleDe;
|
||||
}
|
||||
|
||||
public String getSeoTitleFr() {
|
||||
return seoTitleFr;
|
||||
}
|
||||
|
||||
public void setSeoTitleFr(String seoTitleFr) {
|
||||
this.seoTitleFr = seoTitleFr;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionIt() {
|
||||
return seoDescriptionIt;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionIt(String seoDescriptionIt) {
|
||||
this.seoDescriptionIt = seoDescriptionIt;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionEn() {
|
||||
return seoDescriptionEn;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionEn(String seoDescriptionEn) {
|
||||
this.seoDescriptionEn = seoDescriptionEn;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionDe() {
|
||||
return seoDescriptionDe;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionDe(String seoDescriptionDe) {
|
||||
this.seoDescriptionDe = seoDescriptionDe;
|
||||
}
|
||||
|
||||
public String getSeoDescriptionFr() {
|
||||
return seoDescriptionFr;
|
||||
}
|
||||
|
||||
public void setSeoDescriptionFr(String seoDescriptionFr) {
|
||||
this.seoDescriptionFr = seoDescriptionFr;
|
||||
}
|
||||
|
||||
public String getOgTitle() {
|
||||
return ogTitle;
|
||||
}
|
||||
@@ -428,6 +516,36 @@ public class ShopProduct {
|
||||
}
|
||||
}
|
||||
|
||||
public String getSeoTitleForLanguage(String language) {
|
||||
return resolveLocalizedValue(language, seoTitle, seoTitleIt, seoTitleEn, seoTitleDe, seoTitleFr);
|
||||
}
|
||||
|
||||
public void setSeoTitleForLanguage(String language, String value) {
|
||||
switch (normalizeLanguage(language)) {
|
||||
case "it" -> seoTitleIt = value;
|
||||
case "en" -> seoTitleEn = value;
|
||||
case "de" -> seoTitleDe = value;
|
||||
case "fr" -> seoTitleFr = value;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getSeoDescriptionForLanguage(String language) {
|
||||
return resolveLocalizedValue(language, seoDescription, seoDescriptionIt, seoDescriptionEn, seoDescriptionDe, seoDescriptionFr);
|
||||
}
|
||||
|
||||
public void setSeoDescriptionForLanguage(String language, String value) {
|
||||
switch (normalizeLanguage(language)) {
|
||||
case "it" -> seoDescriptionIt = value;
|
||||
case "en" -> seoDescriptionEn = value;
|
||||
case "de" -> seoDescriptionDe = value;
|
||||
case "fr" -> seoDescriptionFr = value;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveLocalizedValue(String language,
|
||||
String fallback,
|
||||
String valueIt,
|
||||
|
||||
@@ -163,7 +163,13 @@ public class AdminShopProductControllerService {
|
||||
}
|
||||
}
|
||||
|
||||
shopProductModelAssetRepository.findByProduct_Id(productId).ifPresent(asset -> deleteExistingModelFile(asset, productId));
|
||||
shopProductModelAssetRepository.findByProduct_Id(productId).ifPresent(asset -> {
|
||||
deleteExistingModelFile(asset, productId);
|
||||
shopProductModelAssetRepository.delete(asset);
|
||||
});
|
||||
if (!variants.isEmpty()) {
|
||||
shopProductVariantRepository.deleteAll(variants);
|
||||
}
|
||||
shopProductRepository.delete(product);
|
||||
}
|
||||
|
||||
@@ -315,10 +321,18 @@ public class AdminShopProductControllerService {
|
||||
product.setDescriptionEn(localizedContent.descriptions().get("en"));
|
||||
product.setDescriptionDe(localizedContent.descriptions().get("de"));
|
||||
product.setDescriptionFr(localizedContent.descriptions().get("fr"));
|
||||
product.setSeoTitle(normalizeOptional(payload.getSeoTitle()));
|
||||
product.setSeoDescription(normalizeOptional(payload.getSeoDescription()));
|
||||
product.setOgTitle(normalizeOptional(payload.getOgTitle()));
|
||||
product.setOgDescription(normalizeOptional(payload.getOgDescription()));
|
||||
product.setSeoTitle(localizedContent.defaultSeoTitle());
|
||||
product.setSeoTitleIt(localizedContent.seoTitles().get("it"));
|
||||
product.setSeoTitleEn(localizedContent.seoTitles().get("en"));
|
||||
product.setSeoTitleDe(localizedContent.seoTitles().get("de"));
|
||||
product.setSeoTitleFr(localizedContent.seoTitles().get("fr"));
|
||||
product.setSeoDescription(localizedContent.defaultSeoDescription());
|
||||
product.setSeoDescriptionIt(localizedContent.seoDescriptions().get("it"));
|
||||
product.setSeoDescriptionEn(localizedContent.seoDescriptions().get("en"));
|
||||
product.setSeoDescriptionDe(localizedContent.seoDescriptions().get("de"));
|
||||
product.setSeoDescriptionFr(localizedContent.seoDescriptions().get("fr"));
|
||||
product.setOgTitle(localizedContent.defaultSeoTitle());
|
||||
product.setOgDescription(localizedContent.defaultSeoDescription());
|
||||
product.setIndexable(payload.getIndexable() == null || payload.getIndexable());
|
||||
product.setIsFeatured(Boolean.TRUE.equals(payload.getIsFeatured()));
|
||||
product.setIsActive(payload.getIsActive() == null || payload.getIsActive());
|
||||
@@ -374,16 +388,23 @@ public class AdminShopProductControllerService {
|
||||
}
|
||||
|
||||
List<AdminUpsertShopProductVariantRequest> normalized = new ArrayList<>(payloads);
|
||||
Set<String> colorKeys = new LinkedHashSet<>();
|
||||
Set<String> variantKeys = new LinkedHashSet<>();
|
||||
int defaultCount = 0;
|
||||
for (AdminUpsertShopProductVariantRequest payload : normalized) {
|
||||
if (payload == null) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Variant payload is required");
|
||||
}
|
||||
String colorName = normalizeRequired(payload.getColorName(), "Variant colorName is required");
|
||||
String colorKey = colorName.toLowerCase(Locale.ROOT);
|
||||
if (!colorKeys.add(colorKey)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Duplicate variant colorName: " + colorName);
|
||||
String materialCode = normalizeRequired(
|
||||
payload.getInternalMaterialCode(),
|
||||
"Variant internalMaterialCode is required"
|
||||
).toUpperCase(Locale.ROOT);
|
||||
String variantKey = materialCode + "|" + colorName.toLowerCase(Locale.ROOT);
|
||||
if (!variantKeys.add(variantKey)) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Duplicate variant combination: " + materialCode + " / " + colorName
|
||||
);
|
||||
}
|
||||
if (Boolean.TRUE.equals(payload.getIsDefault())) {
|
||||
defaultCount++;
|
||||
@@ -467,7 +488,15 @@ public class AdminShopProductControllerService {
|
||||
dto.setDescriptionDe(product.getDescriptionDe());
|
||||
dto.setDescriptionFr(product.getDescriptionFr());
|
||||
dto.setSeoTitle(product.getSeoTitle());
|
||||
dto.setSeoTitleIt(product.getSeoTitleIt());
|
||||
dto.setSeoTitleEn(product.getSeoTitleEn());
|
||||
dto.setSeoTitleDe(product.getSeoTitleDe());
|
||||
dto.setSeoTitleFr(product.getSeoTitleFr());
|
||||
dto.setSeoDescription(product.getSeoDescription());
|
||||
dto.setSeoDescriptionIt(product.getSeoDescriptionIt());
|
||||
dto.setSeoDescriptionEn(product.getSeoDescriptionEn());
|
||||
dto.setSeoDescriptionDe(product.getSeoDescriptionDe());
|
||||
dto.setSeoDescriptionFr(product.getSeoDescriptionFr());
|
||||
dto.setOgTitle(product.getOgTitle());
|
||||
dto.setOgDescription(product.getOgDescription());
|
||||
dto.setIndexable(product.getIndexable());
|
||||
@@ -596,13 +625,43 @@ public class AdminShopProductControllerService {
|
||||
descriptions.put("de", firstNonBlank(normalizeOptional(payload.getDescriptionDe()), fallbackDescription));
|
||||
descriptions.put("fr", firstNonBlank(normalizeOptional(payload.getDescriptionFr()), fallbackDescription));
|
||||
|
||||
String fallbackSeoTitle = firstNonBlank(
|
||||
normalizeOptional(payload.getSeoTitle()),
|
||||
normalizeOptional(payload.getSeoTitleIt()),
|
||||
normalizeOptional(payload.getSeoTitleEn()),
|
||||
normalizeOptional(payload.getSeoTitleDe()),
|
||||
normalizeOptional(payload.getSeoTitleFr())
|
||||
);
|
||||
Map<String, String> seoTitles = new LinkedHashMap<>();
|
||||
seoTitles.put("it", firstNonBlank(normalizeOptional(payload.getSeoTitleIt()), fallbackSeoTitle));
|
||||
seoTitles.put("en", firstNonBlank(normalizeOptional(payload.getSeoTitleEn()), fallbackSeoTitle));
|
||||
seoTitles.put("de", firstNonBlank(normalizeOptional(payload.getSeoTitleDe()), fallbackSeoTitle));
|
||||
seoTitles.put("fr", firstNonBlank(normalizeOptional(payload.getSeoTitleFr()), fallbackSeoTitle));
|
||||
|
||||
String fallbackSeoDescription = firstNonBlank(
|
||||
normalizeOptional(payload.getSeoDescription()),
|
||||
normalizeOptional(payload.getSeoDescriptionIt()),
|
||||
normalizeOptional(payload.getSeoDescriptionEn()),
|
||||
normalizeOptional(payload.getSeoDescriptionDe()),
|
||||
normalizeOptional(payload.getSeoDescriptionFr())
|
||||
);
|
||||
Map<String, String> seoDescriptions = new LinkedHashMap<>();
|
||||
seoDescriptions.put("it", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionIt()), fallbackSeoDescription), "Italian"));
|
||||
seoDescriptions.put("en", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionEn()), fallbackSeoDescription), "English"));
|
||||
seoDescriptions.put("de", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionDe()), fallbackSeoDescription), "German"));
|
||||
seoDescriptions.put("fr", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionFr()), fallbackSeoDescription), "French"));
|
||||
|
||||
return new LocalizedProductContent(
|
||||
names.get("it"),
|
||||
firstNonBlank(excerpts.get("it"), fallbackExcerpt),
|
||||
firstNonBlank(descriptions.get("it"), fallbackDescription),
|
||||
firstNonBlank(seoTitles.get("it"), fallbackSeoTitle),
|
||||
firstNonBlank(seoDescriptions.get("it"), fallbackSeoDescription),
|
||||
names,
|
||||
excerpts,
|
||||
descriptions
|
||||
descriptions,
|
||||
seoTitles,
|
||||
seoDescriptions
|
||||
);
|
||||
}
|
||||
|
||||
@@ -670,6 +729,13 @@ public class AdminShopProductControllerService {
|
||||
return normalized.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String validateSeoDescriptionLength(String value, String languageLabel) {
|
||||
if (value != null && value.length() > 160) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, languageLabel + " SEO description must be at most 160 characters");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void validateModelUpload(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "3D model file is required");
|
||||
@@ -786,9 +852,13 @@ public class AdminShopProductControllerService {
|
||||
String defaultName,
|
||||
String defaultExcerpt,
|
||||
String defaultDescription,
|
||||
String defaultSeoTitle,
|
||||
String defaultSeoDescription,
|
||||
Map<String, String> names,
|
||||
Map<String, String> excerpts,
|
||||
Map<String, String> descriptions
|
||||
Map<String, String> descriptions,
|
||||
Map<String, String> seoTitles,
|
||||
Map<String, String> seoDescriptions
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class MediaStorageService {
|
||||
private final String frontendBaseUrl;
|
||||
|
||||
public MediaStorageService(@Value("${media.storage.root:storage_media}") String storageRoot,
|
||||
@Value("${app.frontend.base-url:${APP_FRONTEND_BASE_URL:http://localhost:8080}}") String frontendBaseUrl) {
|
||||
@Value("${app.frontend.base-url:${APP_FRONTEND_BASE_URL:http://localhost:8081}}") String frontendBaseUrl) {
|
||||
this.normalizedRootLocation = Paths.get(storageRoot).toAbsolutePath().normalize();
|
||||
this.originalRootLocation = normalizedRootLocation.resolve("original").normalize();
|
||||
this.publicRootLocation = normalizedRootLocation.resolve("public").normalize();
|
||||
@@ -131,8 +131,11 @@ public class MediaStorageService {
|
||||
|
||||
private String buildMediaBaseUrl() {
|
||||
String normalized = frontendBaseUrl != null ? frontendBaseUrl.trim() : "";
|
||||
if (normalized.contains("localhost")){
|
||||
return "http://localhost:8081";
|
||||
}
|
||||
if (normalized.isBlank()) {
|
||||
normalized = "http://localhost:4200";
|
||||
normalized = "http://localhost:8081";
|
||||
}
|
||||
if (normalized.endsWith("/")) {
|
||||
normalized = normalized.substring(0, normalized.length() - 1);
|
||||
|
||||
@@ -375,16 +375,18 @@ public class PublicShopCatalogService {
|
||||
Map<String, List<PublicMediaUsageDto>> productMediaBySlug,
|
||||
String language) {
|
||||
List<PublicMediaUsageDto> images = productMediaBySlug.getOrDefault(productMediaUsageKey(entry.product()), List.of());
|
||||
String localizedSeoTitle = entry.product().getSeoTitleForLanguage(language);
|
||||
String localizedSeoDescription = entry.product().getSeoDescriptionForLanguage(language);
|
||||
return new ShopProductDetailDto(
|
||||
entry.product().getId(),
|
||||
entry.product().getSlug(),
|
||||
entry.product().getNameForLanguage(language),
|
||||
entry.product().getExcerptForLanguage(language),
|
||||
entry.product().getDescriptionForLanguage(language),
|
||||
entry.product().getSeoTitle(),
|
||||
entry.product().getSeoDescription(),
|
||||
entry.product().getOgTitle(),
|
||||
entry.product().getOgDescription(),
|
||||
localizedSeoTitle,
|
||||
localizedSeoDescription,
|
||||
localizedSeoTitle,
|
||||
localizedSeoDescription,
|
||||
entry.product().getIndexable(),
|
||||
entry.product().getIsFeatured(),
|
||||
entry.product().getSortOrder(),
|
||||
|
||||
Reference in New Issue
Block a user