<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Http;
using AutoMapper;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Constants = Umbraco.Core.Constants;

namespace Umbraco.Web.Editors
{
    /// <summary>
    /// Am abstract API controller providing functionality used for dealing with content and media types
    /// </summary>
    [PluginController("UmbracoApi")]    
    [PrefixlessBodyModelValidator]
    public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController
    {
        private ICultureDictionary _cultureDictionary;

        /// <summary>
        /// Constructor
        /// </summary>
        protected ContentTypeControllerBase()
            : this(UmbracoContext.Current)
        {            
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="umbracoContext"></param>
        protected ContentTypeControllerBase(UmbracoContext umbracoContext)
            : base(umbracoContext)
        {
        }

        /// <summary>
        /// Returns the available composite content types for a given content type
        /// </summary>
        /// <param name="type"></param>
        /// <param name="filterContentTypes">
        /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out
        /// along with any content types that have matching property types that are included in the filtered content types
        /// </param>
        /// <param name="filterPropertyTypes">
        /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out.
        /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot
        /// be looked up via the db, they need to be passed in.
        /// </param>
        /// <param name="contentTypeId"></param>        
        /// <returns></returns>
        protected IEnumerable<Tuple<EntityBasic, bool>> PerformGetAvailableCompositeContentTypes(int contentTypeId, 
            UmbracoObjectTypes type, 
            string[] filterContentTypes,
            string[] filterPropertyTypes)
        {
            IContentTypeComposition source = null;

            //below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic
            
            IContentTypeComposition[] allContentTypes;
            switch (type)
            {
                case UmbracoObjectTypes.DocumentType:
                    if (contentTypeId > 0)
                    {
                        source = Services.ContentTypeService.GetContentType(contentTypeId);
                        if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
                    }
                    allContentTypes = Services.ContentTypeService.GetAllContentTypes().Cast<IContentTypeComposition>().ToArray();
                    break;

                case UmbracoObjectTypes.MediaType:
                    if (contentTypeId > 0)
                    {
                        source = Services.ContentTypeService.GetMediaType(contentTypeId);
                        if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
                    }
                    allContentTypes = Services.ContentTypeService.GetAllMediaTypes().Cast<IContentTypeComposition>().ToArray();
                    break;

                case UmbracoObjectTypes.MemberType:
                    if (contentTypeId > 0)
                    {
                        source = Services.MemberTypeService.Get(contentTypeId);
                        if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
                    }
                    allContentTypes = Services.MemberTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
                    break;

                default:
                    throw new ArgumentOutOfRangeException("The entity type was not a content type");
            }

            var filtered = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes);

            var currCompositions = source == null ? new string[] { } : source.ContentTypeComposition.Select(x => x.Alias).ToArray();

            return filtered                
                .Select(x => new Tuple<EntityBasic, bool>(Mapper.Map<IContentTypeComposition, EntityBasic>(x.Item1), x.Item2))
                .Select(x =>
                {
                    //translate the name
                    x.Item1.Name = TranslateItem(x.Item1.Name);

                    //we need to ensure that the item is enabled if it is already selected
                    if (currCompositions.Contains(x.Item1.Alias))
                    {
                        //re-set x to be allowed (NOTE: I didn't know you could set an enumerable item in a lambda!)
                        x = new Tuple<EntityBasic, bool>(x.Item1, true);
                    }

                    return x;
                })
                .ToList();
        }
        

        /// <summary>
        /// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors
        /// </summary>
        /// <param name="contentTypeSave"></param>
        /// <param name="composition"></param>
        /// <returns></returns>
        protected void ValidateComposition(ContentTypeSave contentTypeSave, IContentTypeComposition composition)
        {
            var validateAttempt = Services.ContentTypeService.ValidateComposition(composition);
            if (validateAttempt == false)
            {
                //if it's not successful then we need to return some model state for the property aliases that 
                // are duplicated
                var propertyAliases = validateAttempt.Result.Distinct();
                foreach (var propertyAlias in propertyAliases)
                {
                    //find the property relating to these
                    var prop = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias);
                    var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(prop));
                    var propIndex = group.Properties.IndexOf(prop);
                    var groupIndex = contentTypeSave.Groups.IndexOf(group);

                    var key = string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propIndex);
                    ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions");
                }

                var display = Mapper.Map<ContentTypeDisplay>(composition);
                //map the 'save' data on top
                display = Mapper.Map(contentTypeSave, display);
                display.Errors = ModelState.ToErrorDictionary();
                throw new HttpResponseException(Request.CreateValidationErrorResponse(display));
            }

        }

        protected string TranslateItem(string text)
        {
            if (text == null)
            {
                return null;
            }

            if (text.StartsWith("#") == false)
                return text;

            text = text.Substring(1);
            return CultureDictionary[text].IfNullOrWhiteSpace(text);
        }

        protected TContentType PerformPostSave<TContentType, TContentTypeDisplay>(
            ContentTypeSave contentTypeSave,
            Func<int, TContentType> getContentType,
            Func<string, TContentType> getContentTypeByAlias,
            Action<TContentType> saveContentType,
            bool validateComposition = true,
            Action<ContentTypeSave> beforeCreateNew = null)
            where TContentType : IContentTypeComposition
            where TContentTypeDisplay : ContentTypeCompositionDisplay
        {
            var ctId = Convert.ToInt32(contentTypeSave.Id);
            
            //Validate that there's no other ct with the same name
            var exists = getContentTypeByAlias(contentTypeSave.Alias);
            if (exists != null)
            {
                ModelState.AddModelError("Alias", "A content type with this alias already exists");<commit_msg>Doctype alias validation always fails<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Http;
using AutoMapper;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Constants = Umbraco.Core.Constants;

namespace Umbraco.Web.Editors
{
    /// <summary>
    /// Am abstract API controller providing functionality used for dealing with content and media types
    /// </summary>
    [PluginController("UmbracoApi")]    
    [PrefixlessBodyModelValidator]
    public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController
    {
        private ICultureDictionary _cultureDictionary;

        /// <summary>
        /// Constructor
        /// </summary>
        protected ContentTypeControllerBase()
            : this(UmbracoContext.Current)
        {            
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="umbracoContext"></param>
        protected ContentTypeControllerBase(UmbracoContext umbracoContext)
            : base(umbracoContext)
        {
        }

        /// <summary>
        /// Returns the available composite content types for a given content type
        /// </summary>
        /// <param name="type"></param>
        /// <param name="filterContentTypes">
        /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out
        /// along with any content types that have matching property types that are included in the filtered content types
        /// </param>
        /// <param name="filterPropertyTypes">
        /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out.
        /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot
        /// be looked up via the db, they need to be passed in.
        /// </param>
        /// <param name="contentTypeId"></param>        
        /// <returns></returns>
        protected IEnumerable<Tuple<EntityBasic, bool>> PerformGetAvailableCompositeContentTypes(int contentTypeId, 
            UmbracoObjectTypes type, 
            string[] filterContentTypes,
            string[] filterPropertyTypes)
        {
            IContentTypeComposition source = null;

            //below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic
            
            IContentTypeComposition[] allContentTypes;
            switch (type)
            {
                case UmbracoObjectTypes.DocumentType:
                    if (contentTypeId > 0)
                    {
                        source = Services.ContentTypeService.GetContentType(contentTypeId);
                        if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
                    }
                    allContentTypes = Services.ContentTypeService.GetAllContentTypes().Cast<IContentTypeComposition>().ToArray();
                    break;

                case UmbracoObjectTypes.MediaType:
                    if (contentTypeId > 0)
                    {
                        source = Services.ContentTypeService.GetMediaType(contentTypeId);
                        if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
                    }
                    allContentTypes = Services.ContentTypeService.GetAllMediaTypes().Cast<IContentTypeComposition>().ToArray();
                    break;

                case UmbracoObjectTypes.MemberType:
                    if (contentTypeId > 0)
                    {
                        source = Services.MemberTypeService.Get(contentTypeId);
                        if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
                    }
                    allContentTypes = Services.MemberTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
                    break;

                default:
                    throw new ArgumentOutOfRangeException("The entity type was not a content type");
            }

            var filtered = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes);

            var currCompositions = source == null ? new string[] { } : source.ContentTypeComposition.Select(x => x.Alias).ToArray();

            return filtered                
                .Select(x => new Tuple<EntityBasic, bool>(Mapper.Map<IContentTypeComposition, EntityBasic>(x.Item1), x.Item2))
                .Select(x =>
                {
                    //translate the name
                    x.Item1.Name = TranslateItem(x.Item1.Name);

                    //we need to ensure that the item is enabled if it is already selected
                    if (currCompositions.Contains(x.Item1.Alias))
                    {
                        //re-set x to be allowed (NOTE: I didn't know you could set an enumerable item in a lambda!)
                        x = new Tuple<EntityBasic, bool>(x.Item1, true);
                    }

                    return x;
                })
                .ToList();
        }
        

        /// <summary>
        /// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors
        /// </summary>
        /// <param name="contentTypeSave"></param>
        /// <param name="composition"></param>
        /// <returns></returns>
        protected void ValidateComposition(ContentTypeSave contentTypeSave, IContentTypeComposition composition)
        {
            var validateAttempt = Services.ContentTypeService.ValidateComposition(composition);
            if (validateAttempt == false)
            {
                //if it's not successful then we need to return some model state for the property aliases that 
                // are duplicated
                var propertyAliases = validateAttempt.Result.Distinct();
                foreach (var propertyAlias in propertyAliases)
                {
                    //find the property relating to these
                    var prop = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias);
                    var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(prop));
                    var propIndex = group.Properties.IndexOf(prop);
                    var groupIndex = contentTypeSave.Groups.IndexOf(group);

                    var key = string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propIndex);
                    ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions");
                }

                var display = Mapper.Map<ContentTypeDisplay>(composition);
                //map the 'save' data on top
                display = Mapper.Map(contentTypeSave, display);
                display.Errors = ModelState.ToErrorDictionary();
                throw new HttpResponseException(Request.CreateValidationErrorResponse(display));
            }

        }

        protected string TranslateItem(string text)
        {
            if (text == null)
            {
                return null;
            }

            if (text.StartsWith("#") == false)
                return text;

            text = text.Substring(1);
            return CultureDictionary[text].IfNullOrWhiteSpace(text);
        }

        protected TContentType PerformPostSave<TContentType, TContentTypeDisplay>(
            ContentTypeSave contentTypeSave,
            Func<int, TContentType> getContentType,
            Func<string, TContentType> getContentTypeByAlias,
            Action<TContentType> saveContentType,
            bool validateComposition = true,
            Action<ContentTypeSave> beforeCreateNew = null)
            where TContentType : IContentTypeComposition
            where TContentTypeDisplay : ContentTypeCompositionDisplay
        {
            var ctId = Convert.ToInt32(contentTypeSave.Id);
            
            //Validate that there's no other ct with the same name
            var exists = getContentTypeByAlias(contentTypeSave.Alias);
            if (exists != null && exists.Id != ctId)
            {
                ModelState.AddModelError("Alias", "A content type with this alias already exists");
{'fraction_non_alphanumeric': 0.08329121033417579, 'fraction_numerical': 0.0015725919685481606, 'mean_word_length': 1.931511359894633, 'pattern_counts': {'":': 0, '<': 87, '<?xml version=': 0, '>': 105, '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': '6738501', 'n_tokens_mistral': 4239, 'n_tokens_neox': 4034, 'n_words': 1149}