Error executing template "Designs/Swift/Swift_Page.cshtml"
System.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 - Could not open a connection to SQL Server)
 ---> System.ComponentModel.Win32Exception (2): The system cannot find the file specified.
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at System.Data.SqlClient.SqlConnection.Open()
   at Dynamicweb.Data.DatabaseConnectionProvider.CreateConnection(Boolean open)
   at Dynamicweb.Data.Database.CreateConnection()
   at Dynamicweb.Data.Database.CreateDataReader(CommandBuilder commandBuilder, IDbConnection connection, IDbTransaction transaction, Int32 commandTimeout)
   at Dynamicweb.Ecommerce.Products.ProductRepository.GetProductById(String productId, String productVariantId, String productLanguageId)
   at Dynamicweb.Ecommerce.Products.ProductService.FetchMissingProductsInternal(IProductRepository repo, IEnumerable`1 keys)
   at Dynamicweb.Caching.ServiceCache`2.GetCache(IEnumerable`1 keys)
   at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, User user, Boolean showUntranslated)
   at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, Boolean useAssortments)
   at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId)
   at CompiledRazorTemplates.Dynamic.RazorEngine_5ee9c0e49ab24b6981fc5a2731880b3b.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()
ClientConnectionId:00000000-0000-0000-0000-000000000000
Error Number:2,State:0,Class:20

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.PageViewModel> 2 @using System 3 @using System.Web 4 @using Dynamicweb 5 @using Dynamicweb.Environment 6 @using Dynamicweb.Frontend 7 8 @functions { 9 string GetCookieOptInPermission(string category) 10 { 11 bool categoryOrAllGranted = false; 12 13 if (CookieManager.IsCookieManagementActive) 14 { 15 var cookieOptInLevel = CookieManager.GetCookieOptInLevel(); 16 var cookieOptInCategories = CookieManager.GetCookieOptInCategories(); 17 categoryOrAllGranted = cookieOptInCategories.Contains(category) || cookieOptInLevel == CookieOptInLevel.All; 18 } 19 20 return categoryOrAllGranted ? "granted" : "denied"; 21 } 22 23 bool AllowTracking() 24 { 25 bool allowTracking = true; 26 if (CookieManager.IsCookieManagementActive) 27 { 28 var cookieOptInLevel = CookieManager.GetCookieOptInLevel(); 29 var cookieOptInCategories = CookieManager.GetCookieOptInCategories(); 30 31 bool consentEither = (cookieOptInCategories.Contains("Statistical") || cookieOptInCategories.Contains("Marketing")); 32 bool consentFunctional = cookieOptInLevel == CookieOptInLevel.Functional; 33 bool consentAtLeastOne = cookieOptInLevel == CookieOptInLevel.All || (consentFunctional && consentEither); 34 35 allowTracking = consentAtLeastOne; 36 } 37 return allowTracking; 38 } 39 } 40 41 @{ 42 var cartSummaryPageId = Dynamicweb.Content.Services.Pages.GetPageByNavigationTag(Model.Area.ID, "CartSummary")?.ID; 43 bool enableMiniCart = Model.Area.Item?.GetBoolean("EnableOffcanvasMiniCart") ?? false; 44 var offcanvasMiniCartBehaviour = Model.Area.Item?.GetRawValueString("OffcanvasMinicartBehaviour", "3") ?? "3"; 45 bool miniCartEnabled = cartSummaryPageId != null && enableMiniCart; 46 var brandingPageId = Model.Area.Item?.GetInt32("BrandingPage") ?? 0; 47 var themePageId = Model.Area.Item?.GetInt32("ThemesPage") ?? 0; 48 var cssPageId = Model.Area.Item?.GetInt32("CssPage") ?? 0; 49 var brandingPage = brandingPageId != 0 ? Dynamicweb.Content.Services.Pages?.GetPage(brandingPageId) ?? null : null; 50 var themesParagraphs = themePageId != 0 ? Dynamicweb.Content.Services.Paragraphs?.GetParagraphsByPageId(themePageId) ?? null : null; 51 var cssParagraphs = cssPageId != 0 ? Dynamicweb.Content.Services.Paragraphs?.GetParagraphsByPageId(cssPageId) ?? null : null; 52 53 } 54 55 @if (themesParagraphs != null || brandingPage != null) 56 { 57 string swiftVersion = ReadFile("/Files/Templates/Designs/Swift/swift_version.txt"); 58 bool renderAsResponsive = Model.Area.Item.GetString("DeviceRendering", "responsive").Equals("responsive", StringComparison.OrdinalIgnoreCase); 59 bool renderMobile = Pageview.Device == Dynamicweb.Frontend.Devices.DeviceType.Mobile || Pageview.Device == Dynamicweb.Frontend.Devices.DeviceType.Tablet; 60 string responsiveClassDesktop = string.Empty; 61 string responsiveClassMobile = string.Empty; 62 if (renderAsResponsive) 63 { 64 responsiveClassDesktop = " d-none d-xl-block"; 65 responsiveClassMobile = " d-block d-xl-none"; 66 } 67 68 var headerDesktopLink = Model.Area.Item?.GetLink("HeaderDesktop") ?? null; 69 var headerMobileLink = Model.Area.Item?.GetLink("HeaderMobile") ?? null; 70 71 var footerDesktopLink = Model.Area.Item?.GetLink("FooterDesktop") ?? null; 72 var footerMobileLink = Model.Area.Item?.GetLink("FooterMobile") ?? null; 73 74 var newsletterModalFormPage = 1; 75 if(Dynamicweb.Content.Services.Pages.GetPageByNavigationTag(Pageview.Area.ID, "newsletterSignUpFormModal") != null) 76 { 77 newsletterModalFormPage = Dynamicweb.Content.Services.Pages.GetPageByNavigationTag(Pageview.Area.ID, "newsletterSignUpFormModal").ID; 78 } 79 80 81 var disableWideBreakpoints = Model.Area?.Item?.GetRawValueString("DisableWideBreakpoints", "default"); 82 83 string customHeaderInclude = Model.Area.Item.GetRawValueString("CustomHeaderInclude").Replace("/Files/Templates/Designs/Swift/", ""); 84 if (Model.Area.Item.GetFile("CustomHeaderInclude") != null) 85 { 86 customHeaderInclude = Model.Area.Item.GetFile("CustomHeaderInclude").Path.Replace("/Files/Templates/Designs/Swift/", ""); 87 } 88 89 var themesParagraphLastChanged = Dynamicweb.Content.Services.Paragraphs.GetParagraphsByPageId(themePageId).OrderByDescending(p => p.Audit.LastModifiedAt).FirstOrDefault(); 90 var cssLastModified = brandingPage.Audit.LastModifiedAt > themesParagraphLastChanged.Audit.LastModifiedAt ? brandingPage.Audit.LastModifiedAt : themesParagraphLastChanged.Audit.LastModifiedAt; 91 92 var cssThemeAndBrandingStyleFileInfo = new System.IO.FileInfo(Dynamicweb.Core.SystemInformation.MapPath($"/Files/Templates/Designs/Swift/_parsed/Swift_css/Swift_styles_{Model.Area.ID}.min.css")); 93 94 95 if (cssPageId != 0) 96 { 97 var cssFileInfo = new System.IO.FileInfo(Dynamicweb.Core.SystemInformation.MapPath($"/Files/Templates/Designs/Swift/_parsed/Swift_css/Swift_css_styles_{Model.Area.ID}.css")); 98 var cssParagraphLastChanged = Dynamicweb.Content.Services.Paragraphs.GetParagraphsByPageId(cssPageId).OrderByDescending(p => p.Audit.LastModifiedAt).FirstOrDefault(); 99 if (!cssThemeAndBrandingStyleFileInfo.Exists || cssThemeAndBrandingStyleFileInfo.LastWriteTime < cssParagraphLastChanged.Audit.LastModifiedAt) 100 { 101 var cssPageview = Dynamicweb.Frontend.PageView.GetPageviewByPageID(cssPageId); 102 cssPageview.Redirect = false; 103 cssPageview.Output(); 104 } 105 } 106 107 if (!cssThemeAndBrandingStyleFileInfo.Exists || cssThemeAndBrandingStyleFileInfo.LastWriteTime < brandingPage.Audit.LastModifiedAt) 108 { 109 //Branding page has been saved or the file is missing. Rewrite the file to disc. 110 if (brandingPageId > 0) 111 { 112 var brandingPageview = Dynamicweb.Frontend.PageView.GetPageviewByPageID(brandingPageId); 113 brandingPageview.Redirect = false; 114 brandingPageview.Output(); 115 } 116 } 117 118 if (!cssThemeAndBrandingStyleFileInfo.Exists || cssThemeAndBrandingStyleFileInfo.LastWriteTime < themesParagraphLastChanged.Audit.LastModifiedAt) 119 { 120 //Branding page has been saved or the file is missing. Rewrite the file to disc. 121 if (themePageId > 0) 122 { 123 var themePageview = Dynamicweb.Frontend.PageView.GetPageviewByPageID(themePageId); 124 themePageview.Redirect = false; 125 themePageview.Output(); 126 } 127 } 128 129 // Schema.org details for PDP 130 bool isProductDetailsPage = Dynamicweb.Context.Current.Request.QueryString.AllKeys.Contains("ProductID"); 131 bool isArticlePage = Model.ItemType == "Swift_Article"; 132 string schemaOrgType = string.Empty; 133 134 if (isProductDetailsPage) 135 { 136 schemaOrgType = "itemscope=\"\" itemtype=\"https://schema.org/Product\""; 137 } 138 139 if (isArticlePage) 140 { 141 schemaOrgType = "itemscope=\"\" itemtype=\"https://schema.org/Article\""; 142 } 143 144 145 var cssStyleFileInfo = new System.IO.FileInfo(Dynamicweb.Core.SystemInformation.MapPath("/Files/Templates/Designs/Swift/Assets/css/styles.css")); 146 var jsFileInfo = new System.IO.FileInfo(Dynamicweb.Core.SystemInformation.MapPath("/Files/Templates/Designs/Swift/Assets/js/scripts.js")); 147 148 string masterTheme = !string.IsNullOrWhiteSpace(Model.Area.Item.GetRawValueString("Theme")) ? " theme " + Model.Area.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 149 150 string favicon = Model.Area.Item.GetRawValueString("Favicon", "/Files/Templates/Designs/Swift/Assets/Images/favicon.png"); 151 string appleTouchIcon = Model.Area.Item.GetRawValueString("AppleTouchIcon", "/Files/Templates/Designs/Swift/Assets/Images/apple-touch-icon.png"); 152 153 string headerCssClass = "sticky-top"; 154 bool movePageBehind = false; 155 156 if (Model.PropertyItem != null) 157 { 158 headerCssClass = Model.PropertyItem.GetRawValueString("MoveThisPageBehindTheHeader", "sticky-top"); 159 movePageBehind = headerCssClass == "fixed-top" && !Pageview.IsVisualEditorMode ? true : false; 160 } 161 162 headerCssClass = headerCssClass == "" ? "sticky-top" : headerCssClass; 163 headerCssClass = Pageview.IsVisualEditorMode ? "" : headerCssClass; 164 165 string googleTagManagerID = Model.Area.Item.GetString("GoogleTagManagerID").Trim(); 166 string googleAnalyticsMeasurementID = Model.Area.Item.GetString("GoogleAnalyticsMeasurementID").Trim(); 167 168 bool allowTracking = AllowTracking(); 169 170 Dynamicweb.Context.Current.Response.AddHeader("link", $"</Files/Templates/Designs/Swift/Assets/css/styles.css?{cssStyleFileInfo.LastWriteTime.Ticks}>; rel=preload; as=style;"); 171 Dynamicweb.Context.Current.Response.AddHeader("link", $"</Files/Templates/Designs/Swift/_parsed/Swift_css/Swift_styles_{Model.Area.ID}.min.css?{cssLastModified.Ticks}>; rel=preload; as=style;"); 172 Dynamicweb.Context.Current.Response.AddHeader("link", $"</Files/Templates/Designs/Swift/Assets/js/scripts.js?{jsFileInfo.LastWriteTime.Ticks}>; rel=preload; as=script;"); 173 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 174 175 176 SetMetaTags(); 177 178 List<Dynamicweb.Content.Page> languages = new List<Dynamicweb.Content.Page>(); 179 180 var masterPage = Pageview.Area.IsMaster ? Pageview.Page : Pageview.Page.MasterPage; 181 languages.Add(masterPage); 182 if (masterPage?.Languages != null) 183 { 184 foreach (var language in masterPage.Languages) 185 { 186 languages.Add(language); 187 } 188 } 189 190 Uri url = Dynamicweb.Context.Current.Request.Url; 191 string hostName = url.Host; 192 193 <!doctype html> 194 <html lang="@Pageview.Area.CultureInfo.TwoLetterISOLanguageName"> 195 <head> 196 <!-- @swiftVersion --> 197 @* Required meta tags *@ 198 <meta charset="utf-8"> 199 <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0"> 200 <link rel="shortcut icon" href="@favicon"> 201 <link rel="apple-touch-icon" href="@appleTouchIcon"> 202 203 @Model.MetaTags 204 205 @{ 206 var alreadyWrittenTwoletterIsos = new List<string>(); 207 @* Languages meta data *@ 208 foreach (var language in languages) 209 { 210 hostName = url.Host; 211 if (language?.Area != null) 212 { 213 if (language.Area?.MasterArea != null && !string.IsNullOrEmpty(language.Area.MasterArea.DomainLock)) 214 { 215 hostName = language.Area.MasterArea.DomainLock; //dk.domain.com or dk-domain.dk 216 } 217 if (language != null && language.Area != null && language.Published && language.Area.Active && language.Area.Published) 218 { 219 if (!string.IsNullOrEmpty(language.Area.DomainLock)) 220 { 221 hostName = language.Area.DomainLock; //dk.domain.com or dk-domain.dk 222 } 223 string querystring = $"Default.aspx?ID={language.ID}"; 224 if (!string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString["GroupID"])) 225 { 226 querystring += $"&GroupID={Dynamicweb.Context.Current.Request.QueryString["GroupID"]}"; 227 } 228 if (!string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString["ProductID"])) 229 { 230 querystring += $"&ProductID={Dynamicweb.Context.Current.Request.QueryString["ProductID"]}"; 231 } 232 if (!string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString["VariantID"])) 233 { 234 querystring += $"&VariantID={Dynamicweb.Context.Current.Request.QueryString["VariantID"]}"; 235 } 236 237 string friendlyUrl = Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(querystring); 238 if (language.Area.RedirectFirstPage && language.ParentPageId == 0 && language.Sort == 1) 239 { 240 friendlyUrl = "/"; 241 } 242 string href = $"{url.Scheme}://{hostName}{friendlyUrl}"; 243 244 245 <link rel="alternate" hreflang="@language.Area.CultureInfo.Name.ToLower()" href="@href"> 246 if (!alreadyWrittenTwoletterIsos.Contains(language.Area.CultureInfo.TwoLetterISOLanguageName)) 247 { 248 alreadyWrittenTwoletterIsos.Add(language.Area.CultureInfo.TwoLetterISOLanguageName); 249 <link rel="alternate" hreflang="@language.Area.CultureInfo.TwoLetterISOLanguageName.ToLower()" href="@href"> 250 } 251 } 252 } 253 } 254 } 255 256 <title>@Model.Title</title> 257 @* Bootstrap + Swift stylesheet *@ 258 <link href="/Files/Templates/Designs/Swift/Assets/css/styles.css?@cssStyleFileInfo.LastWriteTime.Ticks" rel="stylesheet" media="all" type="text/css"> 259 260 @if (disableWideBreakpoints != "disableBoth") 261 { 262 <style> 263 @@media ( min-width: 1600px ) { 264 .container-xxl, 265 .container-xl, 266 .container-lg, 267 .container-md, 268 .container-sm, 269 .container { 270 max-width: 1520px; 271 } 272 } 273 </style> 274 275 276 277 if (disableWideBreakpoints != "disableUltraWideOnly") 278 { 279 <style> 280 @@media ( min-width: 1920px ) { 281 .container-xxl, 282 .container-xl, 283 .container-lg, 284 .container-md, 285 .container-sm, 286 .container { 287 max-width: 1820px; 288 } 289 } 290 </style> 291 } 292 } 293 294 @* Branding and Themes min stylesheet *@ 295 <link href="/Files/Templates/Designs/Swift/_parsed/Swift_css/Swift_styles_@(Model.Area.ID).min.css?@cssLastModified.Ticks" rel="stylesheet" media="all" type="text/css" data-last-modified-content="@cssLastModified"> 296 <script src="/Files/Templates/Designs/Swift/Assets/js/scripts.js?@jsFileInfo.LastWriteTime.Ticks"></script> 297 <script type="module"> 298 swift.Scroll.hideHeadersOnScroll(); 299 swift.Scroll.handleAlternativeTheme(); 300 301 //Only load if AOS 302 const aosColumns = document.querySelectorAll('[data-aos]'); 303 if (aosColumns.length > 0) { 304 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/js/aos.js?@jsFileInfo.LastWriteTime.Ticks', 'js'); 305 document.addEventListener('load.swift.assetloader', function () { 306 AOS.init({ duration: 400, delay: 100, easing: 'ease-in-out', mirror: false, disable: window.matchMedia('(prefers-reduced-motion: reduce)') }); 307 }); 308 } 309 </script> 310 311 @* Google gtag method - always include even if it is not used for anything *@ 312 <script> 313 window.dataLayer = window.dataLayer || []; 314 function gtag() { dataLayer.push(arguments); } 315 </script> 316 @* Google tag manager *@ 317 @if (!string.IsNullOrWhiteSpace(googleTagManagerID)) 318 { 319 <script> 320 gtag('consent', 'default', { 321 'ad_storage': 'denied', 322 'ad_user_data': 'denied', 323 'ad_personalization': 'denied', 324 'analytics_storage': 'denied' 325 }); 326 </script> 327 <script> 328 (function (w, d, s, l, i) { 329 w[l] = w[l] || []; w[l].push({ 330 'gtm.start': 331 new Date().getTime(), event: 'gtm.js' 332 }); var f = d.getElementsByTagName(s)[0], 333 j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src = 334 'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f); 335 })(window, document, 'script', 'dataLayer', '@(googleTagManagerID)'); 336 </script> 337 if (allowTracking) 338 { 339 string adConsent = GetCookieOptInPermission("Marketing"); 340 string analyticsConsent = GetCookieOptInPermission("Statistical"); 341 <script> 342 gtag('consent', 'update', { 343 'ad_storage': '@adConsent', 344 'ad_user_data': '@adConsent', 345 'ad_personalization': '@adConsent', 346 'analytics_storage': '@analyticsConsent' 347 }); 348 </script> 349 } 350 } 351 352 @if (!string.IsNullOrWhiteSpace(googleAnalyticsMeasurementID) && allowTracking) 353 { 354 var GoogleAnalyticsDebugMode = ""; 355 356 if (Model.Area.Item.GetBoolean("EnableGoogleAnalyticsDebugMode")) 357 { 358 GoogleAnalyticsDebugMode = ", {'debug_mode': true}"; 359 } 360 361 <script async src="https://www.googletagmanager.com/gtag/js?id=@googleAnalyticsMeasurementID"></script> 362 <script> 363 gtag('js', new Date()); 364 gtag('config', '@googleAnalyticsMeasurementID'@GoogleAnalyticsDebugMode); 365 </script> 366 } 367 368 @if (!string.IsNullOrWhiteSpace(customHeaderInclude)) 369 { 370 @RenderPartial(customHeaderInclude) 371 } 372 </head> 373 <body class="brand @(masterTheme)" id="page@(Model.ID)"> 374 375 @* Google tag manager *@ 376 @if (!string.IsNullOrWhiteSpace(googleTagManagerID) && allowTracking) 377 { 378 <noscript> 379 <iframe src="https://www.googletagmanager.com/ns.html?id=@(googleTagManagerID)" 380 height="0" width="0" style="display:none;visibility:hidden"></iframe> 381 </noscript> 382 } 383 384 @if (renderAsResponsive || !renderMobile) 385 { 386 <header class="page-header @headerCssClass top-0@(responsiveClassDesktop)" id="page-header-desktop"> 387 @if (headerDesktopLink != null) 388 { 389 @RenderGrid(headerDesktopLink.PageId) 390 } 391 </header> 392 } 393 394 @if ((renderAsResponsive || renderMobile)) 395 { 396 <header class="page-header @headerCssClass top-0@(responsiveClassMobile)" id="page-header-mobile"> 397 @if (headerMobileLink != null) 398 { 399 @RenderGrid(headerMobileLink.PageId) 400 } 401 </header> 402 } 403 404 <div data-intersect></div> 405 406 <main id="content" @(schemaOrgType)> 407 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.PageViewModel> 408 @using System 409 @using Dynamicweb.Ecommerce.ProductCatalog 410 411 412 @{ 413 string productIdFromUrl = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("ProductID")) ? Dynamicweb.Context.Current.Request.QueryString.Get("ProductID") : string.Empty; 414 bool isProductDetail = !string.IsNullOrEmpty(productIdFromUrl) && Pageview.Page.NavigationTag.ToLower() == "shop"; 415 416 bool isArticlePagePage = Model.ItemType == "Swift_Article"; 417 bool isArticleListPage = Model.ItemType == "Swift_ArticleListPage"; 418 string schemaOrgProp = string.Empty; 419 if (isArticlePagePage) 420 { 421 schemaOrgProp = "itemprop=\"articleBody\""; 422 } 423 424 string theme = ""; 425 string gridContent = ""; 426 427 if (Model.PropertyItem != null) 428 { 429 theme = !string.IsNullOrWhiteSpace(Model.PropertyItem.GetRawValueString("Theme")) ? "theme " + Model.PropertyItem.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 430 } 431 432 if (Model.Item != null || Pageview.IsVisualEditorMode) 433 { 434 if (!isProductDetail) 435 { 436 gridContent = Model.Grid("Grid", "Grid", "default:true;sort:1", "Page"); 437 } 438 else 439 { 440 var productObject = Dynamicweb.Ecommerce.Services.Products.GetProductById(productIdFromUrl, "", Pageview.Area.EcomLanguageId); 441 if (productObject != null) 442 { 443 var detailPage = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(productObject?.PrimaryGroupId)?.Meta.PrimaryPage ?? string.Empty; 444 var detailPageId = detailPage != string.Empty ? Convert.ToInt16(detailPage.Substring(detailPage.LastIndexOf('=') + 1)) : GetPageIdByNavigationTag("ProductDetailPage"); 445 446 @RenderGrid(detailPageId) 447 } 448 } 449 } 450 451 bool doNotRenderPage = false; 452 453 //Check if we are on the poduct detail page, and if there is data to render 454 ProductViewModel product = new ProductViewModel(); 455 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 456 { 457 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 458 if (string.IsNullOrEmpty(product.Id)) 459 { 460 doNotRenderPage = true; 461 } 462 } 463 464 //Render the page 465 if (!doNotRenderPage) 466 { 467 string itemIdentifier = Model?.Item?.SystemName != null ? "item_" + Model.Item.SystemName.ToLower() : "item_Swift_Page"; 468 469 if (Pageview.IsVisualEditorMode) 470 { 471 @Model.Placeholder("dwcontent", "content", "default:true;sort:1") 472 } 473 474 <div class="@theme @itemIdentifier" @schemaOrgProp> 475 @if (isArticleListPage) 476 { 477 var hx = $"hx-get=\"{Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(Model.ID)}\" hx-select=\"#content\" hx-target=\"#content\" hx-swap=\"outerHTML\" hx-trigger=\"change\" hx-headers='{{\"feed\": \"true\"}}' hx-push-url=\"true\" hx-indicator=\"#ArticleFacetForm\""; 478 479 <form @hx id="ArticleFacetForm"> 480 @gridContent 481 </form> 482 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/htmx.js"></script> 483 <script type="module"> 484 document.addEventListener('htmx:confirm', (event) => { 485 let filters = event.detail.elt.querySelectorAll('select'); 486 for (var i = 0; i < filters.length; i++) { 487 let input = filters[i]; 488 if (input.name && !input.value) { 489 input.name = ''; 490 } 491 } 492 }); 493 494 document.addEventListener('htmx:beforeOnLoad', (event) => { 495 swift.Scroll.stopIntersectionObserver(); 496 }); 497 498 document.addEventListener('htmx:afterOnLoad', () => { 499 swift.Scroll.hideHeadersOnScroll(); 500 swift.Scroll.handleAlternativeTheme(); 501 }); 502 </script> 503 } 504 else 505 { 506 @gridContent 507 } 508 </div> 509 510 } 511 else 512 { 513 <div class="container"> 514 <div class="alert alert-info" role="alert">@Translate("Sorry. There is nothing to view here")</div> 515 </div> 516 } 517 518 if (!Model.IsCurrentUserAllowed) 519 { 520 int signInPage = GetPageIdByNavigationTag("SignInPage"); 521 int dashboardPage = GetPageIdByNavigationTag("MyAccountDashboardPage"); 522 523 if (!Pageview.IsVisualEditorMode) 524 { 525 if (signInPage != 0) 526 { 527 if (signInPage != Model.ID) 528 { 529 Dynamicweb.Context.Current.Response.Redirect("/Default.aspx?ID=" + signInPage); 530 } 531 else 532 { 533 if (dashboardPage != 0) 534 { 535 Dynamicweb.Context.Current.Response.Redirect("/Default.aspx?ID=" + dashboardPage); 536 } 537 else 538 { 539 Dynamicweb.Context.Current.Response.Redirect("/"); 540 } 541 } 542 } 543 else 544 { 545 <div class="alert alert-dark m-0" role="alert"> 546 <span>@Translate("You do not have access to this page")</span> 547 </div> 548 } 549 } 550 else 551 { 552 <div class="alert alert-dark m-0" role="alert"> 553 <span>@Translate("To work on this page, you must be signed in, in the frontend")</span> 554 </div> 555 } 556 } 557 } 558 559 </main> 560 561 @if (renderAsResponsive || !renderMobile) 562 { 563 <footer class="page-footer@(responsiveClassDesktop)" id="page-footer-desktop"> 564 @if (footerDesktopLink != null) 565 { 566 @RenderGrid(footerDesktopLink.PageId) 567 } 568 </footer> 569 } 570 571 @if (renderAsResponsive || renderMobile) 572 { 573 <footer class="page-footer@(responsiveClassMobile)" id="page-footer-mobile"> 574 @if (footerMobileLink != null) 575 { 576 @RenderGrid(footerMobileLink.PageId) 577 } 578 </footer> 579 } 580 581 @* Render any offcanvas menu here *@ 582 @RenderSnippet("offcanvas") 583 584 @{ 585 bool isErpConnectionDown = !Dynamicweb.Core.Converter.ToBoolean(Context.Current.Items["IsWebServiceConnectionAvailable"]); 586 } 587 588 @* Language selector modal *@ 589 <div class="modal fade" id="PreferencesModal" tabindex="-1" aria-hidden="true"> 590 <div class="modal-dialog modal-dialog-centered modal-sm" id="PreferencesModalContent"> 591 @* The content here comes from an external request *@ 592 </div> 593 </div> 594 595 @* Favorite toast *@ 596 <div aria-live="polite" aria-atomic="true"> 597 <div class="position-fixed bottom-0 end-0 p-3" style="z-index: 11"> 598 <div id="favoriteNotificationToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true"> 599 <div class="toast-header"> 600 <strong class="me-auto">@Translate("Favorite list updated")</strong> 601 <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button> 602 </div> 603 <div class="toast-body d-flex gap-3"> 604 <div id="favoriteNotificationToast_Image"></div> 605 <div id="favoriteNotificationToast_Text"></div> 606 </div> 607 </div> 608 </div> 609 </div> 610 611 @* Modal for dynamic content *@ 612 <div class="modal fade js-product" id="DynamicModal" tabindex="-1" aria-hidden="true"> 613 <div class="modal-dialog modal-dialog-centered modal-md"> 614 <div class="modal-content theme light" id="DynamicModalContent"> 615 @* The content here comes from an external request *@ 616 </div> 617 </div> 618 </div> 619 620 @* Capo Custom Newsleter and voucher code *@ 621 @if (allowTracking) 622 { 623 var formSubmitted = !string.IsNullOrEmpty(Model.Area.Item.GetString("Form.Posted")); 624 625 @* Modal for Registar newsletter *@ 626 <div class="modal fade js-product" id="newsletterModal" tabindex="-1" aria-hidden="true"> 627 <div class="modal-dialog modal-dialog-centered"> 628 <div class="modal-content theme light" id="NewsletterModalContent" style="max-width: 500px; padding: 20px;"> 629 <div class="modal-header"> 630 <button type="button" id="newsletterModalCloseButton" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 631 </div> 632 @* <div id="newsletterVoucher" class="newsletterVoucher" style="display:none"> 633 <h1>@Translate("Voucher")</h1> 634 </div> *@ 635 @RenderGrid(newsletterModalFormPage) 636 <div id="modal-response" class="mt-3"></div> 637 </div> 638 </div> 639 </div> 640 <script> 641 console.log("load script"); 642 const modalConsentCookie = document.cookie.match(/modalNewsletterConsentCookie=([^;]*)/)?.[1]; 643 const footerConsentCookie = document.cookie.match(/footerNewsletterConsentCookie=([^;]*)/)?.[1]; 644 const modalVoucherCookie = document.cookie.match(/voucherCode=([^;]*)/)?.[1]; 645 const modalNewsletter = new bootstrap.Modal(document.getElementById('newsletterModal')); 646 const modalVoucherCodeElement = document.getElementById("modal-voucher-code"); 647 const modalVoucherInput = document.querySelector("#newslettersignupvouchercode"); 648 const modalVoucherCode = modalVoucherCodeElement?.getAttribute("data-voucher"); 649 const expirationDate = new Date(Date.now() + 180 * 24 * 60 * 60 * 1000).toUTCString(); 650 const modalCloseButton = document.getElementById('newsletterModalCloseButton'); 651 const modalForm = document.getElementById('newsletterModal')?.querySelector('form'); 652 653 if (modalConsentCookie === "true") { 654 console.log("Form was submitted. Show modal to display response"); 655 656 @* modalNewsletter.show(); *@ 657 658 if (modalCloseButton) { 659 modalCloseButton.addEventListener('click', () => { 660 document.cookie = `modalNewsletterConsentCookie=true&closed; expires=${expirationDate}; path=/`; 661 }); 662 } 663 664 } else if (modalConsentCookie === "false") { 665 console.log("Consent cookie is false � show modal"); 666 modalNewsletter.show(); 667 668 if (modalCloseButton) { 669 modalCloseButton.addEventListener('click', () => { 670 document.cookie = `modalNewsletterConsentCookie=false&closed; expires=${expirationDate}; path=/`; 671 }); 672 } 673 } else if (!modalConsentCookie) { 674 console.log("schedule modal to open"); 675 setTimeout(() => { 676 modalNewsletter.show(); 677 678 if (modalCloseButton) { 679 modalCloseButton.addEventListener('click', () => { 680 document.cookie = `modalNewsletterConsentCookie=false&closed; expires=${expirationDate}; path=/`; 681 }); 682 } 683 684 }, 10000); 685 686 if (modalVoucherCodeElement && modalVoucherInput && modalVoucherCode) { 687 modalVoucherInput.value = modalVoucherCode; 688 } 689 690 if (modalForm) { 691 692 if (!modalForm.contains(modalVoucherInput)) { 693 modalForm.appendChild(modalVoucherInput); 694 } 695 696 modalForm.addEventListener('submit', (e) => { 697 e.preventDefault 698 @* const modalVoucherCodeValue = modalVoucherInput.value; *@ 699 document.cookie = `modalNewsletterConsentCookie=true; expires=${expirationDate}; path=/`; 700 @* document.cookie = `voucherCode=${modalVoucherCodeValue}; expires=${expirationDate}; path=/`; *@ 701 console.log("Form submitted from modal" + modalVoucherCodeValue) 702 modalForm.submit(); 703 }); 704 } 705 } 706 707 if (modalConsentCookie === "false&closed" && footerConsentCookie !== "true&closed") { 708 console.log("footer form"); 709 const form = document.querySelector("#newsletter-container form"); 710 const voucherInput = document.querySelector("#newslettersignupvouchercode"); 711 const voucherCodeElement = document.getElementById("voucher-code"); 712 const voucherCode = voucherCodeElement?.getAttribute("data-voucher"); 713 714 if (voucherCodeElement && voucherInput && voucherCode) { 715 voucherInput.value = voucherCode; 716 } 717 718 form.addEventListener('submit', (e) => { 719 e.preventDefault 720 @* const voucherCodeValue = voucherInput.value; *@ 721 document.cookie = `footerNewsletterConsentCookie=true&closed; expires=${expirationDate}; path=/`; 722 @* document.cookie = `voucherCode=${voucherCodeValue}; expires=${expirationDate}; path=/`; *@ 723 console.log("Form submitted from footer") 724 form.submit(); 725 }); 726 } 727 728 729 </script> 730 731 } 732 @* Offcanvas for dynamic content *@ 733 <div class="offcanvas offcanvas-end theme light" tabindex="-1" id="DynamicOffcanvas"> 734 @* The content here comes from an external request *@ 735 </div> 736 737 @if (Model.Area.Item.GetBoolean("ShowErpDownMessage") && !Dynamicweb.Core.Converter.ToBoolean(Context.Current.Items["IsWebServiceConnectionAvailable"])) 738 { 739 string erpDownMessageTheme = !string.IsNullOrWhiteSpace(Model.Area.Item.GetRawValueString("ErpDownMessageTheme")) ? " theme " + Model.Area.Item.GetRawValueString("ErpDownMessageTheme").Replace(" ", "").Trim().ToLower() : "theme light"; 740 741 <div class="position-fixed bottom-0 end-0 p-3" style="z-index: 1040"> 742 <div class="toast fade show border-0 @erpDownMessageTheme" role="alert" aria-live="assertive" aria-atomic="true"> 743 <div class="toast-header"> 744 <strong class="me-auto">@Translate("Connection down")</strong> 745 <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button> 746 </div> 747 <div class="toast-body"> 748 @Translate("We are experiencing some connectivity issues. Not all features may be available to you.") 749 </div> 750 </div> 751 </div> 752 } 753 754 @if (miniCartEnabled) 755 { 756 @* Open MiniCart when the cart is updated *@ 757 <script type="module"> 758 document.addEventListener('updated.swift.cart', (event) => { 759 let orderContext = event?.detail?.formData?.get("OrderContext"); 760 updateCartSummary(orderContext); 761 762 @if (offcanvasMiniCartBehaviour == "2" || offcanvasMiniCartBehaviour == "3") { 763 <text>openMiniCartOffcanvas();</text> 764 } 765 }); 766 </script> 767 768 if (offcanvasMiniCartBehaviour == "1" || offcanvasMiniCartBehaviour == "3") 769 { 770 @* Open MiniCart when toggle is clicked *@ 771 <script type="module"> 772 let miniCartToggles = document.querySelectorAll('.mini-cart-quantity'); 773 miniCartToggles?.forEach((toggle) => { 774 toggle.parentElement.addEventListener('click', (event) => { 775 event.preventDefault(); 776 let orderContext = toggle.dataset?.orderContext; 777 updateCartSummary(orderContext); 778 779 openMiniCartOffcanvas(); 780 }); 781 }); 782 </script> 783 } 784 785 <script> 786 787 const updateCartSummary = (orderContext) => { 788 const dynamicOffcanvas = document.getElementById('DynamicOffcanvas'); 789 swift.PageUpdater.UpdateFromUrlInline(event, '/Default.aspx?ID=@(cartSummaryPageId)&CartType=minicart&RequestPageID=@(Pageview.Page.ID)&OrderContext=' + orderContext +'', 'Swift_CartSummary.cshtml', dynamicOffcanvas); 790 }; 791 792 const openMiniCartOffcanvas = () => { 793 const dynamicOffcanvas = document.getElementById('DynamicOffcanvas'); 794 const miniCartOffcanvas = bootstrap.Offcanvas.getOrCreateInstance(dynamicOffcanvas); 795 dynamicOffcanvas.classList.add('overflow-y-auto'); 796 797 if (!miniCartOffcanvas._isShown) { 798 miniCartOffcanvas.show(); 799 hideActiveOffcanvases(miniCartOffcanvas); 800 } 801 }; 802 803 const hideActiveOffcanvases = (miniCartOffcanvas) => { 804 let activeOffcanvases = document.querySelectorAll('.offcanvas.show'); 805 activeOffcanvases?.forEach((offCanvas) => { 806 offCanvas = bootstrap.Offcanvas.getInstance(offCanvas); 807 if (offCanvas !== miniCartOffcanvas) { 808 offCanvas.hide(); 809 } 810 }); 811 }; 812 813 </script> 814 } 815 816 <script type="module"> 817 window.addEventListener('DOMContentLoaded', () => { 818 swiftCustom.ScrollTop.init(); 819 }); 820 </script> 821 <button class="scroll-top" onclick="swiftCustom.ScrollTop.scrollToTop()"> 822 <span class="icon-3">@ReadFile(iconPath + "arrow-up.svg")</span> 823 </button> 824 </body> 825 826 </html> 827 828 } 829 else if (Pageview.IsVisualEditorMode) 830 { 831 <head> 832 <title>@Model.Title</title> 833 @* Bootstrap + Swift stylesheet *@ 834 <link href="/Files/Templates/Designs/Swift/Assets/css/styles.css" rel="stylesheet" media="all" type="text/css"> 835 </head> 836 <body class="p-3"> 837 <div class="alert alert-danger" role="alert"> 838 @Translate("Basic Swift setup is needed!") 839 </div> 840 841 @if (brandingPage == null) 842 { 843 <div class="alert alert-warning" role="alert"> 844 @Translate("Please add a Branding page and reference it in website settings") 845 </div> 846 } 847 848 @if (themesParagraphs == null) 849 { 850 <div class="alert alert-warning" role="alert"> 851 @Translate("Please add a Themes collection page and reference it in website settings") 852 </div> 853 } 854 </body> 855 } 856 857 858 @functions { 859 void SetMetaTags() 860 { 861 //Verification Tokens 862 string siteVerificationGoogle = Model.Area.Item.GetString("Google_Site_Verification") != null ? Model.Area.Item.GetString("Google_Site_Verification") : ""; 863 864 //Generic Site Values 865 string openGraphFacebookAppID = Model.Area.Item.GetString("Fb_app_id") != null ? Model.Area.Item.GetString("Fb_app_id") : ""; 866 string openGraphType = Model.Area.Item.GetString("Open_Graph_Type") != null ? Model.Area.Item.GetString("Open_Graph_Type") : ""; 867 string openGraphSiteName = Model.Area.Item.GetString("Open_Graph_Site_Name") != null ? Model.Area.Item.GetString("Open_Graph_Site_Name") : ""; 868 869 string twitterCardSite = Model.Area.Item.GetString("Twitter_Site") != null ? Model.Area.Item.GetString("Twitter_Site") : ""; 870 871 //Page specific values 872 string openGraphSiteTitle = Model.Area.Item.GetString("Open_Graph_Title") != null ? Model.Area.Item.GetString("Open_Graph_Title") : ""; 873 FileViewModel openGraphImage = Model.Area.Item.GetFile("Open_Graph_Image"); 874 string openGraphImageALT = Model.Area.Item.GetString("Open_Graph_Image_ALT") != null ? Model.Area.Item.GetString("Open_Graph_Image_ALT") : ""; 875 string openGraphDescription = Model.Area.Item.GetString("Open_Graph_Description") != null ? Model.Area.Item.GetString("Open_Graph_Description") : ""; 876 877 string twitterCardURL = Model.Area.Item.GetString("Twitter_URL") != null ? Model.Area.Item.GetString("Twitter_URL") : ""; 878 string twitterCardTitle = Model.Area.Item.GetString("Twitter_Title") != null ? Model.Area.Item.GetString("Twitter_Title") : ""; 879 string twitterCardDescription = Model.Area.Item.GetString("Twitter_Description") != null ? Model.Area.Item.GetString("Twitter_Description") : ""; 880 FileViewModel twitterCardImage = Model.Area.Item.GetFile("Twitter_Image"); 881 string twitterCardImageALT = Model.Area.Item.GetString("Twitter_Image_ALT") != null ? Model.Area.Item.GetString("Twitter_Image_ALT") : ""; 882 string topImage = Pageview.Page.TopImage.StartsWith("/Files", StringComparison.OrdinalIgnoreCase) ? Pageview.Page.TopImage : $"/Files{Pageview.Page.TopImage}"; 883 884 if (string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString["ProductID"])) 885 { 886 if (!string.IsNullOrEmpty(Model.Description)) 887 { 888 Pageview.Meta.AddTag($"<meta property=\"og:description\" content=\"{Model.Description}\">"); 889 } 890 else 891 { 892 Pageview.Meta.AddTag($"<meta property=\"og:description\" content=\"{openGraphDescription}\">"); 893 } 894 895 if (!string.IsNullOrEmpty(Pageview.Page.TopImage)) 896 { 897 Pageview.Meta.AddTag($"<meta property=\"og:image\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{topImage}\">"); 898 Pageview.Meta.AddTag($"<meta property=\"og:image:secure_url\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{topImage}\">"); 899 } 900 else if (openGraphImage != null) 901 { 902 Pageview.Meta.AddTag($"<meta property=\"og:image\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{openGraphImage.Path}\">"); 903 Pageview.Meta.AddTag($"<meta property=\"og:image:secure_url\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{openGraphImage.Path}\">"); 904 } 905 906 if (!string.IsNullOrEmpty(openGraphImageALT)) 907 { 908 Pageview.Meta.AddTag($"<meta property=\"og:image:alt\" content=\"{openGraphImageALT}\">"); 909 } 910 if (!string.IsNullOrEmpty(twitterCardDescription)) 911 { 912 Pageview.Meta.AddTag("twitter:description", twitterCardDescription); 913 } 914 915 if (!string.IsNullOrEmpty(Pageview.Page.TopImage)) 916 { 917 Pageview.Meta.AddTag("twitter:image", $"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{topImage}"); 918 } 919 else if (twitterCardImage != null) 920 { 921 Pageview.Meta.AddTag("twitter:image", $"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{openGraphImage.Path}"); 922 } 923 924 if (!string.IsNullOrEmpty(twitterCardImageALT)) 925 { 926 Pageview.Meta.AddTag("twitter:image:alt", twitterCardImageALT); 927 } 928 } 929 930 if (!string.IsNullOrEmpty(siteVerificationGoogle)) 931 { 932 Pageview.Meta.AddTag("google-site-verification", siteVerificationGoogle); 933 } 934 935 if (!string.IsNullOrEmpty(openGraphFacebookAppID)) 936 { 937 Pageview.Meta.AddTag($"<meta property=\"fb:app_id\" content=\"{openGraphFacebookAppID}\">"); 938 } 939 940 if (!string.IsNullOrEmpty(openGraphType)) 941 { 942 Pageview.Meta.AddTag($"<meta property=\"og:type\" content=\"{openGraphType}\">"); 943 } 944 945 if (!string.IsNullOrEmpty(openGraphSiteName)) 946 { 947 Pageview.Meta.AddTag($"<meta property=\"og:url\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{Pageview.SearchFriendlyUrl}\">"); 948 } 949 950 if (!string.IsNullOrEmpty(openGraphSiteName)) 951 { 952 Pageview.Meta.AddTag($"<meta property=\"og:site_name\" content=\"{openGraphSiteName}\">"); 953 } 954 955 if (!string.IsNullOrEmpty(Model.Title)) 956 { 957 Pageview.Meta.AddTag($"<meta property=\"og:title\" content=\"{Model.Title}\">"); 958 } 959 else 960 { 961 Pageview.Meta.AddTag($"<meta property=\"og:title\" content=\"{openGraphSiteTitle}\">"); 962 } 963 964 if (!string.IsNullOrEmpty(twitterCardSite)) 965 { 966 Pageview.Meta.AddTag("twitter:site", twitterCardSite); 967 } 968 969 if (!string.IsNullOrEmpty(twitterCardURL)) 970 { 971 Pageview.Meta.AddTag("twitter:url", twitterCardURL); 972 } 973 974 if (!string.IsNullOrEmpty(twitterCardTitle)) 975 { 976 Pageview.Meta.AddTag("twitter:title", twitterCardTitle); 977 } 978 } 979 } 980