<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using DD4T.ContentModel;
using Sdl.Web.Common;
using Sdl.Web.Common.Configuration;
using Sdl.Web.Common.Logging;
using Sdl.Web.Common.Models;
using Sdl.Web.Modules.SmartTarget.Models;
using Sdl.Web.Tridion.Mapping;
using Tridion.ContentDelivery.AmbientData;
using Tridion.SmartTarget.Analytics;
using Tridion.SmartTarget.Query;
using Tridion.SmartTarget.Query.Builder;
using Tridion.SmartTarget.Utils;
using IPage = DD4T.ContentModel.IPage;
using TcmUri = Tridion.SmartTarget.Utils.TcmUri;

namespace Sdl.Web.Modules.SmartTarget.Mapping
{
    /// <summary>
    /// SmartTarget Model Builder.
    /// </summary>
    /// <remarks>
    /// This Model Builder should be configured in the modelBuilderPipeline section in Web.config to run after the <see cref="DefaultModelBuilder"/>.
    /// </remarks>
    public class SmartTargetModelBuilder : IModelBuilder
    {
        private const string PromotionViewNameConfig = "smarttarget.smartTargetEntityPromotion";

        #region IModelBuilder members
        /// <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.Region.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;
                }
            }
        }
        
        public void BuildEntityModel(ref EntityModel entityModel, IComponentPresentation cp, Localization localization)
        {
            // No post-processing of Entity Models needed
        }

        public void BuildEntityModel(ref EntityModel entityModel, IComponent component, Type baseModelType, Localization localization)
        {
            // No post-processing of Entity Models needed
        }<commit_msg>TSI-2179 No longer using Promotion.Region property which is deprecated in XO 8.<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using DD4T.ContentModel;
using Sdl.Web.Common;
using Sdl.Web.Common.Configuration;
using Sdl.Web.Common.Logging;
using Sdl.Web.Common.Models;
using Sdl.Web.Modules.SmartTarget.Models;
using Sdl.Web.Tridion.Mapping;
using Tridion.ContentDelivery.AmbientData;
using Tridion.SmartTarget.Analytics;
using Tridion.SmartTarget.Query;
using Tridion.SmartTarget.Query.Builder;
using Tridion.SmartTarget.Utils;
using IPage = DD4T.ContentModel.IPage;
using TcmUri = Tridion.SmartTarget.Utils.TcmUri;

namespace Sdl.Web.Modules.SmartTarget.Mapping
{
    /// <summary>
    /// SmartTarget Model Builder.
    /// </summary>
    /// <remarks>
    /// This Model Builder should be configured in the modelBuilderPipeline section in Web.config to run after the <see cref="DefaultModelBuilder"/>.
    /// </remarks>
    public class SmartTargetModelBuilder : IModelBuilder
    {
        private const string PromotionViewNameConfig = "smarttarget.smartTargetEntityPromotion";

        #region IModelBuilder members
        /// <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;
                }
            }
        }
        
        public void BuildEntityModel(ref EntityModel entityModel, IComponentPresentation cp, Localization localization)
        {
            // No post-processing of Entity Models needed
        }

        public void BuildEntityModel(ref EntityModel entityModel, IComponent component, Type baseModelType, Localization localization)
        {
            // No post-processing of Entity Models needed
        }
{'fraction_non_alphanumeric': 0.07440781309016349, 'fraction_numerical': 0.0027518480548211297, 'mean_word_length': 1.8757176105508147, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2015905', 'n_tokens_mistral': 4437, 'n_tokens_neox': 4067, 'n_words': 1029}