<commit_before>﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using System.Web.Http;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.Trees;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;

namespace Umbraco.Web.Trees
{
    [AngularJsonOnlyConfiguration]
    [PluginController("UmbracoTrees")]
    public class ApplicationTreeController : UmbracoAuthorizedApiController
    {
        private static readonly Lazy<IEnumerable<IGrouping<string, (Type, string)>>> CoreTrees
            = new Lazy<IEnumerable<IGrouping<string, (Type, string)>>>(() =>
                Current.Services.ApplicationTreeService.GetAllTypes()
                .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute<CoreTreeAttribute>(false)?.TreeGroup))
                .GroupBy(x => x.TreeGroup)
                .ToList());
    
        
        /// <summary>
        /// Returns the tree nodes for an application
        /// </summary>
        /// <param name="application">The application to load tree for</param>
        /// <param name="tree">An optional single tree alias, if specified will only load the single tree for the request app</param>
        /// <param name="queryStrings"></param>
        /// <param name="onlyInitialized">An optional bool (defaults to true), if set to false it will also load uninitialized trees</param>
        /// <returns></returns>
        [HttpQueryStringFilter("queryStrings")]
        public async Task<IEnumerable<SectionRootNode>> GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true)
        {
            application = application.CleanForXss();

            var rootNodeGroups = new List<SectionRootNode>();

            if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound);

            var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture);

            //find all tree definitions that have the current application alias
            var appTrees = Services.ApplicationTreeService.GetApplicationTrees(application, onlyInitialized).ToArray();

            if (string.IsNullOrEmpty(tree) == false || appTrees.Length <= 1)
            {
                var apptree = string.IsNullOrEmpty(tree) == false
                    ? appTrees.SingleOrDefault(x => x.Alias == tree)
                    : appTrees.SingleOrDefault();

                if (apptree == null) throw new HttpResponseException(HttpStatusCode.NotFound);

                var result = await GetRootForSingleAppTree(
                    apptree,
                    Constants.System.Root.ToString(CultureInfo.InvariantCulture),
                    queryStrings,
                    application);

                //this will be null if it cannot convert to ta single root section
                if (result != null)
                {
                    rootNodeGroups.Add(result);
                    return rootNodeGroups;
                }
            }

            var collection = new TreeNodeCollection();
            foreach (var apptree in appTrees)
            {
                //return the root nodes for each tree in the app
                var rootNode = await GetRootForMultipleAppTree(apptree, queryStrings);
                //This could be null if the tree decides not to return it's root (i.e. the member type tree does this when not in umbraco membership mode)
                if (rootNode != null)
                {
                    collection.Add(rootNode);
                }
            }

            //Don't apply fancy grouping logic futher down, if we are not 'settings' section
            if(application != Constants.Applications.Settings)
            {
                var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection);
                multiTree.Name = Services.TextService.Localize("sections/" + application);

                rootNodeGroups.Add(multiTree);
                return rootNodeGroups;
            }

            //For settings section only
            //Group trees by [CoreTree] attribute

            //Core Trees contains all trees for all sections/applications
            foreach(var treeSectionGroup in CoreTrees.Value)
            {
                var treeGroupName = treeSectionGroup.Key;

                var groupNodeCollection = new TreeNodeCollection();

                //Only add trees to a new collection if they are from 'settings'
                foreach (var treeItem in treeSectionGroup)
                {
                    //Item1 tuple - is the type from all tree types
                    var treeItemType = treeItem.Item1;

                    var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == treeItemType);
                    if (findAppTree != null)
                    {
                        //Now we need to get the 'TreeNode' which is in 'collection'
                        var treeItemNode = collection.SingleOrDefault(x => x.AdditionalData["treeAlias"].ToString() == findAppTree.Alias);

                        if (treeItemNode != null)
                        {
                            //Add to a new list/collection
                            groupNodeCollection.Add(treeItemNode);
                        }
                    }
                }

                //If treeGroupName == null then its third party
                if (treeGroupName == null)
                {
                    //This is used for the localisation key
                    //treeHeaders/thirdPartyGroup
                    treeGroupName = "thirdPartyGroup";
                }

                if (groupNodeCollection.Any())
                {
                    var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection);
                    groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName);

                    rootNodeGroups.Add(groupRoot);
                }
            }

            return rootNodeGroups.OrderBy(x => x.Name);
        }

        /// <summary>
        /// Get the root node for an application with multiple trees
        /// </summary>
        /// <param name="configTree"></param>
        /// <param name="queryStrings"></param>
        /// <returns></returns>
        private async Task<TreeNode> GetRootForMultipleAppTree(ApplicationTree configTree, FormDataCollection queryStrings)<commit_msg>Fixes based on Shan's feedback of the PR Only does fancy group logic if more than one group - means other sections can be grouped in the future<commit_after>﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using System.Web.Http;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.Trees;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;

namespace Umbraco.Web.Trees
{
    [AngularJsonOnlyConfiguration]
    [PluginController("UmbracoTrees")]
    public class ApplicationTreeController : UmbracoAuthorizedApiController
    {
        /// <summary>
        /// Fetches all registered trees and groups them together if they have a [CoreTree]
        /// Attribute with a 'TreeGroup' property set
        /// This allows the settings section trees to be grouped by Settings, Templating & Other
        /// </summary>
        private static readonly Lazy<IEnumerable<IGrouping<string, (Type, string)>>> CoreTrees
            = new Lazy<IEnumerable<IGrouping<string, (Type, string)>>>(() =>
                Current.Services.ApplicationTreeService.GetAllTypes()
                .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute<CoreTreeAttribute>(false)?.TreeGroup))
                .GroupBy(x => x.TreeGroup)
                .ToList());
    
        
        /// <summary>
        /// Returns the tree nodes for an application
        /// </summary>
        /// <param name="application">The application to load tree for</param>
        /// <param name="tree">An optional single tree alias, if specified will only load the single tree for the request app</param>
        /// <param name="queryStrings"></param>
        /// <param name="onlyInitialized">An optional bool (defaults to true), if set to false it will also load uninitialized trees</param>
        /// <returns></returns>
        [HttpQueryStringFilter("queryStrings")]
        public async Task<IEnumerable<SectionRootNode>> GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true)
        {
            application = application.CleanForXss();

            var rootNodeGroups = new List<SectionRootNode>();

            if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound);

            var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture);

            //find all tree definitions that have the current application alias
            var appTrees = Services.ApplicationTreeService.GetApplicationTrees(application, onlyInitialized).ToArray();

            if (string.IsNullOrEmpty(tree) == false || appTrees.Length <= 1)
            {
                var apptree = string.IsNullOrEmpty(tree) == false
                    ? appTrees.SingleOrDefault(x => x.Alias == tree)
                    : appTrees.SingleOrDefault();

                if (apptree == null) throw new HttpResponseException(HttpStatusCode.NotFound);

                var result = await GetRootForSingleAppTree(
                    apptree,
                    Constants.System.Root.ToString(CultureInfo.InvariantCulture),
                    queryStrings,
                    application);

                //this will be null if it cannot convert to ta single root section
                if (result != null)
                {
                    rootNodeGroups.Add(result);
                    return rootNodeGroups;
                }
            }

            var collection = new TreeNodeCollection();
            foreach (var apptree in appTrees)
            {
                //return the root nodes for each tree in the app
                var rootNode = await GetRootForMultipleAppTree(apptree, queryStrings);
                //This could be null if the tree decides not to return it's root (i.e. the member type tree does this when not in umbraco membership mode)
                if (rootNode != null)
                {
                    collection.Add(rootNode);
                }
            }

            //Don't apply fancy grouping logic futher down, if we only have one group of items
            if (CoreTrees.Value.Count() == 1)
            {
                var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection);
                multiTree.Name = Services.TextService.Localize("sections/" + application);

                rootNodeGroups.Add(multiTree);
                return rootNodeGroups;
            }
            
            //Group trees by [CoreTree] attribute with a TreeGroup property
            foreach(var treeSectionGroup in CoreTrees.Value)
            {
                var treeGroupName = treeSectionGroup.Key;

                var groupNodeCollection = new TreeNodeCollection();
                foreach (var treeItem in treeSectionGroup)
                {
                    //Item1 tuple - is the type from all tree types
                    var treeItemType = treeItem.Item1;

                    var findAppTree = appTrees.FirstOrDefault(x => x.GetRuntimeType() == treeItemType);
                    if (findAppTree != null)
                    {
                        //Now we need to get the 'TreeNode' which is in 'collection'
                        var treeItemNode = collection.FirstOrDefault(x => x.AdditionalData["treeAlias"].ToString() == findAppTree.Alias);

                        if (treeItemNode != null)
                        {
                            //Add to a new list/collection
                            groupNodeCollection.Add(treeItemNode);
                        }
                    }
                }

                //If treeGroupName == null then its third party
                if (treeGroupName == null)
                {
                    //This is used for the localisation key
                    //treeHeaders/thirdPartyGroup
                    treeGroupName = "thirdPartyGroup";
                }

                if (groupNodeCollection.Count > 0)
                {
                    var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection);
                    groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName);

                    rootNodeGroups.Add(groupRoot);
                }
            }

            return rootNodeGroups.OrderBy(x => x.Name);
        }

        /// <summary>
        /// Get the root node for an application with multiple trees
        /// </summary>
        /// <param name="configTree"></param>
        /// <param name="queryStrings"></param>
        /// <returns></returns>
        private async Task<TreeNode> GetRootForMultipleAppTree(ApplicationTree configTree, FormDataCollection queryStrings)
{'fraction_non_alphanumeric': 0.0866187893888319, 'fraction_numerical': 0.0005862523816503004, 'mean_word_length': 1.8580104712041885, 'pattern_counts': {'":': 0, '<': 69, '<?xml version=': 0, '>': 82, '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': '366091', 'n_tokens_mistral': 3325, 'n_tokens_neox': 3155, 'n_words': 905}