Represents the View Model for a Page
Inheritance: WebPage
 private IEnumerable<SyndicationItem> GetFeedItemsFromPage(PageModel page)
 {
     List<SyndicationItem> items = new List<SyndicationItem>();
     foreach (RegionModel region in page.Regions)
     {
         foreach (EntityModel entity in region.Entities)
         {
             items.AddRange(GetFeedItemsFromEntity(entity));
         }
     }
     return items;
 }
        public void ExtensionData_Success()
        {
            const string testExtensionDataKey = "TestExtensionData";
            const int testExtensionDataValue = 666;
            ViewModel testViewModel = new PageModel("666");

            Assert.IsNull(testViewModel.ExtensionData, "testViewModel.ExtensionData (before)");

            testViewModel.SetExtensionData(testExtensionDataKey, testExtensionDataValue);
            OutputJson(testViewModel);

            Assert.IsNotNull(testViewModel.ExtensionData, "testViewModel.ExtensionData");
            Assert.AreEqual(1, testViewModel.ExtensionData.Count, "testViewModel.ExtensionData.Count");
            Assert.AreEqual(testExtensionDataValue, testViewModel.ExtensionData[testExtensionDataKey], "testViewModel.ExtensionData[testExtensionDataKey]");
        }
        internal SmartTargetPageModel(PageModel pageModel) : base(pageModel.Id)
        {
            Title = pageModel.Title;
            Url = pageModel.Url;

            MvcData = pageModel.MvcData;
            HtmlClasses = pageModel.HtmlClasses;
            Regions.UnionWith(pageModel.Regions);
            XpmMetadata = pageModel.XpmMetadata;

            foreach (KeyValuePair<string, string> metaKeyValuePair in pageModel.Meta)
            {
                Meta.Add(metaKeyValuePair);
            }
        }
        public void DeepCopy_Success()
        {
            Article testArticle = new Article() { Id = "666-666" };
            testArticle.XpmPropertyMetadata = new Dictionary<string, string> { { "xxx", "yyy" } };
            RegionModel testRegionModel = new RegionModel("test");
            testRegionModel.Entities.Add(testArticle);
            PageModel testPageModel = new PageModel("666") { Url = "/test" };
            testPageModel.Regions.Add(testRegionModel);
            testPageModel.Meta.Add("aaa", "bbb");
            testPageModel.XpmMetadata = new Dictionary<string, object> { {"ccc", "ddd"}, {"eee", 666} };
            testPageModel.ExtensionData = new Dictionary<string, object> { { "fff", "ggg" }, { "hhh", 6666 } };
            OutputJson(testPageModel);

            PageModel clonedPageModel = testPageModel.DeepCopy() as PageModel;
            Assert.IsNotNull(clonedPageModel, "clonedPageModel");
            OutputJson(clonedPageModel);

            Assert.AreNotSame(testPageModel, clonedPageModel, "clonedPageModel");
            Assert.AreEqual(testPageModel.Id, clonedPageModel.Id, "clonedPageModel.Id");
            Assert.AreEqual(testPageModel.Url, clonedPageModel.Url, "clonedPageModel.Url");
            AssertEqualCollections(testPageModel.Meta, clonedPageModel.Meta, "clonedPageModel.Meta");
            AssertEqualCollections(testPageModel.XpmMetadata, clonedPageModel.XpmMetadata, "clonedPageModel.XpmMetadata");
            AssertEqualCollections(testPageModel.ExtensionData, clonedPageModel.ExtensionData, "clonedPageModel.ExtensionData");
            AssertEqualCollections(testPageModel.Regions, clonedPageModel.Regions, "clonedPageModel.Regions");

            RegionModel clonedRegionModel = clonedPageModel.Regions[testRegionModel.Name];
            Assert.IsNotNull(clonedRegionModel, "clonedRegionModel");
            Assert.AreNotSame(testRegionModel, clonedRegionModel, "clonedRegionModel");
            AssertEqualCollections(testRegionModel.Entities, clonedRegionModel.Entities, "clonedRegionModel.Entities");
            AssertEqualCollections(testRegionModel.Regions, clonedRegionModel.Regions, "clonedRegionModel.Regions");

            Article clonedArticle = clonedRegionModel.Entities[0] as Article;
            Assert.IsNotNull(clonedArticle, "clonedArticle");
            Assert.AreNotSame(testArticle, clonedArticle, "clonedArticle");
            Assert.AreEqual(testArticle.Id, clonedArticle.Id, "clonedArticle.Id");
            AssertEqualCollections(testArticle.XpmPropertyMetadata, clonedArticle.XpmPropertyMetadata, "clonedArticle.XpmPropertyMetadata");
        }
        /// <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>
        /// Enriches all the Region/Entity Models embedded in the given Page Model.
        /// </summary>
        /// <param name="model">The Page Model to enrich.</param>
        /// <remarks>Used by <see cref="FormatDataAttribute"/> to get all embedded Models enriched without rendering any Views.</remarks>
        internal void EnrichEmbeddedModels(PageModel model)
        {
            using (new Tracer(model))
            {
                if (model == null)
                {
                    return;
                }

                foreach (RegionModel region in model.Regions)
                {
                    // NOTE: Currently not enriching the Region Model itself, because we don't support custom Region Controllers (yet).
                    for (int i = 0; i < region.Entities.Count; i++)
                    {
                        EntityModel entity = region.Entities[i];
                        if (entity == null || entity.MvcData == null)
                        {
                            continue;
                        }

                        EntityModel enrichedEntityModel;
                        try
                        {
                            enrichedEntityModel = EnrichEntityModel(entity);
                        }
                        catch (Exception ex)
                        {
                            // If there is a problem enriching an Entity, we replace it with an ExceptionEntity which holds the error details and carry on.
                            Log.Error(ex);
                            enrichedEntityModel = new ExceptionEntity(ex);
                        }
                        region.Entities[i] = enrichedEntityModel;
                    }
                }
            }
        }
 public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable<IPage> includes, Localization localization)
 {
     // Nothing to do here
 }
        public void ExtractSyndicationFeedItems_Teasers_Success()
        {
            Localization testLocalization = TestFixture.ParentLocalization;
            Teaser testTeaser1 = new Teaser
            {
                Headline = "Test Teaser 1",
                Text = new RichText("This is the text of Test Teaser 1."),
                Link = new Link() { Url = "http://www.sdl.com/" }
            };
            Teaser testTeaser2 = new Teaser
            {
                Headline = "Test Teaser 2",
            };
            RegionModel testRegion1 = new RegionModel("test1");
            testRegion1.Entities.Add(testTeaser1);
            RegionModel testRegion2 = new RegionModel("test2");
            testRegion2.Entities.Add(testTeaser2);
            PageModel testPageModel = new PageModel("666");
            testPageModel.Regions.Add(testRegion1);
            testPageModel.Regions.Add(testRegion2);
            OutputJson(testPageModel);

            SyndicationItem[] syndicationItems = testPageModel.ExtractSyndicationFeedItems(testLocalization).ToArray();

            Assert.IsNotNull(syndicationItems);
            Assert.AreEqual(2, syndicationItems.Length, "syndicationItems.Length");

            SyndicationItem firstSyndicationItem = syndicationItems[0];
            Assert.IsNotNull(firstSyndicationItem, "firstSyndicationItem");
            Assert.IsNotNull(firstSyndicationItem.Title, "firstSyndicationItem.Title");
            Assert.IsNotNull(firstSyndicationItem.Summary, "firstSyndicationItem.Summary");
            Assert.IsNotNull(firstSyndicationItem.Links, "firstSyndicationItem.Links");
            Assert.AreEqual(testTeaser1.Headline, firstSyndicationItem.Title.Text, "firstSyndicationItem.Title.Text");
            Assert.AreEqual(testTeaser1.Text.ToString(), firstSyndicationItem.Summary.Text, "firstSyndicationItem.Summary.Text");
            Assert.AreEqual(1, firstSyndicationItem.Links.Count, "firstSyndicationItem.Links.Count");
            Assert.AreEqual(testTeaser1.Link.Url, firstSyndicationItem.Links[0].Uri.ToString(), "firstSyndicationItem.Links[0].Uri");

            SyndicationItem secondSyndicationItem = syndicationItems[1];
            Assert.IsNotNull(secondSyndicationItem, "secondSyndicationItem");
            Assert.IsNotNull(secondSyndicationItem.Title, "secondSyndicationItem.Title");
            Assert.IsNull(secondSyndicationItem.Summary, "secondSyndicationItem.Summary");
            Assert.IsNotNull(secondSyndicationItem.Links, "secondSyndicationItem.Links");
            Assert.AreEqual(testTeaser2.Headline, secondSyndicationItem.Title.Text, "secondSyndicationItem.Title.Text");
            Assert.AreEqual(0, secondSyndicationItem.Links.Count, "secondSyndicationItem.Links.Count");
        }
        public void ExtractSyndicationFeedItems_None_Success()
        {
            Localization testLocalization = TestFixture.ParentLocalization;
            PageModel testPageModel = new PageModel("666");
            OutputJson(testPageModel);

            SyndicationItem[] syndicationItems = testPageModel.ExtractSyndicationFeedItems(testLocalization).ToArray();

            Assert.IsNotNull(syndicationItems);
            Assert.AreEqual(0, syndicationItems.Length, "syndicationItems.Length");
        }
        /// <summary>
        /// Enriches all the Region/Entity Models embedded in the given Page Model.
        /// </summary>
        /// <param name="model">The Page Model to enrich.</param>
        /// <remarks>Used by <see cref="FormatDataAttribute"/> to get all embedded Models enriched without rendering any Views.</remarks>
        internal void EnrichEmbeddedModels(PageModel model)
        {
            using (new Tracer(model))
            {
                if (model == null)
                {
                    return;
                }

                foreach (RegionModel region in model.Regions)
                {
                    // NOTE: Currently not enriching the Region Model itself, because we don't support custom Region Controllers (yet).
                    for (int i = 0; i < region.Entities.Count; i++)
                    {
                        EntityModel entity = region.Entities[i];
                        if (entity != null && entity.MvcData != null)
                        {
                            region.Entities[i] = EnrichEntityModel(entity);
                        }
                    }
                }
            }
        }
        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;
        }
        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);
                    }
                }
            }
        }