/// <summary> /// Get markup for a specific campaign /// </summary> /// <param name="campaignContentZip"></param> /// <param name="localization"></param> /// <returns></returns> public CampaignContentMarkup GetCampaignContentMarkup(CampaignContentZIP campaignContentZip, Localization localization) { StaticContentItem zipItem = GetZipItem(campaignContentZip, localization); CampaignContentMarkup campaignContentMarkup; //SiteConfiguration.CacheProvider.TryGet<CampaignContentMarkup>(campaignContentZip.Id, "CampaignContent", out campaignContentMarkup); string cacheKey = GetMarkupCacheKey(campaignContentZip.Id, localization); cachedMarkup.TryGetValue(cacheKey, out campaignContentMarkup); string campaignBaseDir = GetBaseDir(localization, campaignContentZip.Id); if (campaignContentMarkup == null || !Directory.Exists(campaignBaseDir) || Directory.GetFiles(campaignBaseDir).Length == 0) { Log.Info("Extracting campaign " + campaignContentZip.Id + ", last modified = " + zipItem.LastModified); ExtractZip(zipItem, campaignBaseDir, zipItem.LastModified); campaignContentMarkup = GetMarkup(campaignBaseDir); campaignContentMarkup.LastModified = zipItem.LastModified; //SiteConfiguration.CacheProvider.Store<CampaignContentMarkup>(campaignContentZip.Id, "CampaignContent", campaignContentMarkup); cachedMarkup[cacheKey] = campaignContentMarkup; } else if (campaignContentMarkup == null) { campaignContentMarkup = GetMarkup(campaignBaseDir); campaignContentMarkup.LastModified = zipItem.LastModified; cachedMarkup[cacheKey] = campaignContentMarkup; } return(campaignContentMarkup); }
/// <summary> /// Get ZIP item. /// </summary> /// <param name="campaignContentZip"></param> /// <returns></returns> protected ZipItem GetZipItem(CampaignContentZIP campaignContentZip, Localization localization) { var item = SiteConfiguration.ContentProvider.GetStaticContentItem(campaignContentZip.Url, localization); return(new ZipItem { ContentItem = item, UrlPath = campaignContentZip.Url, LocalFileName = GetBinaryLocalFileName(campaignContentZip.Url, localization) }); }
/// <summary> /// Assembly Content /// </summary> /// <param name="entity"></param> /// <param name="containerSize"></param> /// <returns></returns> public ActionResult AssemblyContent(EntityModel entity, int containerSize = 0) { SetupViewData(entity, containerSize); CampaignContentZIP contentZip = (CampaignContentZIP)entity; // Process markup // ProcessMarkup(contentZip); return(View(entity.MvcData.ViewName, entity)); }
/// <summary> /// Process markup. Blend the markup in the ZIP file and CMS content. /// </summary> /// <param name="campaignContentZip"></param> protected void ProcessMarkup(CampaignContentZIP campaignContentZip) { CampaignContentMarkup campaignContentMarkup = CampaignAssetProvider.Instance.GetCampaignContentMarkup(campaignContentZip, WebRequestContext.Localization); // Throw exception if main HTML is not found // if (campaignContentMarkup.MainHtml == null) { throw new DxaException("No markup defined for campaign with ID: " + campaignContentZip.Id); } var htmlDoc = NSoupClient.Parse("<body>" + campaignContentMarkup.MainHtml + "</body>"); // Remove all nodes that are marked as Preview Only // if (!WebRequestContext.IsPreview) { foreach (var element in htmlDoc.Body.Select("[data-preview-only=true]")) { element.Remove(); } } // Inject content into placeholders in the markup // if (campaignContentZip.TaggedContent != null) { int index = 1; foreach (var taggedContent in campaignContentZip.TaggedContent) { foreach (var element in htmlDoc.Body.Select("[data-content-name=" + taggedContent.Name + "]")) { String contentMarkup = taggedContent.Content?.ToString() ?? string.Empty; if (WebRequestContext.IsPreview) { contentMarkup = "<!-- Start Component Field: {\"XPath\":\"tcm:Metadata/custom:Metadata/custom:taggedContent[" + index + "]/custom:content[1]\"} -->" + contentMarkup; } element.Html(contentMarkup); } index++; } } // Inject tagged properties // if (campaignContentZip.TaggedProperties != null) { int index = 1; foreach (var taggedProperty in campaignContentZip.TaggedProperties) { var indexSuffix = taggedProperty.Index != null && taggedProperty.Index > 1 ? "-" + taggedProperty.Index : ""; foreach (var element in htmlDoc.Body.Select("[data-property-name" + indexSuffix + "=" + taggedProperty.Name + "]")) { var propertyValue = taggedProperty.Value; var containsUrlPlaceholder = propertyValue.Contains("%URL%"); if (taggedProperty.Image != null) { propertyValue = propertyValue.Replace("%URL%", taggedProperty.Image.Url); if (element.TagName().Equals("img", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(taggedProperty.ImageAltText)) { element.Attr("alt", taggedProperty.ImageAltText); } if (element.Attr("data-property-sibling-replace") == "true") { foreach (var sibling in element.SiblingElements) { var siblingPropertyValue = sibling.Attr(taggedProperty.Target) ?? string.Empty; siblingPropertyValue = siblingPropertyValue.Replace("%URL%", taggedProperty.Image.Url); sibling.Attr(taggedProperty.Target, siblingPropertyValue); if (!string.IsNullOrEmpty(taggedProperty.ImageAltText) && sibling.TagName().Equals("img", StringComparison.OrdinalIgnoreCase)) { sibling.Attr("alt", taggedProperty.ImageAltText); } } } } element.Attr(taggedProperty.Target, propertyValue ?? string.Empty); // Generate image XPM markup if tagged property can contain/contains an image URL // if (WebRequestContext.IsPreview && containsUrlPlaceholder) { string xpmMarkup = "<!-- Start Component Field: {\"XPath\":\"tcm:Metadata/custom:Metadata/custom:taggedProperties[" + index + "]/custom:image[1]\"} -->"; element.Prepend(xpmMarkup); } } index++; } } // Inject tagged links // if (campaignContentZip.TaggedLinks != null) { int index = 1; foreach (var taggedLink in campaignContentZip.TaggedLinks) { foreach (var element in htmlDoc.Body.Select("[data-link-name=" + taggedLink.Name + "]")) { var link = taggedLink.ComponentLink ?? taggedLink.Url; element.Attr("href", link ?? "#"); if (!string.IsNullOrEmpty(taggedLink.Target)) { element.Attr("target", taggedLink.Target); } if (WebRequestContext.IsPreview) { var fieldName = taggedLink.ComponentLink != null || string.IsNullOrWhiteSpace(taggedLink.Url) ? "componentLink" : "url"; string xpmMarkup = "<!-- Start Component Field: {\"XPath\":\"tcm:Metadata/custom:Metadata/custom:taggedLinks[" + index + "]/custom:" + fieldName + "[1]\"} -->"; element.Prepend(xpmMarkup); } } index++; } } string assetBaseDir = this.GetAssetBaseDir(campaignContentZip); // Process assets // this.ProcessAssetLinks(htmlDoc, assetBaseDir, "href"); this.ProcessAssetLinks(htmlDoc, assetBaseDir, "src"); // Inject tagged images // if (campaignContentZip.TaggedImages != null) { int index = 1; foreach (var taggedImage in campaignContentZip.TaggedImages) { foreach (var element in htmlDoc.Body.Select("[data-image-name=" + taggedImage.Name + "]")) { var imageUrl = string.Empty; if (taggedImage.Image != null && taggedImage.Image.Url != null) { imageUrl = taggedImage.Image.Url; } else if (taggedImage.ImageUrl != null) { imageUrl = taggedImage.ImageUrl; } if (taggedImage.Parameters != null && (taggedImage.Image == null || (taggedImage.Image != null && _useUrlParametersForLocalImages))) { imageUrl += "?" + taggedImage.Parameters; } element.Attr("src", imageUrl); if (!string.IsNullOrEmpty(taggedImage.AltText)) { element.Attr("alt", taggedImage.AltText); } if (WebRequestContext.IsPreview) { string xpmMarkup = "<!-- Start Component Field: {\"XPath\":\"tcm:Metadata/custom:Metadata/custom:taggedImages[" + index + "]/custom:image[1]\"} -->"; if (taggedImage.Image != null) { element.Before(xpmMarkup); } else { // Surround the XPM markup in an additional span. This to avoid that the // image with absolute URL will not disappear as soon you click on it. // element.Before("<span>" + xpmMarkup + "</span>"); } } } index++; } } // Insert header markup (JS, CSS etc) // if (campaignContentMarkup.HeaderHtml != null) { var headerDoc = NSoupClient.Parse("<body>" + campaignContentMarkup.HeaderHtml + "</body>"); this.ProcessAssetLinks(headerDoc, assetBaseDir, "src"); this.ProcessAssetLinks(headerDoc, assetBaseDir, "href"); PluggableMarkup.RegisterMarkup("css", headerDoc.Body.Html()); // TODO: Should it be called top-js?? Or have a top-css injection point as well } // Insert footer markup (JS etc) // if (campaignContentMarkup.FooterHtml != null) { var footerDoc = NSoupClient.Parse("<body>" + campaignContentMarkup.FooterHtml + "</body>"); this.ProcessAssetLinks(footerDoc, assetBaseDir, "src"); PluggableMarkup.RegisterMarkup("bottom-js", footerDoc.Body.Html()); } campaignContentZip.ProcessedContent = new RichTextFragment(htmlDoc.Body.Html()).ToHtml(); }
/// <summary> /// Get asset base dir (which are exposed on the web page) for the specified campaign ZIP /// </summary> /// <param name="campaignContentZip"></param> /// <returns></returns> protected String GetAssetBaseDir(CampaignContentZIP campaignContentZip) { return(_siteBaseUrlProvider.GetSiteBaseUrl() + "/assets/campaign/" + campaignContentZip.Id); }
/// <summary> /// Get ZIP item. /// </summary> /// <param name="campaignContentZip"></param> /// <returns></returns> protected StaticContentItem GetZipItem(CampaignContentZIP campaignContentZip, Localization localization) { return(SiteConfiguration.ContentProvider.GetStaticContentItem(campaignContentZip.Url, localization)); }
/// <summary> /// Process markup. Blend the markup in the ZIP file and CMS content. /// </summary> /// <param name="campaignContentZip"></param> protected void ProcessMarkup(CampaignContentZIP campaignContentZip) { CampaignContentMarkup campaignContentMarkup = CampaignAssetProvider.Instance.GetCampaignContentMarkup(campaignContentZip, WebRequestContext.Localization); // Throw exception if main HTML is not found // if (campaignContentMarkup.MainHtml == null) { throw new DxaException("No markup defined for campaign with ID: " + campaignContentZip.Id); } var htmlDoc = NSoupClient.Parse("<body>" + campaignContentMarkup.MainHtml + "</body>"); // Inject content into placeholders in the markup // if (campaignContentZip.TaggedContent != null) { int index = 1; foreach (var taggedContent in campaignContentZip.TaggedContent) { foreach (var element in htmlDoc.Body.Select("[data-content-name=" + taggedContent.Name + "]")) { String contentMarkup = "<!-- Start Component Field: {\"XPath\":\"tcm:Metadata/custom:Metadata/custom:taggedContent[" + index + "]/custom:content[1]\"} -->" + taggedContent.Content.ToString(); element.Html(contentMarkup); } index++; } } // Inject tagged properties // if (campaignContentZip.TaggedProperties != null) { foreach (var taggedProperty in campaignContentZip.TaggedProperties) { var indexSuffix = taggedProperty.Index != null && taggedProperty.Index > 1 ? "-" + taggedProperty.Index : ""; foreach (var element in htmlDoc.Body.Select("[data-property-name" + indexSuffix + "=" + taggedProperty.Name + "]")) { element.Attr(taggedProperty.Target, taggedProperty.Value); } } } string assetBaseDir = this.GetAssetBaseDir(campaignContentZip); // Process assets // this.ProcessAssetLinks(htmlDoc, assetBaseDir, "href"); this.ProcessAssetLinks(htmlDoc, assetBaseDir, "src"); // Inject tagged images // if (campaignContentZip.TaggedImages != null) { int index = 1; foreach (var taggedImage in campaignContentZip.TaggedImages) { foreach (var element in htmlDoc.Body.Select("[data-image-name=" + taggedImage.Name + "]")) { String xpmMarkup = "<!-- Start Component Field: {\"XPath\":\"tcm:Metadata/custom:Metadata/custom:taggedImages[" + index + "]/custom:image[1]\"} -->"; element.Attr("src", taggedImage.Image.Url); element.Before(xpmMarkup); } index++; } } // Insert header markup (JS, CSS etc) // if (campaignContentMarkup.HeaderHtml != null) { var headerDoc = NSoupClient.Parse("<body>" + campaignContentMarkup.HeaderHtml + "</body>"); this.ProcessAssetLinks(headerDoc, assetBaseDir, "src"); this.ProcessAssetLinks(headerDoc, assetBaseDir, "href"); PluggableMarkup.RegisterMarkup("css", headerDoc.Body.Html()); // TODO: Should it be called top-js?? Or have a top-css injection point as well } // Insert footer markup (JS etc) // if (campaignContentMarkup.FooterHtml != null) { var footerDoc = NSoupClient.Parse("<body>" + campaignContentMarkup.FooterHtml + "</body>"); this.ProcessAssetLinks(footerDoc, assetBaseDir, "src"); PluggableMarkup.RegisterMarkup("bottom-js", footerDoc.Body.Html()); } campaignContentZip.ProcessedContent = new RichTextFragment(htmlDoc.Body.Html()).ToHtml(); }
/// <summary> /// Load markup for a specific campaign /// </summary> /// <param name="campaignContentZip"></param> /// <param name="campaignId"></param> /// <param name="localization"></param> /// <returns></returns> protected CampaignContentMarkup LoadCampaignContentMarkup(CampaignContentZIP campaignContentZip, string campaignId, Localization localization) { CampaignContentMarkup campaignContentMarkup; string cacheKey = GetMarkupCacheKey(campaignId, localization); CachedMarkup.TryGetValue(cacheKey, out campaignContentMarkup); string campaignBaseDir = GetBaseDir(localization, campaignId); if (campaignContentMarkup != null) { var zipFileLastModified = File.GetLastWriteTime(campaignContentMarkup.ZipFileName); if (!localization.IsXpmEnabled && zipFileLastModified > campaignContentMarkup.LastModified || localization.IsXpmEnabled && campaignContentMarkup.LastModified.AddSeconds(stagingCacheTime) < DateTime.Now) { Log.Info("Zip has changed. Extracting campaign " + campaignId + ", last modified = " + zipFileLastModified); var fileLock = GetFileSemaphore(campaignBaseDir); // If no other thread is already working on it. Else keep using the existing version until the new one is available. // if (fileLock.CurrentCount > 0) { using (fileLock.UseWait()) { try { var zipItem = campaignContentZip != null?GetZipItem(campaignContentZip, localization) : GetZipItem(campaignId, localization); if (ExtractZip(zipItem.ContentItem, campaignBaseDir, zipItem.ContentItem.LastModified)) { campaignContentMarkup = GetMarkup(campaignBaseDir); campaignContentMarkup.LastModified = zipItem.ContentItem.LastModified; campaignContentMarkup.ZipFileName = zipItem.LocalFileName; CachedMarkup[cacheKey] = campaignContentMarkup; } } catch (Exception e) { Log.Error($"Could not get/extract ZIP file. Retry next time the campaign is accessed. \nException: {e}"); } } } } } else { var fileLock = GetFileSemaphore(campaignBaseDir); using (fileLock.UseWait()) { var zipItem = campaignContentZip != null?GetZipItem(campaignContentZip, localization) : GetZipItem(campaignId, localization); if (zipItem == null) { // Non-existing campaign. // return(null); } if (!Directory.Exists(campaignBaseDir) || zipItem.ContentItem.LastModified > File.GetLastWriteTime(campaignBaseDir) || Directory.GetFiles(campaignBaseDir).Length == 0) { Log.Info("Extracting campaign " + campaignId + ", last modified = " + zipItem.ContentItem.LastModified); if (!ExtractZip(zipItem.ContentItem, campaignBaseDir, zipItem.ContentItem.LastModified)) { throw new DxaException($"Could not extract campaign with ID: {campaignId}"); } } campaignContentMarkup = GetMarkup(campaignBaseDir); campaignContentMarkup.LastModified = zipItem.ContentItem.LastModified; campaignContentMarkup.ZipFileName = zipItem.LocalFileName; CachedMarkup[cacheKey] = campaignContentMarkup; } } return(campaignContentMarkup); }
/// <summary> /// Get markup for a specific campaign /// </summary> /// <param name="campaignContentZip"></param> /// <param name="localization"></param> /// <returns></returns> public CampaignContentMarkup GetCampaignContentMarkup(CampaignContentZIP campaignContentZip, Localization localization) { return(LoadCampaignContentMarkup(campaignContentZip, campaignContentZip.Id, localization)); }