using System.Globalization; using System.Text.Encodings.Web; using Nop.Core; using Nop.Core.Domain.Catalog; using Nop.Core.Events; using Nop.Web.Framework.Mvc.Routing; using Nop.Web.Models.Catalog; using Nop.Web.Models.JsonLD; namespace Nop.Web.Factories; /// /// Represents the JSON-LD model factory implementation /// public partial class JsonLdModelFactory : IJsonLdModelFactory { #region Fields protected readonly IEventPublisher _eventPublisher; protected readonly INopUrlHelper _nopUrlHelper; protected readonly IWebHelper _webHelper; #endregion #region Ctor public JsonLdModelFactory(IEventPublisher eventPublisher, INopUrlHelper nopUrlHelper, IWebHelper webHelper) { _eventPublisher = eventPublisher; _nopUrlHelper = nopUrlHelper; _webHelper = webHelper; } #endregion #region Utilities /// /// Prepare JSON-LD category breadcrumb model /// /// List of category models /// A task that represents the asynchronous operation /// The task result contains JSON-LD category breadcrumb model /// protected virtual async Task PrepareJsonLdBreadcrumbListAsync(IList categoryModels) { var breadcrumbList = new JsonLdBreadcrumbListModel(); var position = 1; foreach (var cat in categoryModels) { var breadcrumbListItem = new JsonLdBreadcrumbListItemModel { Position = position, Item = new JsonLdBreadcrumbItemModel { Id = await _nopUrlHelper.RouteGenericUrlAsync(new { SeName = cat.SeName }, _webHelper.GetCurrentRequestProtocol()), Name = cat.Name } }; breadcrumbList.ItemListElement.Add(breadcrumbListItem); position++; } return breadcrumbList; } #endregion #region Methods /// /// Prepare JSON-LD category breadcrumb model /// /// List of category models /// A task that represents the asynchronous operation /// The task result contains JSON-LD category breadcrumb model /// public virtual async Task PrepareJsonLdCategoryBreadcrumbAsync(IList categoryModels) { var breadcrumbList = await PrepareJsonLdBreadcrumbListAsync(categoryModels); await _eventPublisher.PublishAsync(new JsonLdCreatedEvent(breadcrumbList)); return breadcrumbList; } /// /// Prepare JSON-LD product breadcrumb model /// /// Product breadcrumb model /// A task that represents the asynchronous operation /// The task result contains JSON-LD product breadcrumb model /// public virtual async Task PrepareJsonLdProductBreadcrumbAsync(ProductDetailsModel.ProductBreadcrumbModel breadcrumbModel) { var breadcrumbList = await PrepareJsonLdBreadcrumbListAsync(breadcrumbModel.CategoryBreadcrumb); breadcrumbList.ItemListElement.Add(new JsonLdBreadcrumbListItemModel { Position = breadcrumbList.ItemListElement.Count + 1, Item = new JsonLdBreadcrumbItemModel { Id = await _nopUrlHelper.RouteGenericUrlAsync(new { SeName = breadcrumbModel.ProductSeName }, _webHelper.GetCurrentRequestProtocol()), Name = breadcrumbModel.ProductName, } }); await _eventPublisher.PublishAsync(new JsonLdCreatedEvent(breadcrumbList)); return breadcrumbList; } /// /// Prepare JSON-LD product model /// /// Product details model /// Product URL /// A task that represents the asynchronous operation /// The task result contains JSON-LD product model /// public virtual async Task PrepareJsonLdProductAsync(ProductDetailsModel model, string productUrl = null) { productUrl ??= await _nopUrlHelper.RouteGenericUrlAsync(new { SeName = model.SeName }, _webHelper.GetCurrentRequestProtocol()); var productPrice = model.AssociatedProducts.Any() ? model.AssociatedProducts.Min(associatedProduct => associatedProduct.ProductPrice.PriceValue) : model.ProductPrice.PriceValue; var product = new JsonLdProductModel { Name = model.Name, Sku = model.Sku, Gtin = model.Gtin, Mpn = model.ManufacturerPartNumber, Description = model.ShortDescription, Image = model.DefaultPictureModel.ImageUrl, Offer = new JsonLdOfferModel { Url = productUrl.ToLowerInvariant(), Price = model.ProductPrice.CallForPrice ? null : productPrice?.ToString("0.00", CultureInfo.InvariantCulture), PriceCurrency = model.ProductPrice.CurrencyCode, PriceValidUntil = model.AvailableEndDate, Availability = @"https://schema.org/" + (model.InStock ? "InStock" : "OutOfStock") }, Brand = model.ProductManufacturers?.Select(manufacturer => new JsonLdBrandModel { Name = manufacturer.Name }).ToList() }; if (model.ProductReviewOverview.TotalReviews > 0) { var ratingPercent = model.ProductReviewOverview.RatingSum * 100 / model.ProductReviewOverview.TotalReviews / 5; var ratingValue = ratingPercent / (decimal)20; product.AggregateRating = new JsonLdAggregateRatingModel { RatingValue = ratingValue.ToString("0.0", CultureInfo.InvariantCulture), ReviewCount = model.ProductReviewOverview.TotalReviews }; product.Review = model.ProductReviews.Items?.Select(review => new JsonLdReviewModel { Name = JavaScriptEncoder.Default.Encode(review.Title), ReviewBody = JavaScriptEncoder.Default.Encode(review.ReviewText), ReviewRating = new JsonLdRatingModel { RatingValue = review.Rating }, Author = new JsonLdPersonModel { Name = JavaScriptEncoder.Default.Encode(review.CustomerName) }, DatePublished = review.WrittenOnStr }).ToList(); } foreach (var associatedProduct in model.AssociatedProducts) { var parentUrl = !associatedProduct.VisibleIndividually ? productUrl : null; product.HasVariant.Add(await PrepareJsonLdProductAsync(associatedProduct, parentUrl)); } await _eventPublisher.PublishAsync(new JsonLdCreatedEvent(product)); return product; } #endregion }