Error executing template "Designs/Swift/eCom/ProductCatalog/ProductViewDetail.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at CompiledRazorTemplates.Dynamic.RazorEngine_e577aa59026643fab57f89c11810346a.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits ViewModelTemplate<ProductViewModel> 2 @using Dynamicweb.Rendering 3 @using Dynamicweb.Ecommerce.ProductCatalog 4 @using Dynamicweb.Core 5 6 @{ 7 string metaDescription = string.IsNullOrEmpty(Model.MetaDescription) ? Model.Name : Model.MetaDescription; 8 9 Pageview.Meta.AddTag($"<meta property=\"og:image\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{Model.DefaultImage.Value}\">"); 10 Pageview.Meta.AddTag("twitter:image", Model.DefaultImage.Value); 11 12 Pageview.Meta.AddTag($"<meta property=\"og:image:alt\" content=\"{Model.Name}\">"); 13 Pageview.Meta.AddTag($"<meta property=\"og:description\" content=\"{metaDescription}\">"); 14 15 Pageview.Meta.AddTag("twitter:image:alt", Model.Name); 16 Pageview.Meta.AddTag("twitter:description", metaDescription); 17 } 18 19 @{ 20 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 21 { 22 Dynamicweb.Context.Current.Items["ProductDetails"] = Model; 23 } 24 else 25 { 26 Dynamicweb.Context.Current.Items.Add("ProductDetails", Model); 27 } 28 29 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 30 if (isLazyLoadingForProductInfoEnabled) 31 { 32 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower(); 33 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat); 34 bool hasVariantId = !string.IsNullOrEmpty(Model.VariantId); 35 string variantIdParam = hasVariantId ? $"/{Model.VariantId}" : ""; 36 string priceFilledProperties = $"Price,PriceFormatted{(showPricesWithVat == "false" && !neverShowVat ? ",PriceWithVat,PriceWithVatFormatted" : "")}"; 37 string productInfoFeed = $@"/dwapi/ecommerce/products/{Model.Id}{variantIdParam} 38 ?UserId={Converter.ToString(Pageview.User?.ID)} 39 &LanguageId={Pageview.Area.EcomLanguageId}&CurrencyCode={Pageview.Area.EcomCurrencyId}&CountryCode={Pageview.Area.EcomCountryCode}&ShopId={Pageview.Area.EcomShopId} 40 &FilledProperties=Id,Price,PriceBeforeDiscount,StockLevel,VariantInfo,NeverOutOfstock,Prices 41 &PriceSettings.ShowPricesWithVat={Pageview.Area.EcomPricesWithVat} 42 &PriceSettings.FilledProperties={priceFilledProperties} 43 &getproductinfo=true"; 44 Dynamicweb.Context.Current.Items["ProductInfoFeed"] = productInfoFeed; 45 46 <script type="module"> 47 swift.LiveProductInfo.init(); 48 </script> 49 } 50 } 51 52 <script> 53 gtag("event", "view_item", { 54 currency: "@Model.Price.CurrencyCode", 55 value: @Model.Price.ToStringInvariant(), 56 items: [ 57 { 58 item_id: "@Model.Number", 59 item_name: "@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(Model.Name)", 60 currency: "@Model.Price.CurrencyCode", 61 price: @Model.Price.ToStringInvariant(), 62 discount: @Model.Discount.ToStringInvariant() 63 } 64 ] 65 }); 66 </script> 67 68 <script> 69 window.addEventListener('load', function (event) { 70 swift.Video.init(); 71 }); 72 </script> 73
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage.cshtml"
System.ArgumentNullException: Value cannot be null. (Parameter 'source')
   at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_f8835b9562274c43a8561b714a542177.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 @using System.Text.RegularExpressions; 6 7 @functions { 8 public ProductViewModel product { get; set; } = new ProductViewModel(); 9 public string galleryLayout { get; set; } 10 public string[] supportedImageFormats { get; set; } 11 public string[] supportedVideoFormats { get; set; } 12 public string[] supportedDocumentFormats { get; set; } 13 public string[] allSupportedFormats { get; set; } 14 15 public class RatioSettings 16 { 17 public string Ratio { get; set; } 18 public string CssClass { get; set; } 19 public string CssVariable { get; set; } 20 public string Fill { get; set; } 21 } 22 23 public RatioSettings GetRatioSettings(string size = "desktop") 24 { 25 var ratioSettings = new RatioSettings(); 26 27 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 28 ratio = ratio != "0" ? ratio : ""; 29 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 30 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 31 cssClass = ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 32 cssVariable = ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 33 34 ratioSettings.Ratio = ratio; 35 ratioSettings.CssClass = cssClass; 36 ratioSettings.CssVariable = cssVariable; 37 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 38 39 return ratioSettings; 40 } 41 42 public string GetArrowsColor() 43 { 44 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 45 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 46 return arrowsColor; 47 } 48 49 public string GetThumbnailPlacement() 50 { 51 return Model.Item.GetRawValueString("ThumbnailPlacement", "bottom"); 52 } 53 54 public string GetThumbnailRowSettingCss() 55 { 56 switch (GetThumbnailPlacement()) 57 { 58 case "bottom": 59 return "d-flex flex-wrap"; 60 case "left": 61 return "d-flex flex-column order-first"; 62 case "right": 63 return "d-flex flex-column order-last"; 64 default: 65 return "d-flex flex-wrap"; 66 } 67 } 68 69 public Dictionary<string, object> GetVideoParams(MediaViewModel asset, string size) 70 { 71 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 72 string type = GetVideoType(asset.Value); 73 bool openInModal = Model.Item.GetString("OpenVideoInModal") == "true" ? true : false; 74 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 75 76 var videoParams = new Dictionary<string, object>(); 77 videoParams.Add("AssetName", asset.Name); 78 videoParams.Add("AssetVideoType", type); 79 videoParams.Add("AssetDisplayName", asset.DisplayName); 80 videoParams.Add("OpenVideoInModal", openInModal); 81 videoParams.Add("VideoAutoPlay", autoPlay); 82 videoParams.Add("Size", size); 83 videoParams.Add("Id", Model.ID); 84 return videoParams; 85 86 } 87 88 public string GetVideoType(string assetValue) 89 { 90 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : string.Empty; 91 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 92 type = string.IsNullOrEmpty(type) ? "selfhosted" : type; 93 94 return type; 95 } 96 97 public string GetYoutubeScreenDump(string assetValue, string quality) 98 { 99 var regex = new Regex(@"(?:youtube\.com\/.*[\?&]v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]+)(?:\?.*)?"); 100 Match match = regex.Match(assetValue); 101 string videoId = match.Success ? match.Groups[1].Value : string.Empty; 102 string youtubeThumbnail = $"https://img.youtube.com/vi/{videoId}/{quality}.jpg"; 103 return youtubeThumbnail; 104 } 105 } 106 107 @{ 108 ProductViewModel product = null; 109 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 110 { 111 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 112 } 113 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 114 { 115 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 116 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 117 118 if (productList?.Products is object) 119 { 120 product = productList.Products[0]; 121 } 122 } 123 } 124 125 @if (product is object) 126 { 127 @* Supported formats *@ 128 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 129 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 130 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 131 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 132 133 @* Collect the assets *@ 134 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>(); 135 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 136 137 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 138 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 139 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 140 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 141 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 142 assetsList = assetsList.Union(assetsImages); 143 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 144 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 145 146 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 147 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 148 149 int totalAssets = 0; 150 if (showOnlyPrimaryImage == false) 151 { 152 foreach (MediaViewModel asset in assetsList) 153 { 154 var assetValue = asset.Value; 155 foreach (string format in allSupportedFormats) 156 { 157 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 158 { 159 totalAssets++; 160 } 161 } 162 } 163 } 164 165 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null) || totalAssets == 0 && defaultImageFallback) 166 { 167 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 168 totalAssets = 1; 169 } 170 171 @* Theme settings *@ 172 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 173 174 var badgeParms = new Dictionary<string, object>(); 175 badgeParms.Add("size", "h5"); 176 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 177 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 178 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 179 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 180 badgeParms.Add("campaignBadgesValues", Model.Item.GetList("CampaignBadges")?.GetRawValue().OfType<string>().ToList()); 181 182 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 183 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 184 DateTime createdDate = product.Created.Value; 185 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 186 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 187 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 188 189 @* Get assets from selected categories or get all assets *@ 190 if (totalAssets != 0) 191 { 192 int assetNumber = 0; 193 int thumbnailNumber = 0; 194 int modalAssetNumber = 0; 195 string thumbnailAxisCss = GetThumbnailPlacement() == "bottom" ? "flex-column" : string.Empty; 196 197 <div class="d-flex gap-3 h-100 @(thumbnailAxisCss) @(theme) item_@Model.Item.SystemName.ToLower()"> 198 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor()) col position-relative" data-bs-ride="carousel"> 199 <div class="carousel-inner h-100"> 200 @foreach (MediaViewModel asset in assetsList) 201 { 202 var assetValue = asset.Value; 203 foreach (string format in allSupportedFormats) 204 { 205 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 206 { 207 string activeSlide = assetNumber == 0 ? "active" : ""; 208 209 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 210 @{ 211 string size = "mobile"; 212 213 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 214 215 216 <div class="h-100 @(imageTheme)"> 217 @foreach (string imageFormat in supportedImageFormats) 218 { //Images 219 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 220 { 221 if (product is object) 222 { 223 string productName = product.Name; 224 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 225 string imageLinkPath = Uri.EscapeDataString(imagePath); 226 227 RatioSettings ratioSettings = GetRatioSettings(size); 228 229 var parms = new Dictionary<string, object>(); 230 parms.Add("alt", productName + asset.Keywords); 231 parms.Add("itemprop", "image"); 232 parms.Add("columns", Model.GridRowColumnCount); 233 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 234 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 235 if (!string.IsNullOrEmpty(asset.DisplayName)) 236 { 237 parms.Add("title", asset.DisplayName); 238 } 239 240 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 241 { 242 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 243 } 244 else 245 { 246 parms.Add("cssClass", "mw-100 mh-100"); 247 } 248 249 <a href="@imagePath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 250 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber"> 251 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 252 </div> 253 </a> 254 } 255 } 256 } 257 @foreach (string videoFormat in supportedVideoFormats) 258 { //Videos 259 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 260 { 261 if (Model.Item.GetString("OpenVideoInModal") == "true") 262 { 263 if (product is object) 264 { 265 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 266 267 string productName = product.Name; 268 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 269 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 270 271 RatioSettings ratioSettings = GetRatioSettings(size); 272 273 string type = GetVideoType(asset.Value); 274 275 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : string.Empty; 276 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 277 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : ""; 278 279 280 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 281 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber"> 282 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 283 @if (type != "selfhosted") 284 { 285 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;"> 286 } 287 else 288 { 289 string videoType = Path.GetExtension(asset.Value).ToLower(); 290 291 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 292 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 293 </video> 294 } 295 </div> 296 </div> 297 298 } 299 } 300 else 301 { 302 if (product is object) 303 { 304 var videoParams = GetVideoParams(asset, size); 305 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value}, videoParams); 306 307 } 308 } 309 } 310 } 311 @foreach (string documentFormat in supportedDocumentFormats) 312 { //Documents 313 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 314 { 315 if (product is object) 316 { 317 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 318 319 string productName = product.Name; 320 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 321 string imageLinkPath = Uri.EscapeDataString(imagePath); 322 323 RatioSettings ratioSettings = GetRatioSettings(size); 324 325 var parms = new Dictionary<string, object>(); 326 parms.Add("alt", productName + asset.Keywords); 327 parms.Add("itemprop", "image"); 328 parms.Add("fullwidth", true); 329 parms.Add("columns", Model.GridRowColumnCount); 330 if (!string.IsNullOrEmpty(asset.DisplayName)) 331 { 332 parms.Add("title", asset.DisplayName); 333 } 334 335 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 336 { 337 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 338 } 339 else 340 { 341 parms.Add("cssClass", "mw-100 mh-100"); 342 } 343 344 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")"> 345 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 346 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 347 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 348 { 349 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 350 } 351 </div> 352 </a> 353 } 354 355 } 356 } 357 </div> 358 } 359 360 361 </div> 362 assetNumber++; 363 } 364 } 365 } 366 </div> 367 @if (showBadges) 368 { 369 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 370 @{@RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)} 371 </div> 372 } 373 374 </div> 375 376 @if (totalAssets > 1) 377 { 378 <div class="@(GetThumbnailRowSettingCss()) gap-3" id="SmallScreenImagesThumbnails_@Model.ID"> 379 @foreach (MediaViewModel asset in assetsList) 380 { 381 var assetValue = asset.Value; 382 string assetName = asset.Name; 383 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 384 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : null; 385 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 386 387 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 388 imagePath = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + assetValue.Substring(assetValue.LastIndexOf('/') + 1) + "/mqdefault.jpg" : imagePath; 389 string imagePathThumb = assetValue.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 && imagePath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=180&format=webp" : imagePath : assetValue; 390 391 RatioSettings ratioSettings = GetRatioSettings("desktop"); 392 393 <div class="border outline-none @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer; " data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@thumbnailNumber"> 394 @foreach (string imageFormat in supportedImageFormats) 395 { //Images 396 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 397 { 398 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 w-100 h-100" style="object-fit: contain;"> 399 400 thumbnailNumber++; 401 } 402 } 403 404 @foreach (string videoFormat in supportedVideoFormats) 405 { //Videos 406 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 407 { 408 409 string type = GetVideoType(asset.Value); 410 411 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "mqdefault") : ""; 412 videoScreendumpPath = type == "vimeo" ? string.Empty : videoScreendumpPath; 413 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 414 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 415 416 <div class="icon-4 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 417 418 if (type != "selfhosted") 419 { 420 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@assetTitle" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" /> 421 } 422 else 423 { 424 string videoType = Path.GetExtension(asset.Value).ToLower(); 425 426 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 427 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 428 </video> 429 } 430 431 thumbnailNumber++; 432 } 433 } 434 435 @foreach (string documentFormat in supportedDocumentFormats) 436 { //Documents 437 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 438 { 439 <a href="@Uri.EscapeDataString(assetValue)" class="ratio ratio-4x3 border outline-none" style="cursor: pointer; min-width: 7rem; max-width: 8rem;" download title="@asset.Value"> 440 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 441 { 442 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 443 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 444 </div> 445 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 446 } 447 else 448 { 449 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 450 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 451 </div> 452 } 453 </a> 454 455 thumbnailNumber++; 456 } 457 } 458 </div> 459 } 460 </div> 461 } 462 </div> 463 464 @* Modal with slides *@ 465 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 466 <div class="modal-dialog modal-dialog-centered modal-xl"> 467 <div class="modal-content"> 468 <div class="modal-header visually-hidden"> 469 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 470 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 471 </div> 472 <div class="modal-body p-2 p-lg-3 h-100"> 473 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 474 <div class="carousel-inner h-100 @theme"> 475 @foreach (MediaViewModel asset in assetsList) 476 { 477 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 478 foreach (string supportedFormat in supportedImageFormats.Concat(supportedVideoFormats).ToArray()) 479 { 480 if (assetValue.IndexOf(supportedFormat, StringComparison.OrdinalIgnoreCase) >= 0) 481 { 482 string imagePath = assetValue; 483 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 484 485 var parms = new Dictionary<string, object>(); 486 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 487 parms.Add("fullwidth", true); 488 parms.Add("columns", Model.GridRowColumnCount); 489 490 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 491 @foreach (string imageFormat in supportedImageFormats) 492 { //Images 493 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 494 { 495 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 496 } 497 } 498 499 @foreach (string videoFormat in supportedVideoFormats) 500 { //Videos 501 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 502 { 503 var videoParams = GetVideoParams(asset, "modal"); 504 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams) 505 } 506 } 507 </div> 508 modalAssetNumber++; 509 } 510 } 511 } 512 <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 513 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 514 <span class="visually-hidden">@Translate("Previous")</span> 515 </button> 516 <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 517 <span class="carousel-control-next-icon" aria-hidden="true"></span> 518 <span class="visually-hidden">@Translate("Next")</span> 519 </button> 520 </div> 521 </div> 522 </div> 523 </div> 524 </div> 525 </div> 526 } 527 else if (Pageview.IsVisualEditorMode) 528 { 529 RatioSettings ratioSettings = GetRatioSettings("desktop"); 530 531 <div class="h-100 @theme"> 532 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 533 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;"> 534 </div> 535 </div> 536 } 537 } 538 else if (Pageview.IsVisualEditorMode) 539 { 540 <div class="alert alert-dark m-0">@Translate("No products available")</div> 541 } 542 543 544 545

Error executing template "Designs/Swift/Paragraph/Swift_ProductPrice.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at CompiledRazorTemplates.Dynamic.RazorEngine_3db3d869fd8b4fe9814ef429fac18861.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 4 @{ 5 ProductViewModel product = null; 6 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 7 { 8 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 9 } 10 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 11 { 12 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 13 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 14 15 if (productList?.Products is object) 16 { 17 product = productList.Products[0]; 18 } 19 } 20 21 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", ""); 22 bool anonymousUser = Pageview.User == null; 23 bool isErpConnectionDown = !Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsWebServiceConnectionAvailable"]); 24 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHidePrices") && isErpConnectionDown; 25 26 bool productIsDiscontinued = product is object && product.Discontinued; 27 bool doNotShowPriceIfProductIsDiscontinued = Model.Item.GetBoolean("DoNotShowPriceIfProductIsDiscontinued"); 28 var isDiscontinued = productIsDiscontinued && doNotShowPriceIfProductIsDiscontinued; 29 } 30 31 @if (product is object && !hidePrice && !isDiscontinued) { 32 bool showInformativePrice = Model.Item.GetBoolean("ShowInformativePrice"); 33 string unitId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("UnitId")) ? Dynamicweb.Context.Current.Request.Form.Get("UnitId") : string.Empty; 34 35 string priceFontSize = Model.Item.GetRawValueString("PriceSize", "fs-2"); 36 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); 37 string layout = Model.Item.GetRawValueString("Layout", "horizontal"); 38 string textAlign = horizontalAlign == "center" ? "text-center" : string.Empty; 39 textAlign = horizontalAlign == "end" ? "text-end" : textAlign; 40 41 horizontalAlign = horizontalAlign == "center" && layout == "horizontal" ? "justify-content-center" : horizontalAlign; 42 horizontalAlign = horizontalAlign == "end" && layout == "horizontal" ? "justify-content-end" : horizontalAlign; 43 horizontalAlign = horizontalAlign == "center" && layout == "vertical" ? "align-items-center" : horizontalAlign; 44 horizontalAlign = horizontalAlign == "end" && layout == "vertical" ? "align-items-end" : horizontalAlign; 45 46 string flexDirection = layout == "horizontal" ? string.Empty : "flex-column"; 47 string flexGap = layout == "horizontal" ? "gap-3" : string.Empty; 48 string order = layout == "horizontal" ? string.Empty : "order-2"; 49 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? "theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 50 theme = GetViewParameter("theme") != null ? GetViewParameterString("theme") : theme; 51 52 string contentPadding = Model.Item.GetRawValueString("ContentPadding", ""); 53 contentPadding = contentPadding == "none" ? "p-0" : contentPadding; 54 contentPadding = contentPadding == "small" ? "p-1 px-md-2 py-md-1" : contentPadding; 55 contentPadding = contentPadding == "large" ? "p-2 px-md-3 py-md-2" : contentPadding; 56 57 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower(); 58 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat); 59 60 string priceMin = ""; 61 string priceMax = ""; 62 63 string liveInfoClass = ""; 64 string productInfoFeed = ""; 65 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 66 67 68 69 //Capo custom - products with QuoteRequest_[CountryCode] = true should not have price rendered and the buy button should be replaced with an email form function 70 var areaEcomCountryCode = Dynamicweb.Frontend.PageView.Current()?.Area?.EcomCountryCode; 71 bool isQuoteRequest = false; 72 if (areaEcomCountryCode != null && product.ProductFields.TryGetValue("QuoteRequest_" + areaEcomCountryCode?.ToUpper(), out var quoteRequestValue)) 73 { 74 75 if (quoteRequestValue != null && bool.TryParse(quoteRequestValue.ToString(), out bool parsedValue)) 76 { 77 isQuoteRequest = parsedValue; 78 } 79 } 80 81 82 83 if (isLazyLoadingForProductInfoEnabled) 84 { 85 if (Dynamicweb.Context.Current.Items.Contains("ProductInfoFeed")) 86 { 87 productInfoFeed = Dynamicweb.Context.Current.Items["ProductInfoFeed"]?.ToString(); 88 if (!string.IsNullOrEmpty(productInfoFeed)) 89 { 90 productInfoFeed = $"data-product-info-feed=\"{productInfoFeed}\""; 91 } 92 } 93 liveInfoClass = "js-live-info"; 94 } 95 96 if (!isQuoteRequest) 97 { 98 <div class="@textAlign @liveInfoClass item_@Model.Item.SystemName.ToLower()" data-product-id="@product.Id" data-variant-id="@product.VariantId" @productInfoFeed> 99 @if (showInformativePrice && product.PriceInformative.Price != 0) 100 { 101 <div class="opacity-50"> 102 <span>@Translate("RRP") </span> 103 <span class="text-decoration-line-through text-price">@product.PriceInformative.PriceFormatted</span> 104 </div> 105 } 106 <div class="@priceFontSize m-0 d-flex flex-wrap @flexDirection @flexGap @horizontalAlign" style="row-gap: 0 !important" itemprop="offers" itemscope itemtype="https://schema.org/Offer"> 107 <span itemprop="priceCurrency" content="@product.Price.CurrencyCode" class="d-none"></span> 108 109 110 @if (showPricesWithVat == "false" && !neverShowVat) 111 { 112 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 113 { 114 <span itemprop="price" content="" class="d-none"></span> 115 <span class="text-decoration-line-through js-text-decoration-line-through opacity-75 me-3 text-price js-text-price d-none" data-show-if="LiveProductInfo.product.Price.Price != LiveProductInfo.product.PriceBeforeDiscount.Price"></span> 116 } 117 else 118 { 119 string beforePrice = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).PriceBeforeDiscount.PriceWithoutVatFormatted : product.PriceBeforeDiscount.PriceWithoutVatFormatted; 120 121 <span itemprop="price" content="@product.Price.PriceWithoutVat.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)" class="d-none"></span> 122 if (product.Price.Price != product.PriceBeforeDiscount.Price) 123 { 124 <span class="text-decoration-line-through opacity-75 @order">@beforePrice</span> 125 } 126 } 127 } 128 else 129 { 130 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 131 { 132 <span itemprop="price" content="" class="d-none"></span> 133 <span class="text-decoration-line-through js-text-decoration-line-through opacity-75 me-3 text-price js-text-price d-none" data-show-if="LiveProductInfo.product.Price.Price != LiveProductInfo.product.PriceBeforeDiscount.Price"></span> 134 } 135 else 136 { 137 string beforePrice = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).PriceBeforeDiscount.PriceFormatted : product.PriceBeforeDiscount.PriceFormatted; 138 139 <span itemprop="price" content="@product.Price.Price.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)" class="d-none"></span> 140 141 if (product.Price.Price != product.PriceBeforeDiscount.Price) 142 { 143 <span class="text-decoration-line-through opacity-75 @order"> 144 <span class="text-price">@beforePrice</span> 145 </span> 146 } 147 } 148 } 149 150 @if (showPricesWithVat == "false" && !neverShowVat) 151 { 152 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 153 { 154 <span class="text-price js-text-price"> 155 <span class="spinner-border" role="status"></span> 156 </span> 157 } 158 else 159 { 160 string price = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).Price.PriceWithoutVatFormatted : product.Price.PriceWithoutVatFormatted; 161 162 if (product?.VariantInfo?.VariantInfo != null) 163 { 164 priceMin = product?.VariantInfo?.PriceMin?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithoutVatFormatted : ""; 165 priceMax = product?.VariantInfo?.PriceMax?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithoutVatFormatted : ""; 166 } 167 if (priceMin != priceMax) 168 { 169 price = priceMin + " - " + priceMax; 170 } 171 else if (priceMax != "") 172 { 173 price = priceMax; 174 } 175 176 <span class="@theme @contentPadding"> 177 <span class="text-price">@price</span> 178 </span> 179 } 180 } 181 else 182 { 183 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 184 { 185 <span class="text-price js-text-price"> 186 <span class="spinner-border" role="status"></span> 187 </span> 188 } 189 else 190 { 191 string price = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).Price.PriceFormatted : product.Price.PriceFormatted; 192 193 if (product?.VariantInfo?.VariantInfo != null) 194 { 195 priceMin = product?.VariantInfo?.PriceMin?.PriceFormatted != null ? product.VariantInfo.PriceMin.PriceFormatted : ""; 196 priceMax = product?.VariantInfo?.PriceMax?.PriceFormatted != null ? product.VariantInfo.PriceMax.PriceFormatted : ""; 197 } 198 if (priceMin != priceMax) 199 { 200 price = priceMin + " - " + priceMax; 201 } 202 else if (priceMax != "") 203 { 204 price = priceMax; 205 } 206 <span class="@theme @contentPadding"> 207 <span class="text-price">@price</span> 208 </span> 209 } 210 } 211 212 @* Stock state for Schema.org, start *@ 213 @{ 214 Uri url = Dynamicweb.Context.Current.Request.Url; 215 } 216 217 <link itemprop="url" href="@url"> 218 219 @{ 220 bool IsNeverOutOfStock = product.NeverOutOfstock; 221 } 222 223 @if (IsNeverOutOfStock) 224 { 225 <span itemprop="availability" class="d-none">@Translate("Available in stock")</span> 226 } 227 else 228 { 229 if (product.StockLevel > 0) 230 { 231 <span itemprop="availability" class="d-none">InStock</span> 232 } 233 else 234 { 235 <span itemprop="availability" class="d-none">OutOfStock</span> 236 } 237 } 238 @* Stock state for Schema.org, stop *@ 239 240 </div> 241 242 @if (showPricesWithVat == "false" && !neverShowVat) 243 { 244 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 245 { 246 <small class="opacity-85 fst-normal js-text-price-with-vat d-none" data-suffix="@Translate("Incl. VAT")"></small> 247 } 248 else 249 { 250 string price = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).Price.PriceWithVatFormatted : product.Price.PriceWithVatFormatted; 251 252 if (product?.VariantInfo?.VariantInfo != null) 253 { 254 priceMin = product?.VariantInfo?.PriceMin?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithVatFormatted : ""; 255 priceMax = product?.VariantInfo?.PriceMax?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithVatFormatted : ""; 256 } 257 if (priceMin != priceMax) 258 { 259 price = priceMin + " - " + priceMax; 260 } 261 else if (priceMax != "") 262 { 263 price = priceMax; 264 } 265 <small class="opacity-85 fst-normal">@price @Translate("Incl. VAT")</small> 266 } 267 } 268 </div> 269 } 270 } 271 else if (Pageview.IsVisualEditorMode) 272 { 273 <div class="alert alert-dark m-0" role="alert"> 274 <span>@Translate("No products available")</span> 275 </div> 276 } 277
Error executing template "Designs/Swift/Paragraph/Swift_ProductAddToCart.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at CompiledRazorTemplates.Dynamic.RazorEngine_55a5f5da6f0b40888ba8362f00e985b2.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Core.Encoders 4 @using System.Globalization 5 6 @functions { 7 string DoubleToString(double? value) 8 { 9 if (value.HasValue) 10 { 11 return value.Value.ToString(CultureInfo.InvariantCulture); 12 } 13 return null; 14 } 15 } 16 17 @{ 18 ProductViewModel product = null; 19 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 20 { 21 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 22 } 23 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 24 { 25 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 26 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 27 28 if (productList?.Products is object) 29 { 30 product = productList.Products[0]; 31 } 32 } 33 34 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", ""); 35 bool anonymousUser = Pageview.User == null; 36 bool isErpConnectionDown = !Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsWebServiceConnectionAvailable"]); 37 bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHideAddToCart") && isErpConnectionDown; 38 hideAddToCart = Pageview.IsVisualEditorMode ? false : hideAddToCart; 39 } 40 41 @if (product is object && !hideAddToCart) 42 { 43 string contextCart = Model.Item.GetRawValueString("ContextCart", ""); 44 45 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); 46 horizontalAlign = horizontalAlign == "center" ? "justify-content-center" : horizontalAlign; 47 horizontalAlign = horizontalAlign == "end" ? "justify-content-end" : horizontalAlign; 48 horizontalAlign = horizontalAlign == "full" ? "" : horizontalAlign; 49 50 bool favoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowAddToFavorites")) ? Model.Item.GetBoolean("ShowAddToFavorites") : false; 51 bool quantitySelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowQuantitySelector")) ? Model.Item.GetBoolean("ShowQuantitySelector") : false; 52 bool unitsSelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowUnitsSelector")) ? Model.Item.GetBoolean("ShowUnitsSelector") : false; 53 bool hideInventory = !string.IsNullOrEmpty(Model.Item.GetString("HideInventory")) ? Model.Item.GetBoolean("HideInventory") : false; 54 bool hideStockState = !string.IsNullOrEmpty(Model.Item.GetString("HideStockState")) ? Model.Item.GetBoolean("HideStockState") : false; 55 56 string buttonSize = Model.Item.GetRawValueString("ButtonSize", "regular"); 57 string inputSize = string.Empty; 58 59 switch (buttonSize) 60 { 61 case "small": 62 inputSize = " input-group-sm"; 63 buttonSize = " btn-sm"; 64 break; 65 case "regular": 66 buttonSize = string.Empty; 67 break; 68 case "large": 69 inputSize = " input-group-lg"; 70 buttonSize = " btn-lg"; 71 break; 72 } 73 74 string iconPath = "/Files/icons/"; 75 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService")); 76 if (!url.Contains("LayoutTemplate")) 77 { 78 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml"; 79 } 80 81 string whenVariantsExist = Model.Item.GetRawValueString("WhenVariantsExist", "hide"); 82 string flexFill = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "flex-fill" : ""; 83 string fullWidth = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "w-100" : ""; 84 string addToCartIcon = Model.Item.GetRawValueString("Icon", iconPath + "shopping-cart.svg"); 85 string addToCartLabel = !addToCartIcon.Contains("_none") ? $"<span class=\"icon-2\">{ReadFile(addToCartIcon)}</span>" : ""; 86 addToCartLabel += !addToCartIcon.Contains("_none") && !Model.Item.GetBoolean("HideButtonText") ? " " : ""; 87 addToCartLabel += !Model.Item.GetBoolean("HideButtonText") ? $"<span class=\"d-none d-md-inline\">{Translate("Add to cart")}</span><span class=\"d-inline d-md-none\">{Translate("Add")}</span>" : ""; 88 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 89 90 bool userHasPendingQuote = Dynamicweb.Ecommerce.Common.Context.Cart != null && Dynamicweb.Ecommerce.Common.Context.Cart.IsQuote; 91 92 if (product.VariantInfo.VariantInfo == null || whenVariantsExist == "disable") 93 { 94 string unitId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("UnitId")) ? Dynamicweb.Context.Current.Request.Form.Get("UnitId") : product.DefaultUnitId; 95 if (string.IsNullOrEmpty(unitId) && product?.UnitOptions != null) 96 { 97 if (product.UnitOptions.FirstOrDefault<UnitOptionViewModel>() != null) 98 { 99 unitId = product.UnitOptions.FirstOrDefault<UnitOptionViewModel>().Id; 100 } 101 } 102 103 double? stepQty = product.PurchaseQuantityStep > 0 ? product.PurchaseQuantityStep : 1; 104 double? minQty = product.PurchaseMinimumQuantity > 0 ? product.PurchaseMinimumQuantity : 1; 105 double? valueQty = minQty > stepQty ? minQty : stepQty; 106 string disableAddToCart = null; 107 double? maxQty = null; 108 109 110 disableAddToCart = whenVariantsExist == "disable" && product.VariantInfo.VariantInfo != null && string.IsNullOrEmpty(product.VariantId) ? "disabled" : disableAddToCart; 111 disableAddToCart = product.Discontinued ? "disabled" : disableAddToCart; 112 113 //Capo custom - all products that are equal to SalesBlocked_[CountryCode] = true should not be possible to purchase (disableAddToCart = "disabled") 114 var areaEcomCountryCode = Dynamicweb.Frontend.PageView.Current()?.Area?.EcomCountryCode; 115 bool isSalesBlocked = false; 116 if (areaEcomCountryCode != null && product.ProductFields.TryGetValue("SalesBlocked_" + areaEcomCountryCode?.ToUpper(), out var salesBlockedValue) ) 117 { 118 119 if (salesBlockedValue != null && bool.TryParse(salesBlockedValue.ToString(), out bool parsedValue)) 120 { 121 isSalesBlocked = parsedValue; 122 } 123 124 if (isSalesBlocked && disableAddToCart != "disabled") 125 { 126 disableAddToCart = "disabled"; 127 } 128 } 129 130 //Capo custom - products with QuoteRequest_[CountryCode] = true should not have price rendered and the buy button should be replaced with an email form function 131 bool isQuoteRequest = false; 132 if (areaEcomCountryCode != null && product.ProductFields.TryGetValue("QuoteRequest_" + areaEcomCountryCode?.ToUpper(), out var quoteRequestValue)) 133 { 134 135 if (quoteRequestValue != null && bool.TryParse(quoteRequestValue.ToString(), out bool parsedValue)) 136 { 137 isQuoteRequest = parsedValue; 138 } 139 } 140 141 if (unitsSelector && product.UnitOptions.Count > 0) 142 { 143 <form method="post" action="/Default.aspx?ID=@(Pageview.Page.ID)&ProductId=@product.Id" id="UnitSelectorForm_@(product.Id)_@(product.VariantId.Replace(".", "_"))_@Model.ID"> 144 <input type="hidden" name="redirect" value="false"> 145 <input type="hidden" name="VariantID" value="@product.VariantId"> 146 <input type="hidden" name="UnitID" class="js-unit-id" value="@unitId"> 147 </form> 148 } 149 <div class="d-flex @horizontalAlign @fullWidth js-input-group item_@Model.Item.SystemName.ToLower()"> 150 @if (!anonymousUser && favoritesSelector) 151 { 152 @RenderPartial("Components/ToggleFavorite.cshtml", product) 153 } 154 @if (isQuoteRequest) 155 { 156 <div class="quote-request"> 157 <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#quoteRequest">@Translate("Quote request")</button> 158 159 <div class="modal fade" tabindex="-1" role="dialog" id="quoteRequest"> 160 <div class="modal-dialog" role="document"> 161 <div class="modal-content"> 162 <div class="modal-header"> 163 <h5 class="modal-title" id="exampleModalLiveLabel">@Translate("Quote request")</h5> 164 <button type="button" class="close" data-bs-dismiss="modal" aria-label="Close" style="background: transparent; border: 0; padding: 0;"> 165 <span class="icon-4">@ReadFile(iconPath + "x.svg")</span> 166 </button> 167 </div> 168 169 170 <script> 171 document.addEventListener("DOMContentLoaded", function() { 172 // 1. Find tag with itemprop="name" and populate id="Productname" 173 var nameElement = document.querySelector('[itemprop="name"]'); 174 var nameInput = document.getElementById('Productname'); 175 if (nameElement && nameInput) { 176 nameInput.value = nameElement.innerHTML.trim(); 177 } 178 179 // 2. Find tag with itemprop="sku" and populate id="Productnumber" 180 var skuElement = document.querySelector('[itemprop="sku"]'); 181 var skuInput = document.getElementById('Productnumber'); 182 if (skuElement && skuInput) { 183 skuInput.value = skuElement.innerHTML.trim(); 184 } 185 186 // 3. Take current browser URL and populate id="ProductURL" 187 var urlInput = document.getElementById('ProductURL'); 188 if (urlInput) { 189 urlInput.value = window.location.href; 190 } 191 }); 192 </script> 193 194 195 <div class="modal-body"> 196 @{ 197 string moduleOutput = Model.GetModuleOutput(); 198 199 if (!string.IsNullOrWhiteSpace(moduleOutput)) 200 { 201 <div>@moduleOutput</div> 202 } 203 } 204 </div> 205 <div class="modal-footer"> 206 <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">@Translate("Close")</button> 207 </div> 208 </div> 209 </div> 210 </div> 211 </div> 212 } 213 else 214 { 215 <form method="post" action="@url" class="@fullWidth" style="z-index: 1"> 216 <input type="hidden" name="OrderContext" value="@contextCart"> 217 <input type="hidden" name="minicartid" value="@contextCart"> 218 <input type="hidden" name="redirect" value="false"> 219 <input type="hidden" name="ProductId" value="@product.Id"> 220 <input type="hidden" name="ProductName" value="@HtmlEncoder.HtmlEncode(product.Name)"> 221 <input type="hidden" name="ProductVariantName" value="@product.VariantName"> 222 <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code"> 223 <input type="hidden" name="ProductPrice" value="@product.Price.ToStringInvariant()"> 224 <input type="hidden" name="ProductDiscount" value="@product.Discount.ToStringInvariant()"> 225 <input type="hidden" name="ProductReferer" value="component_ProductAddToCart"> 226 <input type="hidden" name="cartcmd" value="add"> 227 <input type="submit" class="d-none" onclick="event.preventDefault(); swift.Cart.Update(event)"> @* Fix for enterKey should not redirect to minicart page *@ 228 229 @if (!string.IsNullOrEmpty(product.VariantId)) 230 { 231 <input type="hidden" name="VariantId" value="@product.VariantId"> 232 } 233 234 <template class="js-step-quantity-warning"> 235 <div class="modal-header"> 236 <h1 class="modal-title fs-5">@Translate("The quantity is not valid")</h1> 237 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 238 </div> 239 <div class="modal-body"> 240 @Translate("Please select a quantity that is dividable by") @stepQty 241 </div> 242 </template> 243 244 245 <template class="js-min-quantity-warning"> 246 <div class="modal-header"> 247 <h1 class="modal-title fs-5">@Translate("The product could not be added to the cart")</h1> 248 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 249 </div> 250 <div class="modal-body"> 251 @Translate("The quantity is not valid. You must buy at least") @product.PurchaseMinimumQuantity 252 </div> 253 </template> 254 255 <template class="js-value-missing-warning"> 256 <div class="modal-header"> 257 <h1 class="modal-title fs-5">@Translate("No amount specified")</h1> 258 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 259 </div> 260 <div class="modal-body"> 261 @Translate("Specify an amount to add to the cart") 262 </div> 263 </template> 264 265 266 @if (userHasPendingQuote) 267 { 268 <input type="hidden" name="PendingQuote" value="true"> 269 270 <template class="js-pending-quote-notice"> 271 <div class="modal-header"> 272 <h1 class="modal-title fs-5">@Translate("Pending Quote")</h1> 273 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="@Translate("Close")"></button> 274 </div> 275 <div class="modal-body"> 276 @Translate("You need to complete your current quote or empty the cart before adding this product to cart.") 277 </div> 278 </template> 279 } 280 281 @if (quantitySelector || (!anonymousUser && product.VariantInfo.VariantInfo != null) || (!anonymousUser && favoritesSelector)) 282 { 283 <input type="hidden" id="Unit_@(product.Id)_@product.VariantId.Replace(".", "_")" name="UnitID" value="@unitId" /> 284 } 285 286 <div class="d-flex flex-row w-100"> 287 @if (!quantitySelector) 288 { 289 <input id="Quantity_@(product.Id)_@product.VariantId.Replace(".", "_")" class="swift_quantity_field" name="Quantity" value="@valueQty" type="hidden" @disableAddToCart> 290 } 291 292 @if (unitsSelector && product.UnitOptions.Count > 0) 293 { 294 string selectedUnitName = !string.IsNullOrEmpty(unitId) && product?.UnitOptions != null ? unitId : product.UnitOptions.FirstOrDefault<UnitOptionViewModel>().Name; 295 296 foreach (var unitOption in product.UnitOptions) 297 { 298 if (unitOption.Id == unitId) 299 { 300 selectedUnitName = unitOption.Name; 301 } 302 } 303 304 <div class="d-flex flex-column gap-2 w-100"> 305 <div class="input-group input-primary-button-group flex-nowrap@(inputSize)"> 306 307 @if (quantitySelector) 308 { 309 <input id="Quantity_@(product.Id)_@product.VariantId.Replace(".", "_")" name="Quantity" value="@DoubleToString(valueQty)" step="@DoubleToString(stepQty)" min="@DoubleToString(minQty)" max="@DoubleToString(maxQty)" class="form-control swift_quantity-field" style="min-width: 60px; max-width: 100px; z-index: 1" type="number" @disableAddToCart> 310 } 311 312 <button class="btn btn-secondary @flexFill dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> 313 @selectedUnitName 314 </button> 315 316 <ul class="dropdown-menu swift_unit-field"> 317 @foreach (var unitOption in product.UnitOptions) 318 { 319 var selectedUnit = unitOption.Id == unitId ? "selected" : ""; 320 321 <li> 322 <button type="button" class="btn dropdown-item" data-value="@unitOption.Id" onclick="document.querySelector('#UnitSelectorForm_@(product.Id)_@(product.VariantId.Replace(".", "_"))_@Model.ID').querySelector('.js-unit-id').value = this.getAttribute('data-value'); 323 document.querySelector('#Unit_@(product.Id)_@product.VariantId.Replace(".", "_")').value = this.getAttribute('data-value'); 324 swift.PageUpdater.Update(document.querySelector('#UnitSelectorForm_@(product.Id)_@(product.VariantId.Replace(".", "_"))_@Model.ID'))"> 325 <span>@unitOption.Name</span> 326 <span> 327 @if (unitOption.StockLevel > 0 || unitOption.NeverOutOfStock) 328 { 329 if (!Model.Item.GetBoolean("HideInventory") && !unitOption.NeverOutOfStock) 330 { 331 <span class="small text-success">@unitOption.StockLevel @Translate("In stock")</span> 332 } 333 else 334 { 335 <span class="small text-success">@Translate("In stock")</span> 336 } 337 } 338 else 339 { 340 <span class="small text-danger">@Translate("Out of Stock")</span> 341 } 342 </span> 343 </button> 344 </li> 345 } 346 </ul> 347 </div> 348 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary @(buttonSize) js-add-to-cart-button" style="white-space: nowrap" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)_@Pageview.CurrentParagraph.ID"> 349 @if (!Model.Item.GetBoolean("HideButtonText")) 350 { 351 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2"> 352 @addToCartLabel 353 </span> 354 } 355 else 356 { 357 @addToCartLabel 358 } 359 </button> 360 </div> 361 } 362 else 363 { 364 <div class="input-group input-primary-button-group flex-nowrap@(inputSize)"> 365 @if (quantitySelector) 366 { 367 <input id="Quantity_@(product.Id)_@product.VariantId.Replace(".", "_")" name="Quantity" value="@DoubleToString(valueQty)" step="@DoubleToString(stepQty)" min="@DoubleToString(minQty)" max="@DoubleToString(maxQty)" class="form-control swift_quantity-field" style="min-width: 60px; max-width: 100px; z-index: 1" type="number" @disableAddToCart> 368 } 369 370 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary @(buttonSize) @flexFill js-add-to-cart-button" style="white-space: nowrap" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)_@Pageview.CurrentParagraph.ID"> 371 @if (!Model.Item.GetBoolean("HideButtonText")) 372 { 373 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2"> 374 @addToCartLabel 375 </span> 376 } 377 else 378 { 379 @addToCartLabel 380 } 381 </button> 382 </div> 383 } 384 </div> 385 </form> 386 } 387 </div> 388 } 389 else if (whenVariantsExist == "modal") 390 { 391 string ButtonShape = Model.Item.GetRawValueString("VariantButtonShape", "square"); 392 string buttonAspectRatio = Model.Item.GetRawValueString("VariantImageAspectRatio", "56%"); 393 394 string buttonText = Translate("Select"); 395 string variantId = !string.IsNullOrWhiteSpace(product.VariantId) ? product.VariantId : product.DefaultVariantId; 396 397 string variantSelectorServicePageId = !string.IsNullOrEmpty(Model.Item.GetString("VariantSelectorServicePageId")) ? Model.Item.GetLink("VariantSelectorServicePageId").PageId.ToString() : ""; 398 variantSelectorServicePageId = variantSelectorServicePageId != "" ? variantSelectorServicePageId : GetPageIdByNavigationTag("VariantSelectorService").ToString(); 399 400 <div class="d-flex @horizontalAlign w-100 item_@Model.Item.SystemName.ToLower()"> 401 @if (!anonymousUser && favoritesSelector) 402 { 403 @RenderPartial("Components/ToggleFavorite.cshtml", product) 404 } 405 <form action="/Default.aspx?ID=@variantSelectorServicePageId" data-response-target-element="DynamicModalContent" data-preloader="inline" style="z-index: 1" class="@fullWidth"> 406 <input type="hidden" name="ProductID" value="@product.Id"> 407 <input type="hidden" name="VariantID" value="@variantId"> 408 <input type="hidden" name="QuantitySelector" value="@quantitySelector.ToString()"> 409 <input type="hidden" name="HideInventory" value="@hideInventory.ToString()"> 410 <input type="hidden" name="HideStockState" value="@hideStockState.ToString()"> 411 <input type="hidden" name="ButtonLayout" value="@ButtonShape"> 412 <input type="hidden" name="ButtonAspectRatio" value="@buttonAspectRatio"> 413 <input type="hidden" name="VariantSelectorServicePage" value="@variantSelectorServicePageId"> 414 <input type="hidden" name="ViewType" value="ModalContent"> 415 @if (isLazyLoadingForProductInfoEnabled) 416 { 417 @* If lazy loading is enabled, bypass it because we're loading a modal window, so render everything as if it was server-side *@ 418 <input type="hidden" name="getproductinfo" value="true"> 419 } 420 <button type="button" onclick="swift.PageUpdater.Update(event)" class="btn btn-primary@(buttonSize) @fullWidth" title="@Translate("Select")" data-bs-toggle="modal" data-bs-target="#DynamicModal" id="OpenVariantSelectorModal@(product.Id)_@Pageview.CurrentParagraph.ID">@buttonText</button> 421 </form> 422 </div> 423 } 424 } 425 else if (Pageview.IsVisualEditorMode) 426 { 427 <div class="alert alert-dark m-0">@Translate("No products available")</div> 428 } 429

Lisätiedot

Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable.cshtml"
System.ArgumentNullException: Value cannot be null. (Parameter 'source')
   at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_e81033aa334c4e0498261a8972c6bca6.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using System.Text.RegularExpressions; 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string[] supportedImageFormats { get; set; } 9 public string[] supportedVideoFormats { get; set; } 10 public string[] supportedDocumentFormats { get; set; } 11 public string[] allSupportedFormats { get; set; } 12 13 public class RatioSettings 14 { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings() 22 { 23 var ratioSettings = new RatioSettings(); 24 25 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 26 ratio = ratio != "0" ? ratio : ""; 27 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 28 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 29 30 ratioSettings.Ratio = ratio; 31 ratioSettings.CssClass = cssClass; 32 ratioSettings.CssVariable = cssVariable; 33 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 34 35 return ratioSettings; 36 } 37 38 public Dictionary<string, object> GetVideoParams(MediaViewModel asset, string size) 39 { 40 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 41 string type = GetVideoType(asset.Value); 42 bool openInModal = Model.Item.GetString("OpenVideoInModal") == "true" ? true : false; 43 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 44 45 var videoParams = new Dictionary<string, object>(); 46 videoParams.Add("AssetName", asset.Name); 47 videoParams.Add("AssetVideoType", type); 48 videoParams.Add("AssetDisplayName", asset.DisplayName); 49 videoParams.Add("OpenVideoInModal", openInModal); 50 videoParams.Add("VideoAutoPlay", autoPlay); 51 videoParams.Add("Size", size); 52 videoParams.Add("Id", Model.ID); 53 return videoParams; 54 55 } 56 57 public string GetVideoType(string assetValue) 58 { 59 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : string.Empty; 60 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 61 type = string.IsNullOrEmpty(type) ? "selfhosted" : type; 62 return type; 63 } 64 65 public string GetYoutubeScreenDump(string assetValue) 66 { 67 var regex = new Regex(@"(?:youtube\.com\/.*[\?&]v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]+)(?:\?.*)?"); 68 Match match = regex.Match(assetValue); 69 string videoId = match.Success ? match.Groups[1].Value : string.Empty; 70 string youtubeThumbnail = $"https://img.youtube.com/vi/{videoId}/mqdefault.jpg"; 71 return youtubeThumbnail; 72 } 73 74 } 75 76 @{ 77 @* Get the product data *@ 78 ProductViewModel product = null; 79 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 80 { 81 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 82 } 83 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 84 { 85 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 86 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 87 88 if (productList?.Products is object) 89 { 90 product = productList.Products[0]; 91 } 92 } 93 } 94 95 @if (product is object) 96 { 97 @* Supported formats *@ 98 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 99 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 100 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", ".pptx", ".igs", ".ipt", ".sat", ".stp", ".dwg", ".dxf", ".dwf" }; 101 102 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 103 104 @* Collect the assets *@ 105 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>(); 106 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 107 108 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 109 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 110 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 111 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 112 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 113 114 assetsList = assetsList.Union(assetsImages); 115 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 116 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 117 118 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 119 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 120 121 int totalAssets = 0; 122 if (showOnlyPrimaryImage == false) { 123 foreach (MediaViewModel asset in assetsList) { 124 var assetValue = asset.Value; 125 foreach (string format in allSupportedFormats) { 126 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 127 totalAssets++; 128 } 129 } 130 } 131 } 132 133 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 134 { 135 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 136 totalAssets = 1; 137 } 138 139 int videoNumber = 0; 140 141 @* Layout settings *@ 142 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 143 spacing = spacing == "none" ? "p-0" : spacing; 144 spacing = spacing == "small" ? "p-3" : spacing; 145 spacing = spacing == "large" ? "p-5" : spacing; 146 147 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 148 149 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 150 151 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 152 int modalVideoNumber = 0; 153 154 // CAPO CUSTOM 155 // Mappar varje asset.Value (url) till dess kategori (Name/SystemName) 156 var categoryLookup = product.AssetCategories 157 .SelectMany(cat => cat.Assets.Select(a => new { 158 Key = (a.Value ?? "").ToLowerInvariant(), 159 Name = cat.Name, 160 SystemName = cat.SystemName 161 })) 162 .GroupBy(x => x.Key) 163 .ToDictionary(g => g.Key, g => new { g.First().Name, g.First().SystemName }); 164 165 var imagePatternSet = new HashSet<string>( 166 (product.ImagePatternImages ?? Enumerable.Empty<Dynamicweb.Ecommerce.ProductCatalog.MediaViewModel>()) 167 .Select(a => (a?.Value ?? "").ToLowerInvariant()) 168 ); 169 var defaultImageUrl = (product.DefaultImage?.Value ?? "").ToLowerInvariant(); 170 171 // Hjälpare: ge tillbaka gruppnamn för en asset 172 Func<Dynamicweb.Ecommerce.ProductCatalog.MediaViewModel, string> resolveGroup = a => 173 { 174 var key = (a?.Value ?? "").ToLowerInvariant(); 175 if (string.IsNullOrWhiteSpace(key)) return Translate("Uncategorized"); 176 177 if (categoryLookup.TryGetValue(key, out var g)) 178 return g.Name; 179 180 if (imagePatternSet.Contains(key)) 181 return Translate("Image pattern"); 182 183 if (key == defaultImageUrl) 184 return Translate("Default image"); 185 186 return Translate("Uncategorized"); 187 }; 188 189 Func<Dynamicweb.Ecommerce.ProductCatalog.MediaViewModel, bool> shouldRender = a => 190 { 191 var user = Dynamicweb.Security.UserManagement.User.GetCurrentUser(); 192 if (user == null) 193 { 194 return false; 195 } 196 int trustedPartnerGroupdId = 30932; // NOTE: Should this be hardcoded? 197 int bcInternalGroupId = 1211; // NOTE: Should this be hardcoded? 198 int bcExternalGroupId = 10; // NOTE: Should this be hardcoded? 199 bool show = false; 200 201 string assetGroupName; 202 var key = (a?.Value ?? "").ToLowerInvariant(); 203 if (categoryLookup.TryGetValue(key, out var g)) 204 assetGroupName = g.SystemName; 205 else 206 assetGroupName = ""; 207 208 foreach (var userGroup in user.Groups) 209 { 210 bool isExternal = userGroup.ID == bcExternalGroupId || userGroup.ParentGroupID == bcExternalGroupId; 211 if ((assetGroupName == "ProductSheet" 212 || assetGroupName == "Video" 213 || assetGroupName == "UserManual" 214 || assetGroupName == "AssemblyInstructions" 215 || assetGroupName == "CareInstructions") 216 && isExternal) 217 return true; 218 219 if (assetGroupName == "DWG_Drawings" && userGroup.ID == trustedPartnerGroupdId) 220 return true; 221 222 if (assetGroupName == "Drawings" && (userGroup.ID == bcInternalGroupId || userGroup.ParentGroupID == bcInternalGroupId)) 223 return true; 224 } 225 226 return false; 227 }; 228 229 // Remove assets that the user does not have access to from beeing part of the rendering at all 230 // Makes sure they are not rendered at all in the source code to avoid a security hole 231 // Also avoid the problem that there is an empty list header rendered in case there are assets but the user does 232 // not have access to any of them 233 ICollection<MediaViewModel> assetsListToRender = assetsList.ToList(); 234 @* @foreach (MediaViewModel asset in assetsList) 235 { 236 if (!shouldRender(asset)) 237 { 238 assetsListToRender.Remove(asset); 239 } 240 } *@ 241 assetsList = assetsListToRender.ToList(); 242 243 244 @* Get assets from selected categories or get all assets *@ 245 if (totalAssets != 0 && assetsList.Any()) 246 { 247 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 248 @if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 249 { 250 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 251 <h3 class="@titleFontSize mb-3">@Model.Item.GetString("Title")</h3> 252 } 253 254 <div class="table-responsive"> 255 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 256 <thead> 257 <tr> 258 @if (!hideThumbnails) 259 { 260 <th style="width:60px">&nbsp;</th> 261 } 262 <th>@Translate("Type")</th> 263 <th>@Translate("Name")</th> 264 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 265 <th class="text-end" style="width:100px">@Translate("File type")</th> 266 </tr> 267 </thead> 268 <tbody class="border-top-0"> 269 @foreach (MediaViewModel asset in assetsList) 270 { 271 var assetValue = asset.Value; 272 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 273 274 bool isVideo = false; 275 foreach (string format in supportedVideoFormats) 276 { //Videos 277 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 278 { 279 isVideo = true; 280 } 281 } 282 283 if (!isVideo) 284 { 285 if (assetValue.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase)) 286 { 287 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 288 long fileSize = 0; 289 290 if (File.Exists(filePath)) 291 { 292 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 293 294 foreach (string format in allSupportedFormats) 295 { 296 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 297 { 298 <tr> 299 <td>@resolveGroup(asset)</td> 300 @if (!hideThumbnails) 301 { 302 //From @RenderAsset 303 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 304 305 <td class="@(imageTheme) px-0"> 306 @foreach (string imageFormat in supportedImageFormats) 307 { //Images 308 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 309 { 310 string productName = product.Name; 311 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 312 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 313 string imageLinkPath = imagePath; 314 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 315 imagePath = !imageLinkPath.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? asset.Value : imagePath; 316 317 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 318 319 RatioSettings ratioSettings = GetRatioSettings(); 320 321 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 322 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 323 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 324 </div> 325 </a> 326 } 327 } 328 @foreach (string videoFormat in supportedVideoFormats) 329 { //Videos 330 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 331 { 332 string productName = product.Name; 333 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 334 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 335 336 RatioSettings ratioSettings = GetRatioSettings(); 337 338 string type = GetVideoType(asset.Value); 339 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value) : string.Empty; 340 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 341 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 342 343 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 344 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 345 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 346 @if (type != "selfhosted") 347 { 348 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;"> 349 } 350 else 351 { 352 string videoType = Path.GetExtension(asset.Value).ToLower(); 353 354 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 355 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 356 </video> 357 } 358 </div> 359 </div> 360 } 361 } 362 @foreach (string documentFormat in supportedDocumentFormats) 363 { //Documents 364 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 365 { 366 string productName = product.Name; 367 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 368 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 369 string imageLinkPath = imagePath; 370 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 371 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 372 373 RatioSettings ratioSettings = GetRatioSettings(); 374 375 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 376 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 377 { 378 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 379 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 380 </div> 381 } 382 else 383 { 384 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 385 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 386 </div> 387 } 388 </a> 389 } 390 } 391 </td> 392 393 } 394 <td> 395 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 396 @assetName 397 </a> 398 </td> 399 <td class="text-end d-none d-lg-table-cell"> 400 <a href="@assetValue" class="text-decoration-none" download="@assetName" title="@assetName"> 401 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 402 </a> 403 </td> 404 <td class="text-end">@format</td> 405 </tr> 406 } 407 } 408 } 409 } 410 else 411 { 412 <tr> 413 <td>@resolveGroup(asset)</td> 414 @if (!hideThumbnails) 415 { 416 //From @RenderAsset 417 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 418 419 <td class="@(imageTheme) px-0"> 420 @foreach (string format in supportedImageFormats) 421 { //Images 422 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 423 { 424 string productName = product.Name; 425 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 426 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 427 string imageLinkPath = imagePath; 428 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 429 imagePath = !imageLinkPath.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? asset.Value : imagePath; 430 431 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 432 433 RatioSettings ratioSettings = GetRatioSettings(); 434 435 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 436 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 437 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 438 </div> 439 </a> 440 } 441 } 442 @foreach (string format in supportedVideoFormats) 443 { //Videos 444 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 445 { 446 string type = GetVideoType(asset.Value); 447 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value) : string.Empty; 448 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 449 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 450 451 452 string productName = product.Name; 453 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 454 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 455 456 RatioSettings ratioSettings = GetRatioSettings(); 457 458 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 459 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 460 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 461 @if (type != "selfhosted") 462 { 463 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;"> 464 } 465 else 466 { 467 string videoType = Path.GetExtension(asset.Value).ToLower(); 468 469 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 470 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 471 </video> 472 } 473 </div> 474 </div> 475 } 476 } 477 @foreach (string format in supportedDocumentFormats) 478 { //Documents 479 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 480 { 481 string productName = product.Name; 482 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 483 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 484 string imageLinkPath = imagePath; 485 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 486 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 487 488 RatioSettings ratioSettings = GetRatioSettings(); 489 490 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 491 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 492 { 493 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 494 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 495 </div> 496 } 497 else 498 { 499 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 500 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 501 </div> 502 } 503 </a> 504 } 505 } 506 </td> 507 } 508 <td> 509 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 510 @assetName 511 </a> 512 </td> 513 <td>&nbsp;</td> 514 <td>&nbsp;</td> 515 </tr> 516 } 517 } 518 else 519 { 520 string videoType = asset.Value.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || asset.Value.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "Youtube" : ""; 521 videoType = asset.Value.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "Vimeo" : videoType; 522 523 <tr 524 data-bs-toggle="modal" 525 data-bs-target="#modal_@(Model.ID)_@videoNumber" 526 style="cursor: pointer" 527 > 528 <td>@resolveGroup(asset)</td> 529 @if (!hideThumbnails) 530 { 531 //From @RenderAsset 532 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 533 534 <td>type</td> 535 <td class="@(imageTheme) px-0"> 536 @foreach (string format in supportedImageFormats) 537 { //Images 538 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 539 { 540 string productName = product.Name; 541 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 542 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 543 string imageLinkPath = imagePath; 544 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 545 imagePath = !imageLinkPath.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? asset.Value : imagePath; 546 547 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 548 549 RatioSettings ratioSettings = GetRatioSettings(); 550 551 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 552 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 553 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 554 </div> 555 </a> 556 } 557 } 558 @foreach (string format in supportedVideoFormats) 559 { //Videos 560 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 561 { 562 563 string type = GetVideoType(asset.Value); 564 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value) : string.Empty; 565 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 566 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 567 568 string productName = product.Name; 569 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 570 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 571 572 RatioSettings ratioSettings = GetRatioSettings(); 573 574 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 575 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 576 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 577 @if (type != "selfhosted") 578 { 579 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;"> 580 } 581 else 582 { 583 string fileExtension = Path.GetExtension(asset.Value).ToLower(); 584 585 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 586 <source src="@(videoScreendumpPath)#t=0.001" type="video/@fileExtension.Replace(".", "")"> 587 </video> 588 } 589 </div> 590 </div> 591 } 592 } 593 @foreach (string documentFormat in supportedDocumentFormats) 594 { //Documents 595 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 596 { 597 string productName = product.Name; 598 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 599 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 600 string imageLinkPath = imagePath; 601 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 602 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 603 604 RatioSettings ratioSettings = GetRatioSettings(); 605 606 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 607 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 608 { 609 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 610 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 611 </div> 612 } 613 else 614 { 615 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 616 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 617 </div> 618 } 619 </a> 620 } 621 } 622 </td> 623 } 624 <td>@assetName</td> 625 <td class="d-none d-lg-table-cell">&nbsp;</td> 626 <td align="right">@videoType</td> 627 </tr> 628 629 videoNumber++; 630 } 631 } 632 </tbody> 633 </table> 634 </div> 635 636 @foreach (MediaViewModel asset in assetsList) 637 { 638 var assetName = asset.Value.ToLower(); 639 640 foreach (string videoFormat in supportedVideoFormats) 641 { //Videos 642 if (assetName.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 643 { 644 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 645 <div class="modal-dialog modal-dialog-centered modal-xl"> 646 <div class="modal-content"> 647 <div class="modal-header visually-hidden"> 648 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 649 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 650 </div> 651 <div class="modal-body p-2 p-lg-3 h-100"> 652 @{ 653 var videoParams = GetVideoParams(asset, "modal"); 654 @RenderPartial("Components/VideoPlayer.cshtml", new Dynamicweb.Frontend.FileViewModel { Path = asset.Value }, videoParams) 655 } 656 </div> 657 </div> 658 </div> 659 </div> 660 661 modalVideoNumber++; 662 } 663 } 664 } 665 </div> 666 } 667 else if (Pageview.IsVisualEditorMode) 668 { 669 <div class="h-100 @theme"> 670 <div class="alert alert-dark m-0"> 671 @Translate("No assets are available") 672 </div> 673 </div> 674 } 675 } 676
By clicking 'Accept All' you consent that we may collect information about you for various purposes, including: Statistics and Marketing