2017-09-23 10:08:18 +02:00
using System ;
2013-09-02 15:40:14 +02:00
using System.Collections.Generic ;
using System.Linq ;
using AutoMapper ;
using Umbraco.Core ;
using Umbraco.Core.Models ;
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 ;
2017-05-30 18:13:11 +02:00
using Umbraco.Web.Composing ;
2013-09-02 15:40:14 +02:00
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Creates the tabs collection with properties assigned for display models
/// </summary>
2017-07-19 13:42:47 +02:00
internal class TabsAndPropertiesResolver
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
{
2017-09-12 16:22:16 +02:00
_localizedTextService = localizedTextService ? ? throw new ArgumentNullException ( nameof ( 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 )
2016-08-07 17:08:57 +02:00
{
2017-09-12 16:22:16 +02:00
IgnoreProperties = ignoreProperties ? ? throw new ArgumentNullException ( nameof ( 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">
2016-08-07 17:08:57 +02:00
/// Any additional custom properties to assign to the generic properties tab.
2013-09-02 15:40:14 +02:00
/// </param>
2015-10-28 12:28:29 +01:00
/// <param name="onGenericPropertiesMapped"></param>
2013-09-02 15:40:14 +02:00
/// <remarks>
2016-08-07 17:08:57 +02:00
/// 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 ( ) ;
2018-01-20 12:09:15 +01:00
var labelEditor = Current . PropertyEditors [ Constants . PropertyEditors . Aliases . NoEdit ] . ValueEditor . View ;
2013-09-02 15:40:14 +02:00
var contentProps = new List < ContentPropertyDisplay >
2015-10-28 12:28:29 +01:00
{
new ContentPropertyDisplay
{
2017-09-12 16:22:16 +02:00
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}id" ,
2015-10-28 12:28:29 +01:00
Label = "Id" ,
Value = Convert . ToInt32 ( display . Id ) . ToInvariantString ( ) + "<br/><small class='muted'>" + display . Key + "</small>" ,
View = labelEditor
} ,
new ContentPropertyDisplay
{
2017-09-12 16:22:16 +02:00
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}creator" ,
2016-01-06 11:22:15 +01:00
Label = localizedTextService . Localize ( "content/createBy" ) ,
Description = localizedTextService . Localize ( "content/createByDesc" ) ,
2015-10-28 12:28:29 +01:00
Value = display . Owner . Name ,
View = labelEditor
} ,
new ContentPropertyDisplay
2013-09-02 15:40:14 +02:00
{
2017-09-12 16:22:16 +02:00
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}createdate" ,
2016-01-06 11:22:15 +01:00
Label = localizedTextService . Localize ( "content/createDate" ) ,
Description = localizedTextService . Localize ( "content/createDateDesc" ) ,
2015-10-28 12:28:29 +01:00
Value = display . CreateDate . ToIsoString ( ) ,
View = labelEditor
} ,
new ContentPropertyDisplay
{
2017-09-12 16:22:16 +02:00
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}updatedate" ,
2016-01-06 11:22:15 +01:00
Label = localizedTextService . Localize ( "content/updateDate" ) ,
Description = localizedTextService . Localize ( "content/updateDateDesc" ) ,
2015-10-28 12:28:29 +01:00
Value = display . UpdateDate . ToIsoString ( ) ,
View = labelEditor
}
} ;
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
2017-09-23 10:08:18 +02:00
contentProps . AddRange ( currProps ) ;
2017-09-24 18:54:31 +02:00
2017-09-23 10:08:18 +02:00
//callback
2017-09-12 16:22:16 +02:00
onGenericPropertiesMapped ? . Invoke ( contentProps ) ;
2015-10-28 12:28:29 +01:00
2013-09-02 15:40:14 +02:00
//re-assign
genericProps . Properties = contentProps ;
}
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" :
2016-06-03 10:57:54 +02:00
dtdId = Constants . DataTypes . DefaultContentListView ;
2016-08-07 17:08:57 +02:00
2014-09-18 11:52:12 +10:00
break ;
case "media" :
2016-06-03 10:57:54 +02:00
dtdId = Constants . DataTypes . DefaultMediaListView ;
2014-09-18 11:52:12 +10:00
break ;
2014-09-19 09:47:42 +10:00
case "member" :
2016-06-03 10:57:54 +02:00
dtdId = Constants . DataTypes . DefaultMembersListView ;
2014-09-18 11:52:12 +10:00
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
2018-01-15 17:32:45 +01:00
var dt = dataTypeService . GetDataType ( customDtdName )
? ? dataTypeService . GetDataType ( dtdId ) ;
2014-09-22 18:18:09 +10:00
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" ) ;
}
2018-01-10 12:48:51 +01:00
var editor = Current . PropertyEditors [ dt . EditorAlias ] ;
2014-09-18 11:52:12 +10:00
if ( editor = = null )
{
2018-01-10 12:48:51 +01:00
throw new NullReferenceException ( "The property editor with alias " + dt . EditorAlias + " does not exist" ) ;
2014-09-18 11:52:12 +10:00
}
2014-09-15 13:53:33 +10:00
2017-09-12 16:22:16 +02:00
var listViewTab = new Tab < ContentPropertyDisplay >
{
Alias = Constants . Conventions . PropertyGroups . ListViewGroupName ,
Label = localizedTextService . Localize ( "content/childItems" ) ,
Id = display . Tabs . Count ( ) + 1 ,
IsActive = true
} ;
2014-09-18 09:48:08 +10:00
2018-02-05 17:48:54 +01:00
var listViewConfig = editor . ConfigurationEditor . ToConfigurationEditor ( dt . Configuration ) ;
2014-09-18 11:52:12 +10:00
//add the entity type to the config
2017-09-23 10:08:18 +02:00
listViewConfig [ "entityType" ] = entityType ;
2017-09-24 18:54:31 +02:00
2017-09-12 16:22:16 +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 ( ) ;
2017-09-23 10:08:18 +02:00
}
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
{
2017-09-12 16:22:16 +02:00
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView" ,
2014-09-18 09:48:08 +10:00
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 ) ;
}
2016-08-07 17:08:57 +02:00
private static void SetChildItemsTabPosition < TPersisted > ( TabbedContentItem < ContentPropertyDisplay , TPersisted > display ,
2015-11-15 21:18:21 +01:00
IDictionary < string , object > listViewConfig ,
2016-08-07 17:08:57 +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
}
2017-07-19 13:42:47 +02:00
public virtual IEnumerable < Tab < ContentPropertyDisplay > > Resolve ( IContentBase content )
2013-09-02 15:40:14 +02:00
{
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,
2016-08-07 17:08:57 +02:00
// and there might be duplicates (content does not work like contentType and there is no
2015-11-17 12:30:37 +01:00
// 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-05-12 14:49:44 +02:00
var properties = new List < Property > ( ) ;
2016-08-07 17:08:57 +02:00
2015-11-17 12:30:37 +01:00
// merge properties for groups with the same name
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
2017-05-12 14:49:44 +02: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 ;
2017-05-12 14:49:44 +02: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 ) ,
2017-05-12 14:49:44 +02: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 >
{
Id = 0 ,
2016-01-06 11:22:15 +01:00
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
}
}