2013-09-02 15:40:14 +02:00
using System ;
using System.Collections.Generic ;
2017-09-27 13:58:33 +02:00
using System.ComponentModel.DataAnnotations ;
2013-09-02 15:40:14 +02:00
using System.Linq ;
using AutoMapper ;
using Umbraco.Core ;
using Umbraco.Core.Models ;
using Umbraco.Core.PropertyEditors ;
2014-09-18 11:52:12 +10:00
using Umbraco.Core.Services ;
2013-09-02 15:40:14 +02:00
using Umbraco.Web.Models.ContentEditing ;
using umbraco ;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Creates the tabs collection with properties assigned for display models
/// </summary>
internal class TabsAndPropertiesResolver : ValueResolver < IContentBase , IEnumerable < Tab < ContentPropertyDisplay > > >
2016-01-06 11:22:15 +01:00
{
private readonly ILocalizedTextService _localizedTextService ;
2013-10-22 17:36:46 +11:00
protected IEnumerable < string > IgnoreProperties { get ; set ; }
2013-09-30 15:42:29 +10:00
2016-01-06 11:22:15 +01:00
public TabsAndPropertiesResolver ( ILocalizedTextService localizedTextService )
2013-09-30 15:42:29 +10:00
{
2016-01-06 11:22:15 +01:00
if ( localizedTextService = = null ) throw new ArgumentNullException ( "localizedTextService" ) ;
_localizedTextService = localizedTextService ;
2013-10-22 17:36:46 +11:00
IgnoreProperties = new List < string > ( ) ;
2013-09-30 15:42:29 +10:00
}
2016-01-06 11:22:15 +01:00
public TabsAndPropertiesResolver ( ILocalizedTextService localizedTextService , IEnumerable < string > ignoreProperties )
: this ( localizedTextService )
2017-09-27 13:58:33 +02:00
{
2013-09-30 15:42:29 +10:00
if ( ignoreProperties = = null ) throw new ArgumentNullException ( "ignoreProperties" ) ;
2013-10-22 17:36:46 +11:00
IgnoreProperties = ignoreProperties ;
2013-09-30 15:42:29 +10:00
}
2013-09-02 15:40:14 +02:00
/// <summary>
/// Maps properties on to the generic properties tab
/// </summary>
/// <param name="content"></param>
/// <param name="display"></param>
2016-01-06 11:22:15 +01:00
/// <param name="localizedTextService"></param>
2013-09-02 15:40:14 +02:00
/// <param name="customProperties">
/// Any additional custom properties to assign to the generic properties tab.
/// </param>
2015-10-28 12:28:29 +01:00
/// <param name="onGenericPropertiesMapped"></param>
2013-09-02 15:40:14 +02:00
/// <remarks>
/// The generic properties tab is mapped during AfterMap and is responsible for
2014-09-15 13:53:33 +10:00
/// setting up the properties such as Created date, updated date, template selected, etc...
2013-09-02 15:40:14 +02:00
/// </remarks>
public static void MapGenericProperties < TPersisted > (
2013-10-09 15:07:09 +02:00
TPersisted content ,
2013-09-02 15:40:14 +02:00
ContentItemDisplayBase < ContentPropertyDisplay , TPersisted > display ,
2016-01-06 11:22:15 +01:00
ILocalizedTextService localizedTextService ,
2015-10-28 12:28:29 +01:00
IEnumerable < ContentPropertyDisplay > customProperties = null ,
Action < List < ContentPropertyDisplay > > onGenericPropertiesMapped = null )
2013-09-02 15:40:14 +02:00
where TPersisted : IContentBase
{
var genericProps = display . Tabs . Single ( x = > x . Id = = 0 ) ;
//store the current props to append to the newly inserted ones
var currProps = genericProps . Properties . ToArray ( ) ;
2017-09-27 13:58:33 +02:00
var contentProps = new List < ContentPropertyDisplay > ( ) ;
2013-09-02 15:40:14 +02:00
2015-10-28 12:28:29 +01:00
if ( customProperties ! = null )
{
//add the custom ones
contentProps . AddRange ( customProperties ) ;
}
2013-09-02 15:40:14 +02:00
//now add the user props
contentProps . AddRange ( currProps ) ;
2015-10-28 12:28:29 +01:00
//callback
if ( onGenericPropertiesMapped ! = null )
{
onGenericPropertiesMapped ( contentProps ) ;
}
2017-09-27 13:58:33 +02:00
//re-assign
2013-09-02 15:40:14 +02:00
genericProps . Properties = contentProps ;
2017-09-27 13:58:33 +02:00
2017-09-27 16:02:32 +02:00
//Show or hide properties tab based on wether it has or not any properties
2017-09-27 13:58:33 +02:00
if ( genericProps . Properties . Any ( ) = = false )
{
display . Tabs = display . Tabs . Where ( x = > x . Id ! = 0 ) ;
}
2013-09-02 15:40:14 +02:00
}
2013-11-15 16:56:51 +11:00
/// <summary>
/// Adds the container (listview) tab to the document
/// </summary>
/// <typeparam name="TPersisted"></typeparam>
/// <param name="display"></param>
/// <param name="entityType">This must be either 'content' or 'media'</param>
2014-09-22 18:18:09 +10:00
/// <param name="dataTypeService"></param>
2016-02-11 14:04:14 +01:00
/// <param name="localizedTextService"></param>
2016-01-06 11:22:15 +01:00
internal static void AddListView < TPersisted > ( TabbedContentItem < ContentPropertyDisplay , TPersisted > display , string entityType , IDataTypeService dataTypeService , ILocalizedTextService localizedTextService )
2013-10-20 23:36:26 +02:00
where TPersisted : IContentBase
{
2014-09-18 11:52:12 +10:00
int dtdId ;
2014-09-22 18:18:09 +10:00
var customDtdName = Constants . Conventions . DataTypes . ListViewPrefix + display . ContentTypeAlias ;
2014-09-18 11:52:12 +10:00
switch ( entityType )
{
case "content" :
2017-09-27 13:58:33 +02:00
dtdId = Constants . System . DefaultContentListViewDataTypeId ;
2014-09-18 11:52:12 +10:00
break ;
case "media" :
dtdId = Constants . System . DefaultMediaListViewDataTypeId ;
break ;
2014-09-19 09:47:42 +10:00
case "member" :
2014-09-18 11:52:12 +10:00
dtdId = Constants . System . DefaultMembersListViewDataTypeId ;
break ;
default :
throw new ArgumentOutOfRangeException ( "entityType does not match a required value" ) ;
}
2014-09-22 18:18:09 +10:00
//first try to get the custom one if there is one
2017-09-27 13:58:33 +02:00
var dt = dataTypeService . GetDataTypeDefinitionByName ( customDtdName )
2014-09-22 18:18:09 +10:00
? ? dataTypeService . GetDataTypeDefinitionById ( dtdId ) ;
2015-01-30 10:18:19 +11:00
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" ) ;
}
2014-09-22 18:18:09 +10:00
var preVals = dataTypeService . GetPreValuesCollectionByDataTypeId ( dt . Id ) ;
2014-09-18 11:52:12 +10:00
var editor = PropertyEditorResolver . Current . GetByAlias ( dt . PropertyEditorAlias ) ;
if ( editor = = null )
{
throw new NullReferenceException ( "The property editor with alias " + dt . PropertyEditorAlias + " does not exist" ) ;
}
2014-09-15 13:53:33 +10:00
2014-09-18 09:48:08 +10:00
var listViewTab = new Tab < ContentPropertyDisplay > ( ) ;
listViewTab . Alias = Constants . Conventions . PropertyGroups . ListViewGroupName ;
2016-01-06 11:22:15 +01:00
listViewTab . Label = localizedTextService . Localize ( "content/childItems" ) ;
2017-07-17 09:46:30 +02:00
listViewTab . Id = display . Tabs . Count ( ) + 1 ;
2014-09-18 09:48:08 +10:00
listViewTab . IsActive = true ;
2014-09-18 11:52:12 +10:00
var listViewConfig = editor . PreValueEditor . ConvertDbToEditor ( editor . DefaultPreValues , preVals ) ;
//add the entity type to the config
2017-07-06 10:50:19 -04:00
listViewConfig [ "entityType" ] = entityType ;
2017-07-17 17:50:38 +02:00
//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 ( ) ;
}
2014-09-18 11:52:12 +10:00
2014-09-18 09:48:08 +10:00
var listViewProperties = new List < ContentPropertyDisplay > ( ) ;
listViewProperties . Add ( new ContentPropertyDisplay
2013-10-20 23:36:26 +02:00
{
2014-09-18 09:48:08 +10:00
Alias = string . Format ( "{0}containerView" , Constants . PropertyEditors . InternalGenericPropertiesPrefix ) ,
Label = "" ,
Value = null ,
2014-09-18 11:52:12 +10:00
View = editor . ValueEditor . View ,
2014-09-18 09:48:08 +10:00
HideLabel = true ,
2014-09-18 11:52:12 +10:00
Config = listViewConfig
2014-09-18 09:48:08 +10:00
} ) ;
listViewTab . Properties = listViewProperties ;
2015-11-15 21:18:21 +01:00
SetChildItemsTabPosition ( display , listViewConfig , listViewTab ) ;
}
2017-09-27 13:58:33 +02:00
private static void SetChildItemsTabPosition < TPersisted > ( TabbedContentItem < ContentPropertyDisplay , TPersisted > display ,
2015-11-15 21:18:21 +01:00
IDictionary < string , object > listViewConfig ,
2017-09-27 13:58:33 +02:00
Tab < ContentPropertyDisplay > listViewTab )
2015-11-15 21:18:21 +01:00
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
2014-09-18 09:48:08 +10:00
var tabs = new List < Tab < ContentPropertyDisplay > > ( ) ;
tabs . AddRange ( display . Tabs ) ;
2015-11-15 21:18:21 +01:00
tabs . Insert ( tabIndexForChildItems , listViewTab ) ;
2014-09-18 09:48:08 +10:00
display . Tabs = tabs ;
2013-10-20 23:36:26 +02:00
}
2013-09-02 15:40:14 +02:00
protected override IEnumerable < Tab < ContentPropertyDisplay > > ResolveCore ( IContentBase content )
{
2015-11-17 12:30:37 +01:00
var tabs = new List < Tab < ContentPropertyDisplay > > ( ) ;
2013-09-02 15:40:14 +02:00
2015-11-17 12:30:37 +01:00
// 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 )
2013-09-02 15:40:14 +02:00
{
2017-09-27 13:58:33 +02:00
var properties = new List < Property > ( ) ;
// merge properties for groups with the same name
2015-11-17 12:30:37 +01:00
foreach ( var group in groupsByName )
2013-09-02 15:40:14 +02:00
{
2015-11-17 12:30:37 +01:00
var groupProperties = content . GetPropertiesForGroup ( group )
. Where ( x = > IgnoreProperties . Contains ( x . Alias ) = = false ) ; // skip ignored
2013-09-30 15:42:29 +10:00
2016-12-17 21:12:45 -05:00
properties . AddRange ( groupProperties ) ;
2013-09-02 15:40:14 +02:00
}
2015-11-17 12:30:37 +01:00
if ( properties . Count = = 0 )
2014-11-18 10:29:46 +01:00
continue ;
2016-12-17 21:12:45 -05:00
// Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties.
var mappedProperties = Mapper . Map < IEnumerable < Property > , IEnumerable < ContentPropertyDisplay > > ( properties . OrderBy ( prop = > prop . PropertyType . SortOrder ) ) ;
TranslateProperties ( mappedProperties ) ;
2013-09-02 15:40:14 +02:00
2015-11-17 12:30:37 +01:00
// 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 < ContentPropertyDisplay >
{
Id = groupId ,
Alias = groupName ,
2016-01-06 12:58:05 +01:00
Label = _localizedTextService . UmbracoDictionaryTranslate ( groupName ) ,
2016-12-17 21:12:45 -05:00
Properties = mappedProperties ,
2015-11-17 12:30:37 +01:00
IsActive = false
} ) ;
}
2013-09-02 15:40:14 +02:00
2015-11-17 12:30:37 +01:00
// 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
var genericproperties = Mapper . Map < IEnumerable < Property > , IEnumerable < ContentPropertyDisplay > > ( noGroupProperties ) . ToList ( ) ;
2014-11-20 15:19:58 +00:00
TranslateProperties ( genericproperties ) ;
2015-11-17 12:30:37 +01:00
tabs . Add ( new Tab < ContentPropertyDisplay >
{
2017-09-27 13:58:33 +02:00
Id = 0 ,
Label = _localizedTextService . Localize ( "general/properties" ) ,
2015-11-17 12:30:37 +01:00
Alias = "Generic properties" ,
Properties = genericproperties
} ) ;
2013-09-02 15:40:14 +02:00
2015-11-17 12:30:37 +01:00
// activate the first tab
tabs . First ( ) . IsActive = true ;
2013-09-02 15:40:14 +02:00
2015-11-17 12:30:37 +01:00
return tabs ;
2013-09-02 15:40:14 +02:00
}
2013-10-09 15:07:09 +02:00
2014-11-20 15:19:58 +00:00
private void TranslateProperties ( IEnumerable < ContentPropertyDisplay > properties )
{
// Not sure whether it's a good idea to add this to the ContentPropertyDisplay mapper
foreach ( var prop in properties )
{
2016-01-06 11:22:15 +01:00
prop . Label = _localizedTextService . UmbracoDictionaryTranslate ( prop . Label ) ;
prop . Description = _localizedTextService . UmbracoDictionaryTranslate ( prop . Description ) ;
2014-03-26 10:07:20 +00:00
}
2014-01-07 18:53:47 +01:00
}
2013-09-02 15:40:14 +02:00
}
}