diff --git a/CHANGELOG.md b/CHANGELOG.md index a567b84b39..b453fb2d71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `Intl.NumberFormat()`/`toLocaleString()` via polyfill support in NodeJs - @cewald (#3836, #4040) - Added `saveBandwidthOverCache` parameter for skipping caching for products data - @andrzejewsky (#3706) - New zoom effect for product gallery images - @Michal-Dziedzinski (#2755) +- Product Page Schema implementation as JSON-LD - @Michal-Dziedzinski (#3704) ### Fixed - Fixed Search product fails for category filter when categoryId is string - @adityasharma7 (#3929) diff --git a/core/helpers/index.ts b/core/helpers/index.ts index 8def58832f..650019c3f5 100644 --- a/core/helpers/index.ts +++ b/core/helpers/index.ts @@ -253,3 +253,97 @@ export function extendStore (moduleName: string | string[], module: any) { store.unregisterModule(moduleName) store.registerModule(moduleName, extendedModule) } + +export function reviewJsonLd (reviews, {name, category, mpn, url_path, price, stock, is_in_stock, sku, image, description}, priceCurrency) { + return reviews.map(({title, detail, nickname, created_at}) => ( + { + '@context': 'http://schema.org/', + '@type': 'Review', + reviewAspect: title, + reviewBody: detail, + datePublished: created_at, + author: nickname, + itemReviewed: { + '@type': 'Product', + name, + sku, + image, + description, + offers: { + '@type': 'Offer', + category: category + ? category + .map(({ name }) => name || null) + .filter(name => name !== null) + : null, + mpn, + url: url_path, + priceCurrency, + price, + itemCondition: 'https://schema.org/NewCondition', + availability: stock && is_in_stock ? 'InStock' : 'OutOfStock' + } + } + } + ) + ) +} + +function getMaterials (material, customAttributes) { + const materialsArr = [] + if (customAttributes && customAttributes.length && customAttributes.length > 0 && material && material.length && material.length > 0) { + const materialOptions = customAttributes.find(({attribute_code}) => attribute_code === 'material').options + if (Array.isArray(material)) { + for (let key in materialOptions) { + material.forEach(el => { + if (String(el) === materialOptions[key].value) { + materialsArr.push(materialOptions[key].label) + } + }) + } + } else { + for (let key in materialOptions) { + if (material === materialOptions[key].value) { + materialsArr.push(materialOptions[key].label) + } + } + } + } + return materialsArr +} + +export function productJsonLd ({ category, image, name, id, sku, mpn, description, price, url_path, stock, is_in_stock, material }, color, priceCurrency, customAttributes) { + return { + '@context': 'http://schema.org', + '@type': 'Product', + category: category + ? category + .map(({ name }) => name || null) + .filter(name => name !== null) + : null, + color, + description, + image, + itemCondition: 'http://schema.org/NewCondition', + material: getMaterials(material, customAttributes), + name, + productID: id, + sku, + mpn, + offers: { + '@type': 'Offer', + category: category + ? category + .map(({ name }) => name || null) + .filter(name => name !== null) + : null, + mpn, + url: url_path, + priceCurrency, + price, + itemCondition: 'https://schema.org/NewCondition', + availability: stock && is_in_stock ? 'InStock' : 'OutOfStock', + sku + } + } +} diff --git a/src/themes/default/components/core/blocks/Reviews/Reviews.vue b/src/themes/default/components/core/blocks/Reviews/Reviews.vue index 086d711df1..d528945519 100644 --- a/src/themes/default/components/core/blocks/Reviews/Reviews.vue +++ b/src/themes/default/components/core/blocks/Reviews/Reviews.vue @@ -10,6 +10,7 @@ :per-page="4" :items="reviews" :product-name="productName" + :product="product" />
{{ item.nickname }}, {{ item.created_at | date(null, storeView) }}
-+
{{ item.detail }}