Skip to content

Commit

Permalink
#4129 Microdata product description structure is created in a separat…
Browse files Browse the repository at this point in the history
…e view
  • Loading branch information
DmitriyKulagin committed Dec 11, 2019
1 parent 8ed3055 commit 11ca44c
Show file tree
Hide file tree
Showing 17 changed files with 136 additions and 34 deletions.
5 changes: 5 additions & 0 deletions src/Libraries/Nop.Core/Domain/Seo/SeoSettings.cs
Expand Up @@ -82,5 +82,10 @@ public class SeoSettings : ISettings
/// Custom tags in the <![CDATA[<head></head>]]> section
/// </summary>
public string CustomHeadTags { get; set; }

/// <summary>
/// A value indicating whether Microdata tags should be generated
/// </summary>
public bool MicrodataEnabled { get; set; }
}
}
Expand Up @@ -6048,6 +6048,7 @@ protected virtual void InstallSettings()
WwwRequirement = WwwRequirement.NoMatter,
TwitterMetaTags = true,
OpenGraphMetaTags = true,
MicrodataEnabled = true,
ReservedUrlRecordSlugs = new List<string>
{
"admin",
Expand Down
Expand Up @@ -6972,6 +6972,12 @@
<LocaleResource Name="Admin.Configuration.Settings.GeneralCommon.Logo.Hint">
<Value>Upload your store logo. If not uploaded, then the default one will be used.</Value>
</LocaleResource>
<LocaleResource Name="Admin.Configuration.Settings.GeneralCommon.Microdata">
<Value>Microdata tags</Value>
</LocaleResource>
<LocaleResource Name="Admin.Configuration.Settings.GeneralCommon.Microdata.Hint">
<Value>Check to generate Microdata tags on the product details page.</Value>
</LocaleResource>
<LocaleResource Name="Admin.Configuration.Settings.GeneralCommon.OpenGraphMetaTags">
<Value>Open Graph META tags</Value>
</LocaleResource>
Expand Down
Expand Up @@ -1285,6 +1285,7 @@ public virtual IActionResult GeneralCommon(GeneralCommonSettingsModel model)
seoSettings.WwwRequirement = (WwwRequirement)model.SeoSettings.WwwRequirement;
seoSettings.TwitterMetaTags = model.SeoSettings.TwitterMetaTags;
seoSettings.OpenGraphMetaTags = model.SeoSettings.OpenGraphMetaTags;
seoSettings.MicrodataEnabled = model.SeoSettings.MicrodataEnabled;
seoSettings.CustomHeadTags = model.SeoSettings.CustomHeadTags;

//we do not clear cache after each setting update.
Expand All @@ -1302,6 +1303,7 @@ public virtual IActionResult GeneralCommon(GeneralCommonSettingsModel model)
_settingService.SaveSettingOverridablePerStore(seoSettings, x => x.TwitterMetaTags, model.SeoSettings.TwitterMetaTags_OverrideForStore, storeScope, false);
_settingService.SaveSettingOverridablePerStore(seoSettings, x => x.OpenGraphMetaTags, model.SeoSettings.OpenGraphMetaTags_OverrideForStore, storeScope, false);
_settingService.SaveSettingOverridablePerStore(seoSettings, x => x.CustomHeadTags, model.SeoSettings.CustomHeadTags_OverrideForStore, storeScope, false);
_settingService.SaveSettingOverridablePerStore(seoSettings, x => x.MicrodataEnabled, model.SeoSettings.MicrodataEnabled_OverrideForStore, storeScope, false);

//now clear settings cache
_settingService.ClearCache();
Expand Down
Expand Up @@ -435,7 +435,8 @@ protected virtual SeoSettingsModel PrepareSeoSettingsModel()

TwitterMetaTags = seoSettings.TwitterMetaTags,
OpenGraphMetaTags = seoSettings.OpenGraphMetaTags,
CustomHeadTags = seoSettings.CustomHeadTags
CustomHeadTags = seoSettings.CustomHeadTags,
MicrodataEnabled = seoSettings.MicrodataEnabled
};

if (storeId <= 0)
Expand All @@ -454,6 +455,7 @@ protected virtual SeoSettingsModel PrepareSeoSettingsModel()
model.TwitterMetaTags_OverrideForStore = _settingService.SettingExists(seoSettings, x => x.TwitterMetaTags, storeId);
model.OpenGraphMetaTags_OverrideForStore = _settingService.SettingExists(seoSettings, x => x.OpenGraphMetaTags, storeId);
model.CustomHeadTags_OverrideForStore = _settingService.SettingExists(seoSettings, x => x.CustomHeadTags, storeId);
model.MicrodataEnabled_OverrideForStore = _settingService.SettingExists(seoSettings, x => x.MicrodataEnabled, storeId);

return model;
}
Expand Down
Expand Up @@ -64,6 +64,9 @@ public partial class SeoSettingsModel : BaseNopModel, ISettingsModel
public string CustomHeadTags { get; set; }
public bool CustomHeadTags_OverrideForStore { get; set; }

[NopResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.Microdata")]
public bool MicrodataEnabled { get; set; }
public bool MicrodataEnabled_OverrideForStore { get; set; }
#endregion
}
}
Expand Up @@ -111,6 +111,16 @@
<span asp-validation-for="SeoSettings.OpenGraphMetaTags"></span>
</div>
</div>
<div class="form-group advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SeoSettings.MicrodataEnabled_OverrideForStore" asp-input="SeoSettings.MicrodataEnabled" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SeoSettings.MicrodataEnabled" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SeoSettings.MicrodataEnabled" />
<span asp-validation-for="SeoSettings.MicrodataEnabled"></span>
</div>
</div>
<div class="form-group advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SeoSettings.CustomHeadTags_OverrideForStore" asp-input="SeoSettings.CustomHeadTags" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
Expand All @@ -120,5 +130,5 @@
<nop-textarea asp-for="SeoSettings.CustomHeadTags" />
<span asp-validation-for="SeoSettings.CustomHeadTags"></span>
</div>
</div>
</div>
</div>
9 changes: 3 additions & 6 deletions src/Presentation/Nop.Web/Factories/ProductModelFactory.cs
Expand Up @@ -1179,7 +1179,8 @@ public virtual string PrepareProductTemplateViewPath(Product product)
ManageInventoryMethod = product.ManageInventoryMethod,
StockAvailability = _productService.FormatStockMessage(product, ""),
HasSampleDownload = product.IsDownload && product.HasSampleDownload,
DisplayDiscontinuedMessage = !product.Published && _catalogSettings.DisplayDiscontinuedMessageForUnpublishedProducts
DisplayDiscontinuedMessage = !product.Published && _catalogSettings.DisplayDiscontinuedMessageForUnpublishedProducts,
AvailableEndDate = product.AvailableEndDateTimeUtc
};

//automatically generate product description?
Expand Down Expand Up @@ -1318,11 +1319,7 @@ public virtual string PrepareProductTemplateViewPath(Product product)
}

//manufacturers
//do not prepare this model for the associated products. anyway it's not used
if (!isAssociatedProduct)
{
model.ProductManufacturers = PrepareProductManufacturerModels(product);
}
model.ProductManufacturers = PrepareProductManufacturerModels(product);

//rental products
if (product.IsRental)
Expand Down
Expand Up @@ -70,6 +70,8 @@ public ProductDetailsModel()
public DateTime? RentalStartDate { get; set; }
public DateTime? RentalEndDate { get; set; }

public DateTime? AvailableEndDate { get; set; }

public ManageInventoryMethod ManageInventoryMethod { get; set; }

public string StockAvailability { get; set; }
Expand Down
Expand Up @@ -53,15 +53,19 @@
<div class="page-body">
@await Component.InvokeAsync("Widget", new { widgetZone = PublicWidgetZones.ProductDetailsTop, additionalData = Model })
<form asp-route="Product" asp-route-sename="@Model.SeName" method="post" id="product-details-form">
<div itemscope itemtype="http://schema.org/Product" data-productid="@Model.Id">
@if (seoSettings.MicrodataEnabled)
{
@await Html.PartialAsync("_Microdata", Model)
}
<div data-productid="@Model.Id">
<div class="product-essential">
@await Component.InvokeAsync("Widget", new { widgetZone = PublicWidgetZones.ProductDetailsEssentialTop, additionalData = Model })
<!--product pictures-->
@await Html.PartialAsync("_ProductDetailsPictures", Model)
<div class="overview">
@await Html.PartialAsync("_Discontinued", Model)
<div class="product-name">
<h1 itemprop="name">
<h1>
@Model.Name
</h1>
</div>
Expand All @@ -88,7 +92,7 @@
</div>
@if (!string.IsNullOrEmpty(Model.FullDescription))
{
<div class="full-description" itemprop="description">
<div class="full-description">
@Html.Raw(Model.FullDescription)
</div>
}
Expand Down
Expand Up @@ -53,15 +53,19 @@
<div class="page-body">
@await Component.InvokeAsync("Widget", new { widgetZone = PublicWidgetZones.ProductDetailsTop, additionalData = Model })
<form asp-route="Product" asp-route-sename="@Model.SeName" method="post" id="product-details-form">
<div itemscope itemtype="http://schema.org/Product" data-productid="@Model.Id">
@if (seoSettings.MicrodataEnabled)
{
@await Html.PartialAsync("_Microdata", Model)
}
<div data-productid="@Model.Id">
<div class="product-essential">
@await Component.InvokeAsync("Widget", new { widgetZone = PublicWidgetZones.ProductDetailsEssentialTop, additionalData = Model })
<!--product pictures-->
@await Html.PartialAsync("_ProductDetailsPictures", Model)
<div class="overview">
@await Html.PartialAsync("_Discontinued", Model)
<div class="product-name">
<h1 itemprop="name">
<h1>
@Model.Name
</h1>
</div>
Expand Down Expand Up @@ -131,7 +135,7 @@
</div>
@if (!string.IsNullOrEmpty(Model.FullDescription))
{
<div class="full-description" itemprop="description">
<div class="full-description">
@Html.Raw(Model.FullDescription)
</div>
}
Expand Down
60 changes: 60 additions & 0 deletions src/Presentation/Nop.Web/Views/Product/_Microdata.cshtml
@@ -0,0 +1,60 @@
@model ProductDetailsModel
@using System.Globalization
@using Nop.Core.Domain.Catalog
@inject Nop.Core.IWebHelper webHelper

<!--Microdata-->
<div @if (ViewData.ContainsKey("isAccessoryOrSparePartFor")) { <text>itemprop="isAccessoryOrSparePartFor"</text> } itemscope itemtype="http://schema.org/Product">
<meta itemprop="name" content="@Model.Name"/>
<meta itemprop="sku" content="@Model.Sku"/>
<meta itemprop="gtin" content="@Model.Gtin"/>
<meta itemprop="mpn" content="@Model.ManufacturerPartNumber"/>
<meta itemprop="description" content="@Model.ShortDescription"/>
<meta itemprop="image" content="@Model.DefaultPictureModel.ImageUrl"/>
@foreach (var manufacturer in Model.ProductManufacturers)
{
<meta itemprop="brand" content="@manufacturer.Name"/>
}
@if (@Model.ProductReviewOverview.TotalReviews > 0)
{
<div itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">
@{
var ratingPercent = 0;
if (Model.ProductReviewOverview.TotalReviews != 0)
{
ratingPercent = ((Model.ProductReviewOverview.RatingSum * 100) / Model.ProductReviewOverview.TotalReviews) / 5;
}
var ratingValue = ratingPercent / (decimal)20;
}
<meta itemprop="ratingValue" content="@ratingValue.ToString("0.0", new CultureInfo("en-US"))"/>
<meta itemprop="reviewCount" content="@Model.ProductReviewOverview.TotalReviews"/>
</div>
}
<div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
<meta itemprop="url" content="@Url.RouteUrl("Product", new { SeName = Model.SeName }, webHelper.CurrentRequestProtocol).ToLowerInvariant()"/>
<meta itemprop="price" content="@Model.ProductPrice.PriceValue.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)"/>
<meta itemprop="priceCurrency" content="@Model.ProductPrice.CurrencyCode"/>
<meta itemprop="priceValidUntil" content="@Model.AvailableEndDate"/>
@if (Model.ManageInventoryMethod == ManageInventoryMethod.ManageStock || Model.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes)
{
@if (Model.StockAvailability == @T("Products.Availability.InStock").ToString() || Model.StockAvailability == @T("Products.Availability.InStockWithQuantity").ToString())
{
<meta itemprop="availability" content="http://schema.org/InStock"/>
}
else
{
<meta itemprop="availability" content="http://schema.org/OutOfStock"/>
}
}
</div>
<div itemprop="review" itemscope itemtype="http://schema.org/Review">
<meta itemprop="author" content="ALL"/>
<meta itemprop="url" content="@Url.RouteUrl("ProductReviews", new { productId = Model.ProductReviewOverview.ProductId })"/>
</div>
@foreach (var product in Model.AssociatedProducts)
{
var dataAccociatedType = new ViewDataDictionary(ViewData);
dataAccociatedType.Add("isAccessoryOrSparePartFor", "true");
@await Html.PartialAsync("_Microdata", product, dataAccociatedType)
}
</div>
Expand Up @@ -9,7 +9,7 @@
@if (Model.DefaultPictureZoomEnabled && Model.PictureModels.Count == 1)
{
<a href="@Model.DefaultPictureModel.FullSizeImageUrl" title="@Model.DefaultPictureModel.Title" id="main-product-img-lightbox-anchor-@Model.Id">
<img alt="@Model.DefaultPictureModel.AlternateText" src="@Model.DefaultPictureModel.ImageUrl" title="@Model.DefaultPictureModel.Title" itemprop="image" id="main-product-img-@Model.Id" />
<img alt="@Model.DefaultPictureModel.AlternateText" src="@Model.DefaultPictureModel.ImageUrl" title="@Model.DefaultPictureModel.Title" id="main-product-img-@Model.Id" />
</a>
<script asp-location="Footer">
$(document).ready(function() {
Expand All @@ -19,7 +19,7 @@
}
else
{
<img alt="@Model.DefaultPictureModel.AlternateText" src="@Model.DefaultPictureModel.ImageUrl" title="@Model.DefaultPictureModel.Title" itemprop="image" id="main-product-img-@Model.Id" />
<img alt="@Model.DefaultPictureModel.AlternateText" src="@Model.DefaultPictureModel.ImageUrl" title="@Model.DefaultPictureModel.Title" id="main-product-img-@Model.Id" />
}
</div>
@if (Model.PictureModels.Count > 1 && Model.DefaultPictureZoomEnabled)
Expand Down
17 changes: 7 additions & 10 deletions src/Presentation/Nop.Web/Views/Product/_ProductPrice.cshtml
Expand Up @@ -4,7 +4,7 @@
@inject IWorkContext workContext
@if (!Model.CustomerEntersPrice)
{
<div class="prices" itemprop="offers" itemscope itemtype="http://schema.org/Offer">
<div class="prices">
@if (Model.CallForPrice)
{
@*call for price*@
Expand Down Expand Up @@ -42,10 +42,11 @@
@*display "Price:" label if we have old price or discounted one*@
<label>@T("Products.Price"):</label>
}
@*render price*@<span @if (string.IsNullOrWhiteSpace(Model.PriceWithDiscount))
{
<text> itemprop="price" content="@Model.PriceValue.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)" class="price-value-@(Model.ProductId)" </text>
}>
@*render price*@
<span @if (string.IsNullOrWhiteSpace(Model.PriceWithDiscount))
{
<text>class="price-value-@(Model.ProductId)" </text>
}>
@Html.Raw(Model.Price)
</span>
</div>
Expand All @@ -54,7 +55,7 @@
@*discounted price*@
<div class="product-price discounted-price">
<span>@T("Products.Price.WithDiscount"):</span>
<span itemprop="price" content="@Model.PriceValue.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)" class="price-value-@(Model.ProductId)">
<span class="price-value-@(Model.ProductId)">
@Html.Raw(Model.PriceWithDiscount)
</span>
</div>
Expand All @@ -74,10 +75,6 @@
@T(inclTax ? "Products.Price.TaxShipping.InclTax" : "Products.Price.TaxShipping.ExclTax", Url.RouteUrl("Topic", new {SeName = Html.GetTopicSeName("shippinginfo")}))
</div>
}
if (!string.IsNullOrEmpty(Model.CurrencyCode))
{
<meta itemprop="priceCurrency" content="@Model.CurrencyCode"/>
}
}
</div>
}
@@ -1,5 +1,4 @@
@model ProductReviewOverviewModel
@using System.Globalization
@{
var ratingPercent = 0;
if (Model.TotalReviews != 0)
Expand All @@ -9,7 +8,7 @@
}
@if (Model.AllowCustomerReviews)
{
<div class="product-reviews-overview" @if (Model.TotalReviews > 0) { <text>itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating"</text>}>
<div class="product-reviews-overview">
<div class="product-review-box">
<div class="rating">
<div style="width: @(ratingPercent)%">
Expand All @@ -22,11 +21,7 @@
<div class="product-review-links">
<a href="@Url.RouteUrl("ProductReviews", new { productId = Model.ProductId })">@Model.TotalReviews
@T("Reviews.Overview.Reviews")</a> <span class="separator">|</span> <a href="@Url.RouteUrl("ProductReviews", new { productId = Model.ProductId })">@T("Reviews.Overview.AddNew")</a>
</div>
@*hidden microdata info*@
var ratingValue = ratingPercent / (decimal)20;
<span itemprop="ratingValue" style="display: none;">@ratingValue.ToString("0.0", new CultureInfo("en-US"))</span>
<span itemprop="reviewCount" style="display: none;">@Model.TotalReviews</span>
</div>
}
else
{
Expand Down
Expand Up @@ -6,7 +6,7 @@
{
<div class="sku" @(string.IsNullOrWhiteSpace(Model.Sku) ? Html.Raw("style=\"display:none\"") : null)>
<span class="label">@T("Products.Sku"):</span>
<span class="value" itemprop="sku" id="sku-@Model.Id">@Model.Sku</span>
<span class="value" id="sku-@Model.Id">@Model.Sku</span>
</div>
}
@*Manufacturer part number*@
Expand Down
14 changes: 14 additions & 0 deletions upgradescripts/4.20-4.30 (under development)/upgrade.sql
Expand Up @@ -176,6 +176,12 @@ set @resources='
<LocaleResource Name="Admin.Configuration.Settings.GeneralCommon.FaviconAndAppIcons.UploadIconsArchive.Hint">
<Value>Upload archive with favicon and app icons for different operating systems and devices. You can see an example of the favicon and app icons archive in /icons/samples in the root of the site. Your favicon and app icons path is "/icons/icons_{0}"</Value>
</LocaleResource>
<LocaleResource Name="Admin.Configuration.Settings.GeneralCommon.Microdata">
<Value>Microdata tags</Value>
</LocaleResource>
<LocaleResource Name="Admin.Configuration.Settings.GeneralCommon.Microdata.Hint">
<Value>Check to generate Microdata tags on the product details page.</Value>
</LocaleResource>
</Language>
'

Expand Down Expand Up @@ -288,4 +294,12 @@ BEGIN
ALTER TABLE [StorePickupPoint] ADD
Longitude decimal(18, 8) NULL
END
GO

--new setting
IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [Name] = N'seosettings.microdataenabled')
BEGIN
INSERT [Setting] ([Name], [Value], [StoreId])
VALUES (N'seosettings.microdataenabled', 'true', 0)
END
GO

0 comments on commit 11ca44c

Please sign in to comment.