using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using umbraco; namespace Umbraco.Web.Models.Mapping { /// /// Creates the tabs collection with properties assigned for display models /// internal class TabsAndPropertiesResolver : IValueResolver where TSource : IContentBase { private readonly ILocalizedTextService _localizedTextService; protected IEnumerable IgnoreProperties { get; set; } public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService) { if (localizedTextService == null) throw new ArgumentNullException("localizedTextService"); _localizedTextService = localizedTextService; IgnoreProperties = new List(); } public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) : this(localizedTextService) { if (ignoreProperties == null) throw new ArgumentNullException("ignoreProperties"); IgnoreProperties = ignoreProperties; } /// /// Implements the /// /// /// public ResolutionResult Resolve(ResolutionResult source) { if (source.Value != null && (source.Value is TSource) == false) throw new AutoMapperMappingException(string.Format("Value supplied is of type {0} but expected {1}.\nChange the value resolver source type, or redirect the source value supplied to the value resolver using FromMember.", new object[] { source.Value.GetType(), typeof (TSource) })); return source.New( //perform the mapping with the current umbraco context ResolveCore(source.Context.GetUmbracoContext(), (TSource)source.Value), typeof(List>)); } /// /// Adds the container (listview) tab to the document /// /// /// /// This must be either 'content' or 'media' /// /// internal static void AddListView(TabbedContentItem display, string entityType, IDataTypeService dataTypeService, ILocalizedTextService localizedTextService) where TPersisted : IContentBase { int dtdId; var customDtdName = Constants.Conventions.DataTypes.ListViewPrefix + display.ContentTypeAlias; switch (entityType) { case "content": dtdId = Constants.System.DefaultContentListViewDataTypeId; break; case "media": dtdId = Constants.System.DefaultMediaListViewDataTypeId; break; case "member": dtdId = Constants.System.DefaultMembersListViewDataTypeId; break; default: throw new ArgumentOutOfRangeException("entityType does not match a required value"); } //first try to get the custom one if there is one var dt = dataTypeService.GetDataTypeDefinitionByName(customDtdName) ?? dataTypeService.GetDataTypeDefinitionById(dtdId); if (dt == null) { throw new InvalidOperationException("No list view data type was found for this document type, ensure that the default list view data types exists and/or that your custom list view data type exists"); } var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dt.Id); var editor = PropertyEditorResolver.Current.GetByAlias(dt.PropertyEditorAlias); if (editor == null) { throw new NullReferenceException("The property editor with alias " + dt.PropertyEditorAlias + " does not exist"); } var listViewTab = new Tab(); listViewTab.Alias = Constants.Conventions.PropertyGroups.ListViewGroupName; listViewTab.Label = localizedTextService.Localize("content/childItems"); listViewTab.Id = display.Tabs.Count() + 1; listViewTab.IsActive = true; var listViewConfig = editor.PreValueEditor.ConvertDbToEditor(editor.DefaultPreValues, preVals); //add the entity type to the config listViewConfig["entityType"] = entityType; //Override Tab Label if tabName is provided if (listViewConfig.ContainsKey("tabName")) { var configTabName = listViewConfig["tabName"]; if (configTabName != null && string.IsNullOrWhiteSpace(configTabName.ToString()) == false) listViewTab.Label = configTabName.ToString(); } var listViewProperties = new List(); listViewProperties.Add(new ContentPropertyDisplay { Alias = string.Format("{0}containerView", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = "", Value = null, View = editor.ValueEditor.View, HideLabel = true, Config = listViewConfig }); listViewTab.Properties = listViewProperties; SetChildItemsTabPosition(display, listViewConfig, listViewTab); } private static void SetChildItemsTabPosition(TabbedContentItem display, IDictionary listViewConfig, Tab listViewTab) where TPersisted : IContentBase { // Find position of tab from config var tabIndexForChildItems = 0; if (listViewConfig["displayAtTabNumber"] != null && int.TryParse((string)listViewConfig["displayAtTabNumber"], out tabIndexForChildItems)) { // Tab position is recorded 1-based but we insert into collection 0-based tabIndexForChildItems--; // Ensure within bounds if (tabIndexForChildItems < 0) { tabIndexForChildItems = 0; } if (tabIndexForChildItems > display.Tabs.Count()) { tabIndexForChildItems = display.Tabs.Count(); } } // Recreate tab list with child items tab at configured position var tabs = new List>(); tabs.AddRange(display.Tabs); tabs.Insert(tabIndexForChildItems, listViewTab); display.Tabs = tabs; } /// /// Create the list of tabs for the /// /// /// Source value /// Destination protected virtual List> ResolveCore(UmbracoContext umbracoContext, TSource content) { var tabs = new List>(); // add the tabs, for properties that belong to a tab // need to aggregate the tabs, as content.PropertyGroups contains all the composition tabs, // and there might be duplicates (content does not work like contentType and there is no // content.CompositionPropertyGroups). var groupsGroupsByName = content.PropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name); foreach (var groupsByName in groupsGroupsByName) { var properties = new List(); // merge properties for groups with the same name foreach (var group in groupsByName) { var groupProperties = content.GetPropertiesForGroup(group) .Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored properties.AddRange(groupProperties); } if (properties.Count == 0) continue; //map the properties var mappedProperties = MapProperties(umbracoContext, content, properties); // add the tab // we need to pick an identifier... there is no "right" way... var g = groupsByName.FirstOrDefault(x => x.Id == content.ContentTypeId) // try local ?? groupsByName.First(); // else pick one randomly var groupId = g.Id; var groupName = groupsByName.Key; tabs.Add(new Tab { Id = groupId, Alias = groupName, Label = _localizedTextService.UmbracoDictionaryTranslate(groupName), Properties = mappedProperties, IsActive = false }); } MapGenericProperties(umbracoContext, content, tabs); // activate the first tab tabs[0].IsActive = true; return tabs; } /// /// Returns a collection of custom generic properties that exist on the generic properties tab /// /// protected virtual IEnumerable GetCustomGenericProperties(IContentBase content) { return Enumerable.Empty(); } /// /// Maps properties on to the generic properties tab /// /// /// /// /// /// The generic properties tab is responsible for /// setting up the properties such as Created date, updated date, template selected, etc... /// protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List> tabs) { // add the generic properties tab, for properties that don't belong to a tab // get the properties, map and translate them, then add the tab var noGroupProperties = content.GetNonGroupedProperties() .Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored .ToList(); var genericproperties = MapProperties(umbracoContext, content, noGroupProperties); tabs.Add(new Tab { Id = 0, Label = _localizedTextService.Localize("general/properties"), Alias = "Generic properties", Properties = genericproperties }); var genericProps = tabs.Single(x => x.Id == 0); //store the current props to append to the newly inserted ones var currProps = genericProps.Properties.ToArray(); var contentProps = new List(); var customProperties = GetCustomGenericProperties(content); if (customProperties != null) { //add the custom ones contentProps.AddRange(customProperties); } //now add the user props contentProps.AddRange(currProps); //re-assign genericProps.Properties = contentProps; //Show or hide properties tab based on wether it has or not any properties if (genericProps.Properties.Any() == false) { //loop throug the tabs, remove the one with the id of zero and exit the loop for (var i = 0; i < tabs.Count; i++) { if (tabs[i].Id != 0) continue; tabs.RemoveAt(i); break; } } } /// /// Maps a list of to a list of /// /// /// /// /// protected virtual List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties) { var result = Mapper.Map, IEnumerable>( // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. properties.OrderBy(prop => prop.PropertyType.SortOrder)) .ToList(); return result; } } }