/// <summary> /// Post-processes the Page Model constructed by the <see cref="DefaultModelBuilder"/>. /// </summary> /// <remarks> /// This implementation relies on the <see cref="DefaultModelBuilder"/> already having constructed Region Models of type <see cref="SmartTargetRegion"/>. /// We "upgrade" the Page Model to type <see cref="SmartTargetPageModel"/> and populate the ST Regions <see cref="SmartTargetPromotion"/> Entities. /// </remarks> public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable <IPage> includes, ILocalization localization) { using (new Tracer(pageModel, page, includes, localization)) { if ((pageModel == null) || !pageModel.Regions.OfType <SmartTargetRegion>().Any()) { Log.Debug("No SmartTarget Regions on Page."); return; } if ((page?.PageTemplate?.MetadataFields == null) || !page.PageTemplate.MetadataFields.ContainsKey("regions")) { Log.Debug("No Regions metadata found."); return; } // "Upgrade" the PageModel to a SmartTargetPageModel, so we can store AllowDuplicates in the Page Model. SmartTargetPageModel smartTargetPageModel = new SmartTargetPageModel(pageModel) { AllowDuplicates = GetAllowDuplicatesOnSamePage(page.PageTemplate, localization), NoCache = true // Don't cache the Page Model, because its contents are dynamic. }; pageModel = smartTargetPageModel; // Set SmartTargetRegionModel.MaxItem based on the Region Metadata in the Page Template. foreach (IFieldSet smartTargetRegionField in page.PageTemplate.MetadataFields["regions"].EmbeddedValues) { string regionName; IField regionNameField; if (smartTargetRegionField.TryGetValue("name", out regionNameField) && !string.IsNullOrEmpty(regionNameField.Value)) { regionName = regionNameField.Value; } else { regionName = new MvcData(smartTargetRegionField["view"].Value).ViewName; } SmartTargetRegion smartTargetRegion = smartTargetPageModel.Regions[regionName] as SmartTargetRegion; if (smartTargetRegion != null) { int maxItems = 100; // Default IField maxItemsField; if (smartTargetRegionField.TryGetValue("maxItems", out maxItemsField)) { maxItems = Convert.ToInt32(maxItemsField.NumericValues[0]); } smartTargetRegion.MaxItems = maxItems; } } PopulateSmartTargetRegions(smartTargetPageModel, localization); } }
/// <summary> /// Ensures that the Component Fields of DCPs on the Page are populated. /// </summary> private static void FullyLoadDynamicComponentPresentations(IPage page, Localization localization) { using (new Tracer(page, localization)) { foreach (ComponentPresentation dcp in page.ComponentPresentations.Where(cp => cp.IsDynamic).OfType <ComponentPresentation>()) { IComponentFactory componentFactory = DD4TFactoryCache.GetComponentFactory(localization); dcp.Component = (Component)componentFactory.GetComponent(dcp.Component.Id, dcp.ComponentTemplate.Id); } } }
/// <summary> /// Creates a Strongly Typed Page Model for a given DD4T Page and an optional set of include Pages. /// </summary> /// <param name="page">The DD4T Page object.</param> /// <param name="includes">The set of DD4T Page object for include pages. Can be <c>null</c>.</param> /// <param name="localization">The context <see cref="Localization"/>.</param> /// <returns>The Strongly Typed Page Model (an instance of class <see cref="PageModel"/> or a subclass).</returns> public static PageModel CreatePageModel(IPage page, IPage[] includes, Localization localization) { using (new Tracer(page, includes, localization)) { PageModel pageModel = null; foreach (IModelBuilder modelBuilder in ModelBuilders) { modelBuilder.BuildPageModel(ref pageModel, page, includes, localization); } return(pageModel); } }
protected virtual IEnumerable <IPage> GetIncludesFromModel(IPage page, Localization localization) { using (new Tracer(page.Id, localization)) { List <IPage> result = new List <IPage>(); string[] pageTemplateTcmUriParts = page.PageTemplate.Id.Split('-'); IEnumerable <string> includePageUrls = localization.GetIncludePageUrls(pageTemplateTcmUriParts[1]); foreach (string includePageUrl in includePageUrls) { IPage includePage = GetPage(localization.GetAbsoluteUrlPath(includePageUrl), localization); if (includePage == null) { Log.Error("Include Page '{0}' not found.", includePageUrl); continue; } result.Add(includePage); } return(result); } }
protected virtual IEnumerable <IPage> GetIncludesFromModel(IPage page, Localization localization) { List <IPage> result = new List <IPage>(); string[] pageTemplateTcmUriParts = page.PageTemplate.Id.Split('-'); IEnumerable <string> includePageUrls = SiteConfiguration.GetIncludePageUrls(pageTemplateTcmUriParts[1], localization); foreach (string includePageUrl in includePageUrls) { IPage includePage = GetPage(SiteConfiguration.LocalizeUrl(includePageUrl, localization), localization); if (includePage == null) { Log.Error("Include Page '{0}' not found.", includePageUrl); continue; } FullyLoadDynamicComponentPresentations(includePage, localization); result.Add(includePage); } return(result); }
#pragma warning restore 618 /// <summary> /// Gets a Page Model for a given URL. /// </summary> /// <param name="url">The URL.</param> /// <param name="localization">The context Localization.</param> /// <param name="addIncludes">Indicates whether include Pages should be expanded.</param> /// <returns>The Page Model.</returns> /// <exception cref="DxaItemNotFoundException">If no Page Model exists for the given URL.</exception> public virtual PageModel GetPageModel(string url, Localization localization, bool addIncludes) { using (new Tracer(url, localization, addIncludes)) { //We can have a couple of tries to get the page model if there is no file extension on the url request, but it does not end in a slash: //1. Try adding the default extension, so /news becomes /news.html IPage page = GetPage(url, localization); if (page == null && (url == null || (!url.EndsWith("/") && url.LastIndexOf(".", StringComparison.Ordinal) <= url.LastIndexOf("/", StringComparison.Ordinal)))) { //2. Try adding the default page, so /news becomes /news/index.html page = GetPage(url + "/", localization); } if (page == null) { throw new DxaItemNotFoundException(url); } FullyLoadDynamicComponentPresentations(page, localization); IPage[] includes = addIncludes ? GetIncludesFromModel(page, localization).ToArray() : new IPage[0]; return(ModelBuilderPipeline.CreatePageModel(page, includes, localization)); } }
/// <summary> /// Post-processes the Page Model constructed by the <see cref="DefaultModelBuilder"/>. /// </summary> /// <remarks> /// This implementation relies on the <see cref="DefaultModelBuilder"/> already having constructed Region Models of type <see cref="SmartTargetRegion"/>. /// We "upgrade" the Page Model to type <see cref="SmartTargetPageModel"/> and populate the ST Regions <see cref="SmartTargetPromotion"/> Entities. /// </remarks> public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable<IPage> includes, Localization localization) { using (new Tracer(pageModel, page, includes, localization)) { if (pageModel == null || !pageModel.Regions.OfType<SmartTargetRegion>().Any()) { Log.Debug("No SmartTarget Regions on Page."); return; } if (page == null || page.PageTemplate == null || page.PageTemplate.MetadataFields == null || !page.PageTemplate.MetadataFields.ContainsKey("regions")) { Log.Debug("No Regions metadata found."); return; } // "Upgrade" the PageModel to a SmartTargetPageModel, so we can store AllowDuplicates in the Page Model. SmartTargetPageModel smartTargetPageModel = new SmartTargetPageModel(pageModel) { AllowDuplicates = GetAllowDuplicatesOnSamePage(page.PageTemplate, localization), NoCache = true // Don't cache the Page Model, because its contents are dynamic. }; pageModel = smartTargetPageModel; // Set SmartTargetRegionModel.MaxItem based on the Region Metadata in the Page Template. foreach (IFieldSet smartTargetRegionField in page.PageTemplate.MetadataFields["regions"].EmbeddedValues) { string regionName; IField regionNameField; if (smartTargetRegionField.TryGetValue("name", out regionNameField) && !String.IsNullOrEmpty(regionNameField.Value)) { regionName = regionNameField.Value; } else { regionName = new MvcData(smartTargetRegionField["view"].Value).ViewName; } SmartTargetRegion smartTargetRegion = smartTargetPageModel.Regions[regionName] as SmartTargetRegion; if (smartTargetRegion != null) { int maxItems = 100; // Default IField maxItemsField; if (smartTargetRegionField.TryGetValue("maxItems", out maxItemsField)) { maxItems = Convert.ToInt32(maxItemsField.NumericValues[0]); } smartTargetRegion.MaxItems = maxItems; } } // Execute a ST Query for all SmartTargetRegions on the Page. ResultSet resultSet = ExecuteSmartTargetQuery(smartTargetPageModel, localization); Log.Debug("SmartTarget query returned {0} Promotions.", resultSet.Promotions.Count); string promotionViewName = localization.GetConfigValue(PromotionViewNameConfig); if (String.IsNullOrEmpty(promotionViewName)) { Log.Warn("No View name for SmartTarget Promotions is configured on CM-side ({0})", PromotionViewNameConfig); promotionViewName = "SmartTarget:Entity:Promotion"; } Log.Debug("Using Promotion View '{0}'", promotionViewName); // TODO: we shouldn't access HttpContext in a Model Builder. HttpContext httpContext = HttpContext.Current; if (httpContext == null) { throw new DxaException("HttpContext is not available."); } List<string> itemsAlreadyOnPage = new List<string>(); ExperimentCookies existingExperimentCookies = CookieProcessor.GetExperimentCookies(httpContext.Request); ExperimentCookies newExperimentCookies = new ExperimentCookies(); // Filter the Promotions for each SmartTargetRegion foreach (SmartTargetRegion smartTargetRegion in smartTargetPageModel.Regions.OfType<SmartTargetRegion>()) { string regionName = smartTargetRegion.Name; List<string> itemsOutputInRegion = new List<string>(); ExperimentDimensions experimentDimensions; List<Promotion> promotions = new List<Promotion>(resultSet.Promotions); ResultSet.FilterPromotions(promotions, regionName, smartTargetRegion.MaxItems, smartTargetPageModel.AllowDuplicates, itemsOutputInRegion, itemsAlreadyOnPage, ref existingExperimentCookies, ref newExperimentCookies, out experimentDimensions); if (experimentDimensions != null) { // The SmartTarget API doesn't set all ExperimentDimensions properties, but they are required by the ExperimentTrackingHandler (see CRQ-1667). experimentDimensions.PublicationId = string.Format("tcm:0-{0}-1", localization.LocalizationId); experimentDimensions.PageId = string.Format("tcm:{0}-{1}-64", localization.LocalizationId, smartTargetPageModel.Id); experimentDimensions.Region = smartTargetRegion.Name; } if (localization.IsStaging) { // The SmartTarget API provides the entire XPM markup tag; put it in XpmMetadata["Query"]. See SmartTargetRegion.GetStartQueryXpmMarkup. smartTargetRegion.XpmMetadata = new Dictionary<string, object> { {"Query", ResultSet.GetExperienceManagerMarkup(smartTargetRegion.Name, smartTargetRegion.MaxItems, promotions)} }; } // Create SmartTargetPromotion Entity Models for visible Promotions in the current SmartTargetRegion. // It seems that ResultSet.FilterPromotions doesn't really filter on Region name, so we do post-filtering here. foreach (Promotion promotion in promotions.Where(promotion => promotion.Visible && promotion.Regions.Contains(regionName))) { SmartTargetPromotion smartTargetPromotion = CreatePromotionEntity(promotion, promotionViewName, smartTargetRegion.Name, localization, experimentDimensions); if (!smartTargetRegion.HasSmartTargetContent) { // Discard any fallback content coming from Content Manager smartTargetRegion.Entities.Clear(); smartTargetRegion.HasSmartTargetContent = true; } smartTargetRegion.Entities.Add(smartTargetPromotion); } } if (newExperimentCookies.Count > 0) { smartTargetPageModel.ExperimentCookies = newExperimentCookies; } } }
/// <summary> /// Post-processes the Page Model constructed by the <see cref="DefaultModelBuilder"/>. /// </summary> /// <remarks> /// This implementation relies on the <see cref="DefaultModelBuilder"/> already having constructed Region Models of type <see cref="SmartTargetRegion"/>. /// We "upgrade" the Page Model to type <see cref="SmartTargetPageModel"/> and populate the ST Regions <see cref="SmartTargetPromotion"/> Entities. /// </remarks> public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable <IPage> includes, Localization localization) { using (new Tracer(pageModel, page, includes, localization)) { if (pageModel == null || !pageModel.Regions.OfType <SmartTargetRegion>().Any()) { Log.Debug("No SmartTarget Regions on Page."); return; } if (page == null || page.PageTemplate == null || page.PageTemplate.MetadataFields == null || !page.PageTemplate.MetadataFields.ContainsKey("regions")) { Log.Debug("No Regions metadata found."); return; } // "Upgrade" the PageModel to a SmartTargetPageModel, so we can store AllowDuplicates in the Page Model. SmartTargetPageModel smartTargetPageModel = new SmartTargetPageModel(pageModel) { AllowDuplicates = GetAllowDuplicatesOnSamePage(page.PageTemplate, localization) }; pageModel = smartTargetPageModel; // Set SmartTargetRegionModel.MaxItem based on the Region Metadata in the Page Template. foreach (IFieldSet smartTargetRegionField in page.PageTemplate.MetadataFields["regions"].EmbeddedValues) { string regionName; IField regionNameField; if (smartTargetRegionField.TryGetValue("name", out regionNameField) && !String.IsNullOrEmpty(regionNameField.Value)) { regionName = regionNameField.Value; } else { regionName = new MvcData(smartTargetRegionField["view"].Value).ViewName; } SmartTargetRegion smartTargetRegion = smartTargetPageModel.Regions[regionName] as SmartTargetRegion; if (smartTargetRegion != null) { int maxItems = smartTargetRegionField.ContainsKey("maxItems") ? Convert.ToInt32(smartTargetRegionField["maxItems"].Value) : 100; smartTargetRegion.MaxItems = maxItems; } } // Execute a ST Query for all SmartTargetRegions on the Page. ResultSet resultSet = ExecuteSmartTargetQuery(smartTargetPageModel, localization); Log.Debug("SmartTarget query returned {0} Promotions.", resultSet.Promotions.Count); string promotionViewName = localization.GetConfigValue(PromotionViewNameConfig); if (String.IsNullOrEmpty(promotionViewName)) { Log.Warn("No View name for SmartTarget Promotions is configured on CM-side ({0})", PromotionViewNameConfig); promotionViewName = "SmartTarget:Entity:Promotion"; } Log.Debug("Using Promotion View '{0}'", promotionViewName); List <string> itemsAlreadyOnPage = new List <string>(); ExperimentCookies existingExperimentCookies = CookieProcessor.GetExperimentCookies(HttpContext.Current.Request); // TODO: we shouldn't access HttpContext in a Model Builder. // Filter the Promotions for each SmartTargetRegion foreach (SmartTargetRegion smartTargetRegion in smartTargetPageModel.Regions.OfType <SmartTargetRegion>()) { string regionName = smartTargetRegion.Name; List <string> itemsOutputInRegion = new List <string>(); ExperimentCookies newExperimentCookies = new ExperimentCookies(); ExperimentDimensions experimentDimensions; List <Promotion> promotions = new List <Promotion>(resultSet.Promotions); ResultSet.FilterPromotions(promotions, regionName, smartTargetRegion.MaxItems, smartTargetPageModel.AllowDuplicates, itemsOutputInRegion, itemsAlreadyOnPage, ref existingExperimentCookies, ref newExperimentCookies, out experimentDimensions); if (localization.IsStaging) { // The SmartTarget API provides the entire XPM markup tag; put it in XpmMetadata["Query"]. See SmartTargetRegion.GetStartQueryXpmMarkup. smartTargetRegion.XpmMetadata = new Dictionary <string, object> { { "Query", ResultSet.GetExperienceManagerMarkup(smartTargetRegion.Name, smartTargetRegion.MaxItems, promotions) } }; } // Create SmartTargetPromotion Entity Models for visible Promotions in the current SmartTargetRegion. // It seems that ResultSet.FilterPromotions doesn't really filter on Region name, so we do post-filtering here. foreach (Promotion promotion in promotions.Where(promotion => promotion.Visible && promotion.Region.Contains(regionName))) { SmartTargetPromotion smartTargetPromotion = CreatePromotionEntity(promotion, promotionViewName, smartTargetRegion.Name, localization); if (!smartTargetRegion.HasSmartTargetContent) { // Discard any fallback content coming from Content Manager smartTargetRegion.Entities.Clear(); smartTargetRegion.HasSmartTargetContent = true; } smartTargetRegion.Entities.Add(smartTargetPromotion); } } } }
public virtual void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable<IPage> includes, Localization localization) { using (new Tracer(pageModel, page, includes, localization)) { pageModel = CreatePageModel(page, localization); RegionModelSet regions = pageModel.Regions; // Create predefined Regions from Page Template Metadata CreatePredefinedRegions(regions, page.PageTemplate); // Create Regions/Entities from Component Presentations IConditionalEntityEvaluator conditionalEntityEvaluator = SiteConfiguration.ConditionalEntityEvaluator; foreach (IComponentPresentation cp in page.ComponentPresentations) { MvcData cpRegionMvcData = GetRegionMvcData(cp); RegionModel region; if (regions.TryGetValue(cpRegionMvcData.ViewName, out region)) { // Region already exists in Page Model; MVC data should match. if (!region.MvcData.Equals(cpRegionMvcData)) { Log.Warn("Region '{0}' is defined with conflicting MVC data: [{1}] and [{2}]. Using the former.", region.Name, region.MvcData, cpRegionMvcData); } } else { // Region does not exist in Page Model yet; create Region Model and add it. region = CreateRegionModel(cpRegionMvcData); regions.Add(region); } EntityModel entity; try { entity = ModelBuilderPipeline.CreateEntityModel(cp, localization); } catch (Exception ex) { //if there is a problem mapping the item, we replace it with an exception entity //and carry on processing - this should not cause a failure in the rendering of //the page as a whole Log.Error(ex); entity = new ExceptionEntity(ex) { MvcData = GetMvcData(cp) // TODO: The regular View won't expect an ExceptionEntity model. Should use an Exception View (?) }; } if (conditionalEntityEvaluator == null || conditionalEntityEvaluator.IncludeEntity(entity)) { region.Entities.Add(entity); } } // Create Regions from Include Pages if (includes != null) { foreach (IPage includePage in includes) { PageModel includePageModel = ModelBuilderPipeline.CreatePageModel(includePage, null, localization); // Model Include Page as Region: RegionModel includePageRegion = GetRegionFromIncludePage(includePage); RegionModel existingRegion; if (regions.TryGetValue(includePageRegion.Name, out existingRegion)) { // Region with same name already exists; merge include Page Region. existingRegion.Regions.UnionWith(includePageModel.Regions); if (existingRegion.XpmMetadata != null) { existingRegion.XpmMetadata.Remove(RegionModel.IncludedFromPageIdXpmMetadataKey); existingRegion.XpmMetadata.Remove(RegionModel.IncludedFromPageTitleXpmMetadataKey); existingRegion.XpmMetadata.Remove(RegionModel.IncludedFromPageFileNameXpmMetadataKey); } Log.Info("Merged Include Page [{0}] into Region [{1}]. Note that merged Regions can't be edited properly in XPM (yet).", includePageModel, existingRegion); } else { includePageRegion.Regions.UnionWith(includePageModel.Regions); regions.Add(includePageRegion); } #pragma warning disable 618 // Legacy WebPage.Includes support: pageModel.Includes.Add(includePage.Title, includePageModel); #pragma warning restore 618 } if (pageModel.MvcData.ViewName != "IncludePage") { pageModel.Title = ProcessPageMetadata(page, pageModel.Meta, localization); } } } }
public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable <IPage> includes, Localization localization) { // Nothing to do here }
public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable<IPage> includes, Localization localization) { // Nothing to do here }
public void BuildPageModel(ref PageModel pageModel, DD4T.ContentModel.IPage page, IEnumerable <DD4T.ContentModel.IPage> includes, Localization localization) { var containers = new List <AbstractContainerModel>(); // Get absolute order of the different container items // var regionAbsoluteOrder = new Dictionary <string, IList <int> >(); var containerAbsoluteOrder = new List <int>(); int index = 0; foreach (var cp in page.ComponentPresentations) { if (cp.ComponentTemplate.MetadataFields.ContainsKey("regionName")) { string regionName = (string)cp.ComponentTemplate.MetadataFields["regionName"].Values[0]; IList <int> orderList = null; regionAbsoluteOrder.TryGetValue(regionName, out orderList); if (orderList == null) { orderList = new List <int>(); regionAbsoluteOrder.Add(regionName, orderList); } orderList.Add(index); } else if (cp.ComponentTemplate.MetadataFields.ContainsKey("action")) { string controllerAction = (string)cp.ComponentTemplate.MetadataFields["action"].Values[0]; if (controllerAction.Equals("Container")) { containerAbsoluteOrder.Add(index); } } index++; } // Go through the entity list and extract all containers // index = 1; foreach (var region in pageModel.Regions) { // TODO: Remove container region from the top level? Otherwise XPM drop zones are generated for them as well? foreach (var entity in region.Entities) { if (entity is AbstractContainerModel) { AbstractContainerModel container = (AbstractContainerModel)entity; // Override region name using MVC route values // string containerRegionName = null; container.MvcData.RouteValues.TryGetValue("containerRegion", out containerRegionName); if (containerRegionName != null) { container.Name = containerRegionName; } // TODO: if no container region name -> should we skip this container then??? try { container.Region = new ContainerRegionModel(container); } catch (DxaException e) { throw new DxaException("Could not create empty region for container: " + container.Name, e); } container.Index = index; index++; containers.Add(container); } } } // Move all container items to the containers (to avoid the container items to be rendered outside the container) // foreach (var container in containers) { if (containerAbsoluteOrder.Count == 0) { break; } int containerIndex = containerAbsoluteOrder[0]; containerAbsoluteOrder.RemoveAt(0); RegionModel regionWithContainerItems = null; pageModel.Regions.TryGetValue(container.Name, out regionWithContainerItems); if (regionWithContainerItems == null) { // No container items found on the page -> continue // continue; } IList <int> orderList = null; regionAbsoluteOrder.TryGetValue(container.Name, out orderList); if (orderList != null && orderList.Count > 0) { index = orderList[0]; if (containerIndex + 1 == index) { int noOfItems = 0; foreach (var entity in regionWithContainerItems.Entities) { container.Region.Entities.Add(entity); noOfItems++; orderList.RemoveAt(0); if (orderList.Count == 0) { break; } int nextIndex = orderList[0]; if (nextIndex != index + 1) { break; } else { index = nextIndex; } } for (int i = 0; i < noOfItems; i++) { regionWithContainerItems.Entities.RemoveAt(0); } } } } }
protected virtual Dictionary<string, string> GetPageData(IPage page) { var res = new Dictionary<string, string>(); if (page != null) { res.Add("PageID", page.Id); res.Add("PageModified", page.RevisionDate.ToString("s")); res.Add("PageTemplateID", page.PageTemplate.Id); res.Add("PageTemplateModified", page.PageTemplate.RevisionDate.ToString("s")); } return res; }
/// <summary> /// Creates a Strongly Typed Page Model for a given DD4T Page and an optional set of include Pages. /// </summary> /// <param name="page">The DD4T Page object.</param> /// <param name="includes">The set of DD4T Page object for include pages. Can be <c>null</c>.</param> /// <param name="localization">The context <see cref="Localization"/>.</param> /// <returns>The Strongly Typed Page Model (an instance of class <see cref="PageModel"/> or a subclass).</returns> public static PageModel CreatePageModel(IPage page, IPage[] includes, Localization localization) { using (new Tracer(page, includes, localization)) { PageModel pageModel = null; foreach (IModelBuilder modelBuilder in ModelBuilders) { modelBuilder.BuildPageModel(ref pageModel, page, includes, localization); } return pageModel; } }
private PageModel CreatePageModel(IPage page, Localization localization) { MvcData pageMvcData = GetMvcData(page); Type pageModelType = ModelTypeRegistry.GetViewModelType(pageMvcData); string pageId = GetDxaIdentifierFromTcmUri(page.Id); ISchema pageMetadataSchema = page.Schema; PageModel pageModel; if (pageModelType == typeof(PageModel)) { // Standard Page Model pageModel = new PageModel(pageId); } else if (pageMetadataSchema == null) { // Custom Page Model but no Page metadata that can be mapped; simply create a Page Model instance of the right type. pageModel = (PageModel)Activator.CreateInstance(pageModelType, pageId); } else { // Custom Page Model and Page metadata is present; do full-blown model mapping. string[] schemaTcmUriParts = pageMetadataSchema.Id.Split('-'); SemanticSchema semanticSchema = SemanticMapping.GetSchema(schemaTcmUriParts[1], localization); MappingData mappingData = new MappingData { TargetType = pageModelType, SemanticSchema = semanticSchema, EntityNames = semanticSchema.GetEntityNames(), TargetEntitiesByPrefix = GetEntityDataFromType(pageModelType), Meta = page.MetadataFields, ModelId = pageId, Localization = localization }; pageModel = (PageModel) CreateViewModel(mappingData); } pageModel.MvcData = pageMvcData; pageModel.XpmMetadata = GetXpmMetadata(page); pageModel.Title = page.Title; // add html classes to model from metadata // TODO: move to CreateViewModel so it can be merged with the same code for a Component/ComponentTemplate IPageTemplate template = page.PageTemplate; if (template.MetadataFields != null && template.MetadataFields.ContainsKey("htmlClasses")) { // strip illegal characters to ensure valid html in the view (allow spaces for multiple classes) pageModel.HtmlClasses = template.MetadataFields["htmlClasses"].Value.StripIllegalCharacters(@"[^\w\-\ ]"); } return pageModel; }
private static RegionModel GetRegionFromIncludePage(IPage page) { string regionName = page.Title; MvcData regionMvcData = new MvcData(regionName); InitializeRegionMvcData(regionMvcData); return new RegionModel(regionName) { MvcData = regionMvcData, XpmMetadata = new Dictionary<string, string> { {RegionModel.IncludedFromPageIdXpmMetadataKey, page.Id}, {RegionModel.IncludedFromPageTitleXpmMetadataKey, page.Title}, {RegionModel.IncludedFromPageFileNameXpmMetadataKey, page.Filename} } }; }
/// <summary> /// Determine MVC data such as view, controller and area name from a Page /// </summary> /// <param name="page">The DD4T Page object</param> /// <returns>MVC data</returns> private static MvcData GetMvcData(IPage page) { IPageTemplate template = page.PageTemplate; string viewName = template.Title.RemoveSpaces(); if (template.MetadataFields != null) { if (template.MetadataFields.ContainsKey("view")) { viewName = template.MetadataFields["view"].Value; } } MvcData mvcData = CreateViewData(viewName); // defaults mvcData.ControllerName = SiteConfiguration.GetPageController(); mvcData.ControllerAreaName = SiteConfiguration.GetDefaultModuleName(); mvcData.ActionName = SiteConfiguration.GetPageAction(); return mvcData; }
protected virtual string ProcessPageMetadata(IPage page, IDictionary<string, string> meta, Localization localization) { //First grab metadata from the page if (page.MetadataFields != null) { foreach (IField field in page.MetadataFields.Values) { ProcessMetadataField(field, meta); } } string description = meta.ContainsKey("description") ? meta["description"] : null; string title = meta.ContainsKey("title") ? meta["title"] : null; string image = meta.ContainsKey("image") ? meta["image"] : null; //If we don't have a title or description - go hunting for a title and/or description from the first component in the main region on the page if (title == null || description == null) { bool first = true; foreach (IComponentPresentation cp in page.ComponentPresentations) { MvcData regionMvcData = GetRegionMvcData(cp); // determine title and description from first component in 'main' region if (first && regionMvcData.ViewName.Equals(RegionForPageTitleComponent)) { first = false; IFieldSet metadata = cp.Component.MetadataFields; IFieldSet fields = cp.Component.Fields; if (metadata.ContainsKey(StandardMetadataXmlFieldName) && metadata[StandardMetadataXmlFieldName].EmbeddedValues.Count > 0) { IFieldSet standardMeta = metadata[StandardMetadataXmlFieldName].EmbeddedValues[0]; if (title == null && standardMeta.ContainsKey(StandardMetadataTitleXmlFieldName)) { title = standardMeta[StandardMetadataTitleXmlFieldName].Value; } if (description == null && standardMeta.ContainsKey(StandardMetadataDescriptionXmlFieldName)) { description = standardMeta[StandardMetadataDescriptionXmlFieldName].Value; } } if (title == null && fields.ContainsKey(ComponentXmlFieldNameForPageTitle)) { title = fields[ComponentXmlFieldNameForPageTitle].Value; } //Try to find an image if (image == null && fields.ContainsKey("image")) { image = fields["image"].LinkedComponentValues[0].Multimedia.Url; } } } } string titlePostfix = GetResource("core.pageTitleSeparator") + GetResource("core.pageTitlePostfix"); //if we still dont have a title, use the page title if (title == null) { title = Regex.Replace(page.Title, @"^\d{3}\s", String.Empty); // Index and Default are not a proper titles for an HTML page if (title.ToLowerInvariant().Equals("index") || title.ToLowerInvariant().Equals("default")) { title = GetResource("core.defaultPageTitle"); } } meta.Add("twitter:card", "summary"); meta.Add("og:title", title); // TODO: if the URL is really needed, it should be added higher up (e.g. in the View code): meta.Add("og:url", WebRequestContext.RequestUrl); // TODO: is this always article? meta.Add("og:type", "article"); meta.Add("og:locale", localization.Culture); if (description != null) { meta.Add("og:description", description); } if (image != null) { image = localization.GetBaseUrl() + image; meta.Add("og:image", image); } if (!meta.ContainsKey("description")) { meta.Add("description", description ?? title); } // TODO: meta.Add("fb:admins", Configuration.GetConfig("core.fbadmins"); return title + titlePostfix; }
protected virtual IDictionary<string, string> GetXpmMetadata(IPage page) { IDictionary<string, string> result = new Dictionary<string, string>(); if (page != null) { result.Add("PageID", page.Id); result.Add("PageModified", page.RevisionDate.ToString("yyyy-MM-ddTHH:mm:ss")); result.Add("PageTemplateID", page.PageTemplate.Id); result.Add("PageTemplateModified", page.PageTemplate.RevisionDate.ToString("yyyy-MM-ddTHH:mm:ss")); } return result; }
/// <summary> /// Gets a Page Model for a given URL. /// </summary> /// <param name="urlPath">The URL path (unescaped).</param> /// <param name="localization">The context Localization.</param> /// <param name="addIncludes">Indicates whether include Pages should be expanded.</param> /// <returns>The Page Model.</returns> /// <exception cref="DxaItemNotFoundException">If no Page Model exists for the given URL.</exception> public virtual PageModel GetPageModel(string urlPath, Localization localization, bool addIncludes) { using (new Tracer(urlPath, localization, addIncludes)) { if (urlPath == null) { urlPath = "/"; } else if (!urlPath.StartsWith("/")) { urlPath = "/" + urlPath; } IPage page = GetPage(urlPath, localization); if (page == null && !urlPath.EndsWith("/")) { // This may be a SG URL path; try if the index page exists. urlPath += Constants.IndexPageUrlSuffix; page = GetPage(urlPath, localization); } else if (urlPath.EndsWith("/")) { urlPath += Constants.DefaultExtensionLessPageName; } if (page == null) { throw new DxaItemNotFoundException(urlPath, localization.Id); } IPage[] includes = addIncludes ? GetIncludesFromModel(page, localization).ToArray() : new IPage[0]; List <string> dependencies = new List <string>() { page.Id }; dependencies.AddRange(includes.Select(p => p.Id)); PageModel result = null; if (CacheRegions.IsViewModelCachingEnabled) { PageModel cachedPageModel = SiteConfiguration.CacheProvider.GetOrAdd( string.Format("{0}:{1}", page.Id, addIncludes), // Cache Page Models with and without includes separately CacheRegions.PageModel, () => { PageModel pageModel = ModelBuilderPipeline.CreatePageModel(page, includes, localization); pageModel.Url = urlPath; if (pageModel.NoCache) { result = pageModel; return(null); } return(pageModel); }, dependencies ); if (cachedPageModel != null) { // Don't return the cached Page Model itself, because we don't want dynamic logic to modify the cached state. result = (PageModel)cachedPageModel.DeepCopy(); } } else { result = ModelBuilderPipeline.CreatePageModel(page, includes, localization); result.Url = urlPath; } if (SiteConfiguration.ConditionalEntityEvaluator != null) { result.FilterConditionalEntities(localization); } return(result); } }
protected virtual IEnumerable<IPage> GetIncludesFromModel(IPage page, Localization localization) { using (new Tracer(page.Id, localization)) { List<IPage> result = new List<IPage>(); string[] pageTemplateTcmUriParts = page.PageTemplate.Id.Split('-'); IEnumerable<string> includePageUrls = localization.GetIncludePageUrls(pageTemplateTcmUriParts[1]); foreach (string includePageUrl in includePageUrls) { IPage includePage = GetPage(SiteConfiguration.LocalizeUrl(includePageUrl, localization), localization); if (includePage == null) { Log.Error("Include Page '{0}' not found.", includePageUrl); continue; } result.Add(includePage); } return result; } }