Merge pull request #2558 from umbraco/temp-U4-11127

WIP - U4-11127 Update ContentController for editing variants
This commit is contained in:
Shannon Deminick
2018-04-10 13:39:47 +10:00
committed by GitHub
82 changed files with 793 additions and 526 deletions
+8 -107
View File
@@ -1,128 +1,32 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Umbraco.Core.Models;
namespace Umbraco.Core.Events
{
public class RecycleBinEventArgs : CancellableEventArgs, IEquatable<RecycleBinEventArgs>, IDeletingMediaFilesEventArgs
public class RecycleBinEventArgs : CancellableEventArgs, IEquatable<RecycleBinEventArgs>
{
public RecycleBinEventArgs(Guid nodeObjectType, Dictionary<int, IEnumerable<Property>> allPropertyData, bool emptiedSuccessfully)
: base(false)
public RecycleBinEventArgs(Guid nodeObjectType, EventMessages eventMessages)
: base(true, eventMessages)
{
AllPropertyData = allPropertyData;
NodeObjectType = nodeObjectType;
Ids = AllPropertyData.Select(x => x.Key);
RecycleBinEmptiedSuccessfully = emptiedSuccessfully;
Files = new List<string>();
}
public RecycleBinEventArgs(Guid nodeObjectType, bool emptiedSuccessfully)
: base(false)
{
AllPropertyData = new Dictionary<int, IEnumerable<Property>>();
NodeObjectType = nodeObjectType;
Ids = new int[0];
RecycleBinEmptiedSuccessfully = emptiedSuccessfully;
Files = new List<string>();
}
public RecycleBinEventArgs(Guid nodeObjectType, Dictionary<int, IEnumerable<Property>> allPropertyData)
: base(true)
{
AllPropertyData = allPropertyData;
NodeObjectType = nodeObjectType;
Ids = AllPropertyData.Select(x => x.Key);
Files = new List<string>();
}
public RecycleBinEventArgs(Guid nodeObjectType)
: base(true)
{
AllPropertyData = new Dictionary<int, IEnumerable<Property>>();
NodeObjectType = nodeObjectType;
Ids = new int[0];
Files = new List<string>();
}
/// <summary>
/// Backwards compatibility constructor
/// </summary>
/// <param name="nodeObjectType"></param>
/// <param name="allPropertyData"></param>
/// <param name="files"></param>
/// <param name="emptiedSuccessfully"></param>
internal RecycleBinEventArgs(Guid nodeObjectType, Dictionary<int, IEnumerable<Property>> allPropertyData, List<string> files, bool emptiedSuccessfully)
: base(false)
{
AllPropertyData = allPropertyData;
NodeObjectType = nodeObjectType;
Ids = AllPropertyData.Select(x => x.Key);
RecycleBinEmptiedSuccessfully = emptiedSuccessfully;
Files = files;
}
/// <summary>
/// Backwards compatibility constructor
/// </summary>
/// <param name="nodeObjectType"></param>
/// <param name="allPropertyData"></param>
/// <param name="files"></param>
internal RecycleBinEventArgs(Guid nodeObjectType, Dictionary<int, IEnumerable<Property>> allPropertyData, List<string> files)
: base(true)
{
AllPropertyData = allPropertyData;
NodeObjectType = nodeObjectType;
Ids = AllPropertyData.Select(x => x.Key);
Files = files;
}
[Obsolete("Use the other ctor that specifies all property data instead")]
public RecycleBinEventArgs(Guid nodeObjectType, IEnumerable<int> ids, List<string> files, bool emptiedSuccessfully)
: base(false)
{
NodeObjectType = nodeObjectType;
Ids = ids;
Files = files;
RecycleBinEmptiedSuccessfully = emptiedSuccessfully;
}
[Obsolete("Use the other ctor that specifies all property data instead")]
public RecycleBinEventArgs(Guid nodeObjectType, IEnumerable<int> ids, List<string> files)
: base(true)
{
NodeObjectType = nodeObjectType;
Ids = ids;
Files = files;
}
/// <summary>
/// Gets the Id of the node object type of the items
/// being deleted from the Recycle Bin.
/// </summary>
public Guid NodeObjectType { get; private set; }
/// <summary>
/// Gets the list of Ids for each of the items being deleted from the Recycle Bin.
/// </summary>
public IEnumerable<int> Ids { get; private set; }
/// <summary>
/// Gets the list of Files that should be deleted as part
/// of emptying the Recycle Bin.
/// </summary>
/// <remarks>
/// This list can be appended to during an event handling operation, generally this is done based on the property data contained in these event args
/// </remarks>
public List<string> Files { get; private set; }
public List<string> MediaFilesToDelete { get { return Files; } }
/// <summary>
/// Gets the list of all property data associated with a content id
/// </summary>
public Dictionary<int, IEnumerable<Property>> AllPropertyData { get; private set; }
public Guid NodeObjectType { get; }
/// <summary>
/// Boolean indicating whether the Recycle Bin was emptied successfully
/// </summary>
@@ -142,7 +46,7 @@ namespace Umbraco.Core.Events
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return base.Equals(other) && AllPropertyData.Equals(other.AllPropertyData) && Files.Equals(other.Files) && Ids.Equals(other.Ids) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully;
return base.Equals(other) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully;
}
public override bool Equals(object obj)
@@ -158,9 +62,6 @@ namespace Umbraco.Core.Events
unchecked
{
int hashCode = base.GetHashCode();
hashCode = (hashCode * 397) ^ AllPropertyData.GetHashCode();
hashCode = (hashCode * 397) ^ Files.GetHashCode();
hashCode = (hashCode * 397) ^ Ids.GetHashCode();
hashCode = (hashCode * 397) ^ NodeObjectType.GetHashCode();
hashCode = (hashCode * 397) ^ RecycleBinEmptiedSuccessfully.GetHashCode();
return hashCode;
@@ -250,7 +250,8 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1041, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext",
Configuration = "{\"group\":\"default\"}" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1043, EditorAlias = Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultContentListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultContentListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar",
Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMediaListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar",
Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMembersListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar",
+1 -13
View File
@@ -17,7 +17,6 @@ namespace Umbraco.Core.Models
private ITemplate _template;
private bool _published;
private PublishedState _publishedState;
private string _language;
private DateTime? _releaseDate;
private DateTime? _expireDate;
private string _nodeName;//NOTE Once localization is introduced this will be the non-localized Node Name.
@@ -79,7 +78,6 @@ namespace Umbraco.Core.Models
{
public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo<Content, ITemplate>(x => x.Template);
public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo<Content, bool>(x => x.Published);
public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<Content, string>(x => x.Language);
public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ReleaseDate);
public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ExpireDate);
public readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo<Content, string>(x => x.NodeName);
@@ -149,7 +147,7 @@ namespace Umbraco.Core.Models
/// is true or false, but can also temporarily be Publishing or Unpublishing when the
/// content item is about to be saved.</remarks>
[DataMember]
internal PublishedState PublishedState
public PublishedState PublishedState
{
get => _publishedState;
set
@@ -163,16 +161,6 @@ namespace Umbraco.Core.Models
[IgnoreDataMember]
public bool Edited { get; internal set; }
/// <summary>
/// Language of the data contained within this Content object.
/// </summary>
[Obsolete("This is not used and will be removed from the codebase in future versions")]
[EditorBrowsable(EditorBrowsableState.Never)]
public string Language
{
get => _language;
set => SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector);
}
/// <summary>
/// The date this Content should be released and thus be published
+5 -2
View File
@@ -19,6 +19,9 @@ namespace Umbraco.Core.Models
[DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")]
public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase
{
//fixme this should be invariant by default but for demo purposes and until the UI is updated to support changing a property type we'll make this neutral by default
private const ContentVariation DefaultVaryBy = ContentVariation.CultureNeutral;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private string _alias;
@@ -46,7 +49,7 @@ namespace Umbraco.Core.Models
_propertyTypes = new PropertyTypeCollection(IsPublishing);
_propertyTypes.CollectionChanged += PropertyTypesChanged;
_variations = ContentVariation.InvariantNeutral;
_variations = DefaultVaryBy;
}
protected ContentTypeBase(IContentTypeBase parent)
@@ -67,7 +70,7 @@ namespace Umbraco.Core.Models
_propertyTypes = new PropertyTypeCollection(IsPublishing);
_propertyTypes.CollectionChanged += PropertyTypesChanged;
_variations = ContentVariation.InvariantNeutral;
_variations = DefaultVaryBy;
}
/// <summary>
+3 -5
View File
@@ -18,6 +18,8 @@ namespace Umbraco.Core.Models
/// </summary>
bool Published { get; }
PublishedState PublishedState { get; }
/// <summary>
/// Gets a value indicating whether the content has been edited.
/// </summary>
@@ -54,11 +56,7 @@ namespace Umbraco.Core.Models
/// Gets the date and time the content was published.
/// </summary>
DateTime? PublishDate { get; }
[Obsolete("This will be removed in future versions")]
[EditorBrowsable(EditorBrowsableState.Never)]
string Language { get; set; }
/// <summary>
/// Gets or sets the date and time the content item should be published.
/// </summary>
@@ -21,6 +21,7 @@ namespace Umbraco.Core.Models
var val = media.Properties[propertyType];
if (val == null) return string.Empty;
//fixme doesn't take into account variants
var jsonString = val.GetValue() as string;
if (jsonString == null) return string.Empty;
+1 -1
View File
@@ -89,7 +89,7 @@ namespace Umbraco.Core.Models
/// Gets the list of values.
/// </summary>
[DataMember]
public List<PropertyValue> Values
public IReadOnlyCollection<PropertyValue> Values
{
get => _values;
set
@@ -112,6 +112,7 @@ namespace Umbraco.Core.Models
private static void RemoveTags(this Property property, IEnumerable<string> tags, TagsStorageType storageType, char delimiter)
{
// already empty = nothing to do
//fixme doesn't take into account variants
var value = property.GetValue()?.ToString();
if (string.IsNullOrWhiteSpace(value)) return;
@@ -145,6 +146,7 @@ namespace Umbraco.Core.Models
{
if (property == null) throw new ArgumentNullException(nameof(property));
//fixme doesn't take into account variants
var value = property.GetValue()?.ToString();
if (string.IsNullOrWhiteSpace(value)) return Enumerable.Empty<string>();
@@ -154,6 +156,7 @@ namespace Umbraco.Core.Models
return value.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
case TagsStorageType.Json:
//fixme doesn't take into account variants
return JsonConvert.DeserializeObject<JArray>(property.GetValue().ToString()).Select(x => x.ToString().Trim());
default:
@@ -167,9 +170,7 @@ namespace Umbraco.Core.Models
/// <param name="property">The property.</param>
/// <param name="value">The property value.</param>
/// <param name="tagConfiguration">The datatype configuration.</param>
/// <param name="tagPropertyEditorAttribute">The property editor tags configuration attribute.</param>
/// <remarks>
/// <para>The tags configuration is specified by the <paramref name="tagPropertyEditorAttribute"/> marking the property editor.</para>
/// <remarks>
/// <para>The value is either a string (delimited string) or an enumeration of strings (tag list).</para>
/// <para>This is used both by the content repositories to initialize a property with some tag values, and by the
/// content controllers to update a property with values received from the property editor.</para>
+9 -4
View File
@@ -19,7 +19,10 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
[DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")]
public class PropertyType : EntityBase, IEquatable<PropertyType>
{
{
//fixme this should be invariant by default but for demo purposes and until the UI is updated to support changing a property type we'll make this neutral by default
private const ContentVariation DefaultVaryBy = ContentVariation.CultureNeutral;
private static PropertySelectors _selectors;
private readonly bool _forceValueStorageType;
@@ -47,7 +50,7 @@ namespace Umbraco.Core.Models
_propertyEditorAlias = dataType.EditorAlias;
_valueStorageType = dataType.DatabaseType;
_variations = ContentVariation.InvariantNeutral;
_variations = DefaultVaryBy;
}
/// <summary>
@@ -84,7 +87,7 @@ namespace Umbraco.Core.Models
_valueStorageType = valueStorageType;
_forceValueStorageType = forceValueStorageType;
_alias = propertyTypeAlias == null ? null : SanitizeAlias(propertyTypeAlias);
_variations = ContentVariation.InvariantNeutral;
_variations = DefaultVaryBy;
}
private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors());
@@ -391,7 +394,9 @@ namespace Umbraco.Core.Models
{
throw new InvalidOperationException($"Cannot assign value \"{value}\" of type \"{value.GetType()}\" to property \"{alias}\" expecting type \"{expected}\".");
}
//fixme - perhaps this and other validation methods should be a service level (not a model) thing?
/// <summary>
/// Determines whether a value is valid for this property type.
/// </summary>
@@ -39,14 +39,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="entityId"></param>
/// <returns></returns>
EntityPermissionCollection GetPermissionsForEntity(int entityId);
///// <summary>
///// Gets the implicit/inherited list of permissions for the content item
///// </summary>
///// <param name="path"></param>
///// <returns></returns>
//IEnumerable<EntityPermission> GetPermissionsForPath(string path);
/// <summary>
/// Used to add/update a permission for a content item
/// </summary>
@@ -476,6 +476,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (!tagConfigurations.TryGetValue(property.PropertyType.PropertyEditorAlias, out var tagConfiguration))
continue;
//fixme doesn't take into account variants
property.SetTagsValue(property.GetValue(), tagConfiguration);
}
@@ -67,7 +67,7 @@ namespace Umbraco.Core.PropertyEditors
: JsonConvert.DeserializeObject<Dictionary<string, object>>(configurationJson);
/// <inheritdoc />
public virtual object FromConfigurationEditor(Dictionary<string, object> editorValues, object configuration)
public virtual object FromConfigurationEditor(IDictionary<string, object> editorValues, object configuration)
{
// by default, return the posted dictionary
// but only keep entries that have a non-null/empty value
@@ -80,7 +80,7 @@ namespace Umbraco.Core.PropertyEditors
}
/// <inheritdoc />
public virtual Dictionary<string, object> ToConfigurationEditor(object configuration)
public virtual IDictionary<string, object> ToConfigurationEditor(object configuration)
{
// editors that do not override ToEditor/FromEditor have their configuration
// as a dictionary of <string, object> and, by default, we merge their default
@@ -100,7 +100,7 @@ namespace Umbraco.Core.PropertyEditors
}
/// <inheritdoc />
public virtual Dictionary<string, object> ToValueEditor(object configuration)
public virtual IDictionary<string, object> ToValueEditor(object configuration)
=> ToConfigurationEditor(configuration);
/// <summary>
@@ -133,4 +133,4 @@ namespace Umbraco.Core.PropertyEditors
}
}
}
}
}
@@ -113,7 +113,7 @@ namespace Umbraco.Core.PropertyEditors
}
/// <inheritdoc />
public sealed override object FromConfigurationEditor(Dictionary<string, object> editorValues, object configuration)
public sealed override object FromConfigurationEditor(IDictionary<string, object> editorValues, object configuration)
{
return FromConfigurationEditor(editorValues, (TConfiguration) configuration);
}
@@ -123,7 +123,7 @@ namespace Umbraco.Core.PropertyEditors
/// </summary>
/// <param name="editorValues">The configuration object posted by the editor.</param>
/// <param name="configuration">The current configuration object.</param>
public virtual TConfiguration FromConfigurationEditor(Dictionary<string, object> editorValues, TConfiguration configuration)
public virtual TConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, TConfiguration configuration)
{
// note - editorValue contains a mix of Clr types (string, int...) and JToken
// turning everything back into a JToken... might not be fastest but is simplest
@@ -144,7 +144,7 @@ namespace Umbraco.Core.PropertyEditors
}
/// <inheritdoc />
public sealed override Dictionary<string, object> ToConfigurationEditor(object configuration)
public sealed override IDictionary<string, object> ToConfigurationEditor(object configuration)
{
return ToConfigurationEditor((TConfiguration) configuration);
}
@@ -152,7 +152,6 @@ namespace Umbraco.Core.PropertyEditors
/// <summary>
/// Converts configuration values to values for the editor.
/// </summary>
/// <param name="defaultConfiguration">The default configuration.</param>
/// <param name="configuration">The configuration.</param>
public virtual Dictionary<string, object> ToConfigurationEditor(TConfiguration configuration)
{
@@ -170,4 +169,4 @@ namespace Umbraco.Core.PropertyEditors
return ObjectExtensions.ToObjectDictionary(configuration, FieldNamer);
}
}
}
}
@@ -19,8 +19,7 @@ namespace Umbraco.Core.PropertyEditors
public class DataEditor : IDataEditor
{
private IDictionary<string, object> _defaultConfiguration;
private IDataValueEditor _valueEditorAssigned;
/// <summary>
/// Initializes a new instance of the <see cref="DataEditor"/> class.
/// </summary>
@@ -160,10 +159,6 @@ namespace Umbraco.Core.PropertyEditors
/// <returns></returns>
protected virtual IDataValueEditor CreateValueEditor()
{
// handle assigned editor, or create a new one
if (_valueEditorAssigned != null)
return _valueEditorAssigned;
if (Attribute == null)
throw new InvalidOperationException("The editor does not specify a view.");
@@ -232,22 +232,24 @@ namespace Umbraco.Core.PropertyEditors
// eg
// [ { "value": "hello" }, { "lang": "fr-fr", "value": "bonjour" } ]
/// <summary>
/// A method to deserialize the string value that has been saved in the content editor
/// to an object to be stored in the database.
/// </summary>
/// <param name="editorValue"></param>
/// <param name="currentValue">
/// The current value that has been persisted to the database for this editor. This value may be usesful for
/// how the value then get's deserialized again to be re-persisted. In most cases it will probably not be used.
/// </param>
/// <summary>
/// A method to deserialize the string value that has been saved in the content editor
/// to an object to be stored in the database.
/// </summary>
/// <param name="editorValue"></param>
/// <param name="currentValue">
/// The current value that has been persisted to the database for this editor. This value may be usesful for
/// how the value then get's deserialized again to be re-persisted. In most cases it will probably not be used.
/// </param>
/// <param name="languageId"></param>
/// <param name="segment"></param>
/// <returns></returns>
/// <remarks>
/// By default this will attempt to automatically convert the string value to the value type supplied by ValueType.
///
/// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the
/// value to the DB will fail when it tries to validate the value type.
/// </remarks>
/// <remarks>
/// By default this will attempt to automatically convert the string value to the value type supplied by ValueType.
///
/// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the
/// value to the DB will fail when it tries to validate the value type.
/// </remarks>
public virtual object FromEditor(ContentPropertyData editorValue, object currentValue)
{
//if it's json but it's empty json, then return null
@@ -269,16 +271,18 @@ namespace Umbraco.Core.PropertyEditors
/// A method used to format the database value to a value that can be used by the editor
/// </summary>
/// <param name="property"></param>
/// <param name="propertyType"></param>
/// <param name="dataTypeService"></param>
/// <param name="languageId"></param>
/// <param name="segment"></param>
/// <returns></returns>
/// <remarks>
/// The object returned will automatically be serialized into json notation. For most property editors
/// the value returned is probably just a string but in some cases a json structure will be returned.
/// </remarks>
public virtual object ToEditor(Property property, IDataTypeService dataTypeService)
public virtual object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null)
{
if (property.GetValue() == null) return string.Empty;
var val = property.GetValue(languageId, segment);
if (val == null) return string.Empty;
switch (ValueTypes.ToStorageType(ValueType))
{
@@ -286,7 +290,7 @@ namespace Umbraco.Core.PropertyEditors
case ValueStorageType.Nvarchar:
//if it is a string type, we will attempt to see if it is json stored data, if it is we'll try to convert
//to a real json object so we can pass the true json object directly to angular!
var asString = property.GetValue().ToString();
var asString = val.ToString();
if (asString.DetectIsJson())
{
try
@@ -304,12 +308,12 @@ namespace Umbraco.Core.PropertyEditors
case ValueStorageType.Decimal:
//Decimals need to be formatted with invariant culture (dots, not commas)
//Anything else falls back to ToString()
var decim = property.GetValue().TryConvertTo<decimal>();
var decim = val.TryConvertTo<decimal>();
return decim.Success
? decim.Result.ToString(NumberFormatInfo.InvariantInfo)
: property.GetValue().ToString();
: val.ToString();
case ValueStorageType.Date:
var date = property.GetValue().TryConvertTo<DateTime?>();
var date = val.TryConvertTo<DateTime?>();
if (date.Success == false || date.Result == null)
{
return string.Empty;
@@ -39,18 +39,18 @@ namespace Umbraco.Core.PropertyEditors
/// </summary>
/// <param name="editorValues">The values posted by the configuration editor.</param>
/// <param name="configuration">The current configuration object.</param>
object FromConfigurationEditor(Dictionary<string, object> editorValues, object configuration);
object FromConfigurationEditor(IDictionary<string, object> editorValues, object configuration);
/// <summary>
/// Converts the configuration object to values for the configuration editor.
/// </summary>
/// <param name="configuration">The configuration.</param>
Dictionary<string, object> ToConfigurationEditor(object configuration);
IDictionary<string, object> ToConfigurationEditor(object configuration);
/// <summary>
/// Converts the configuration object to values for the value editror.
/// </summary>
/// <param name="configuration">The configuration.</param>
Dictionary<string, object> ToValueEditor(object configuration);
IDictionary<string, object> ToValueEditor(object configuration);
}
}
}
@@ -61,7 +61,7 @@ namespace Umbraco.Core.PropertyEditors
/// <summary>
/// Converts a property value to a value for the editor.
/// </summary>
object ToEditor(Property property, IDataTypeService dataTypeService);
object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null);
// fixme - editing - document or remove these
// why property vs propertyType? services should be injected! etc...
+1 -1
View File
@@ -329,7 +329,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Empties the recycle bin.
/// </summary>
void EmptyRecycleBin();
OperationResult EmptyRecycleBin();
/// <summary>
/// Sorts documents.
+1 -1
View File
@@ -248,7 +248,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
void EmptyRecycleBin();
OperationResult EmptyRecycleBin();
/// <summary>
/// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
@@ -1518,11 +1518,11 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IContent"/> that resides in the bin
/// </summary>
public void EmptyRecycleBin()
public OperationResult EmptyRecycleBin()
{
var nodeObjectType = Constants.ObjectTypes.Document;
var deleted = new List<IContent>();
var evtMsgs = EventMessagesFactory.Get(); // todo - and then?
var evtMsgs = EventMessagesFactory.Get();
using (var scope = ScopeProvider.CreateScope())
{
@@ -1533,11 +1533,11 @@ namespace Umbraco.Core.Services.Implement
// are managed by Delete, and not here.
// no idea what those events are for, keep a simplified version
var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType);
var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType, evtMsgs);
if (scope.Events.DispatchCancelable(EmptyingRecycleBin, this, recycleBinEventArgs))
{
scope.Complete();
return; // causes rollback
return OperationResult.Cancel(evtMsgs);
}
// emptying the recycle bin means deleting whetever is in there - do it properly!
@@ -1557,6 +1557,8 @@ namespace Umbraco.Core.Services.Implement
scope.Complete();
}
return OperationResult.Succeed(evtMsgs);
}
#endregion
@@ -2311,7 +2313,7 @@ namespace Umbraco.Core.Services.Implement
content.WriterId = userId;
foreach (var property in blueprint.Properties)
content.SetValue(property.Alias, property.GetValue());
content.SetValue(property.Alias, property.GetValue()); //fixme doesn't take into account variants
return content;
}
@@ -1185,7 +1185,7 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
public void EmptyRecycleBin()
public OperationResult EmptyRecycleBin()
{
var nodeObjectType = Constants.ObjectTypes.Media;
var deleted = new List<IMedia>();
@@ -1206,12 +1206,12 @@ namespace Umbraco.Core.Services.Implement
// emptying the recycle bin means deleting whetever is in there - do it properly!
// are managed by Delete, and not here.
// no idea what those events are for, keep a simplified version
var args = new RecycleBinEventArgs(nodeObjectType);
var args = new RecycleBinEventArgs(nodeObjectType, evtMsgs);
if (scope.Events.DispatchCancelable(EmptyingRecycleBin, this, args))
{
scope.Complete();
return;
return OperationResult.Cancel(evtMsgs);
}
// emptying the recycle bin means deleting whetever is in there - do it properly!
var query = Query<IMedia>().Where(x => x.ParentId == Constants.System.RecycleBinMedia);
@@ -1227,6 +1227,8 @@ namespace Umbraco.Core.Services.Implement
Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, Constants.System.RecycleBinMedia);
scope.Complete();
}
return OperationResult.Succeed(evtMsgs);
}
#endregion
@@ -370,6 +370,8 @@ namespace Umbraco.Core.Services.Implement
var props = content.Properties.ToArray();
foreach (var p in props)
{
//fixme doesn't take into account variants
var newText = p.GetValue() != null ? p.GetValue().ToString() : "";
var oldText = newText;
@@ -0,0 +1,36 @@
using System.Linq;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
{
public static class LocalizationServiceExtensions
{
/// <summary>
/// Returns the configured default variant language
/// </summary>
/// <param name="service"></param>
/// <returns></returns>
public static ILanguage GetDefaultVariantLanguage(this ILocalizationService service)
{
var langs = service.GetAllLanguages().OrderBy(x => x.Id).ToList();
if (langs.Count == 0) return null;
//if there's only one language, by default it is the default
if (langs.Count == 1)
{
langs[0].IsDefaultVariantLanguage = true;
langs[0].Mandatory = true;
return langs[0];
}
var foundDefault = langs.FirstOrDefault(x => x.IsDefaultVariantLanguage);
if (foundDefault != null) return foundDefault;
//if no language has the default flag, then the defaul language is the one with the lowest id
langs[0].IsDefaultVariantLanguage = true;
langs[0].Mandatory = true;
return langs[0];
}
}
}
+1
View File
@@ -135,6 +135,7 @@ namespace Umbraco.Core
/// <returns></returns>
public static bool DetectIsJson(this string input)
{
if (input.IsNullOrWhiteSpace()) return false;
input = input.Trim();
return (input.StartsWith("{") && input.EndsWith("}"))
|| (input.StartsWith("[") && input.EndsWith("]"));
+1
View File
@@ -1413,6 +1413,7 @@
<Compile Include="Services\IUserService.cs" />
<Compile Include="Services\Implement\LocalizationService.cs" />
<Compile Include="Services\Implement\LocalizedTextService.cs" />
<Compile Include="Services\LocalizationServiceExtensions.cs" />
<Compile Include="Services\LocalizedTextServiceExtensions.cs" />
<Compile Include="Services\Implement\LocalizedTextServiceFileSources.cs" />
<Compile Include="Services\Implement\LocalizedTextServiceSupplementaryFileSource.cs" />
@@ -347,6 +347,7 @@ namespace Umbraco.Examine
foreach (var property in c.Properties)
{
//only add the value if its not null or empty (we'll check for string explicitly here too)
//fixme support variants with language id
var val = property.GetValue();
switch (val)
{
@@ -160,6 +160,19 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- get NuGet packages directory -->
<PropertyGroup>
<PostBuildEvent>
REM if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
REM xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
REM if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
REM xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"</PostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PreBuildEvent>REM xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\amd64\*.* "$(TargetDir)amd64\" /Y /F /E /I /C /D
REM xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" /Y /F /E /I /C /D</PreBuildEvent>
<PostBuildEvent>REM if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
REM xcopy /s /y "$(NugetPackages)\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
REM if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
REM xcopy /s /y "$(NugetPackages)\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"</PostBuildEvent>
<NuGetPackages>$(NuGetPackageFolders.Split(';')[0])</NuGetPackages>
</PropertyGroup>
<Target Name="BeforeBuild">
@@ -86,7 +86,7 @@ namespace Umbraco.Tests.Cache
new EventDefinition<IMediaService, DeleteEventArgs<IMedia>>(null, serviceContext.MediaService, new DeleteEventArgs<IMedia>(Enumerable.Empty<IMedia>())),
new EventDefinition<IMediaService, MoveEventArgs<IMedia>>(null, serviceContext.MediaService, new MoveEventArgs<IMedia>(new MoveEventInfo<IMedia>(null, "", -1)), "Moved"),
new EventDefinition<IMediaService, MoveEventArgs<IMedia>>(null, serviceContext.MediaService, new MoveEventArgs<IMedia>(new MoveEventInfo<IMedia>(null, "", -1)), "Trashed"),
new EventDefinition<IMediaService, RecycleBinEventArgs>(null, serviceContext.MediaService, new RecycleBinEventArgs(Guid.NewGuid(), new Dictionary<int, IEnumerable<Property>>(), true)),
new EventDefinition<IMediaService, RecycleBinEventArgs>(null, serviceContext.MediaService, new RecycleBinEventArgs(Guid.NewGuid())),
new EventDefinition<IContentService, SaveEventArgs<IContent>>(null, serviceContext.ContentService, new SaveEventArgs<IContent>(Enumerable.Empty<IContent>()), "Saved"),
new EventDefinition<IContentService, DeleteEventArgs<IContent>>(null, serviceContext.ContentService, new DeleteEventArgs<IContent>(Enumerable.Empty<IContent>()), "Deleted"),
@@ -97,7 +97,7 @@ namespace Umbraco.Tests.Cache
new EventDefinition<IContentService, CopyEventArgs<IContent>>(null, serviceContext.ContentService, new CopyEventArgs<IContent>(null, null, -1)),
new EventDefinition<IContentService, MoveEventArgs<IContent>>(null, serviceContext.ContentService, new MoveEventArgs<IContent>(new MoveEventInfo<IContent>(null, "", -1)), "Trashed"),
new EventDefinition<IContentService, RecycleBinEventArgs>(null, serviceContext.ContentService, new RecycleBinEventArgs(Guid.NewGuid(), new Dictionary<int, IEnumerable<Property>>(), true)),
new EventDefinition<IContentService, RecycleBinEventArgs>(null, serviceContext.ContentService, new RecycleBinEventArgs(Guid.NewGuid())),
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, serviceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "Published"),
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, serviceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "UnPublished"),
-4
View File
@@ -194,7 +194,6 @@ namespace Umbraco.Tests.Models
content.CreatorId = 22;
content.ExpireDate = DateTime.Now;
content.Key = Guid.NewGuid();
content.Language = "en";
content.Level = 3;
content.Path = "-1,4,10";
content.ReleaseDate = DateTime.Now;
@@ -254,7 +253,6 @@ namespace Umbraco.Tests.Models
content.CreatorId = 22;
content.ExpireDate = DateTime.Now;
content.Key = Guid.NewGuid();
content.Language = "en";
content.Level = 3;
content.Path = "-1,4,10";
content.ReleaseDate = DateTime.Now;
@@ -294,7 +292,6 @@ namespace Umbraco.Tests.Models
Assert.AreEqual(clone.CreatorId, content.CreatorId);
Assert.AreEqual(clone.ExpireDate, content.ExpireDate);
Assert.AreEqual(clone.Key, content.Key);
Assert.AreEqual(clone.Language, content.Language);
Assert.AreEqual(clone.Level, content.Level);
Assert.AreEqual(clone.Path, content.Path);
Assert.AreEqual(clone.ReleaseDate, content.ReleaseDate);
@@ -355,7 +352,6 @@ namespace Umbraco.Tests.Models
content.CreatorId = 22;
content.ExpireDate = DateTime.Now;
content.Key = Guid.NewGuid();
content.Language = "en";
content.Level = 3;
content.Path = "-1,4,10";
content.ReleaseDate = DateTime.Now;
@@ -42,6 +42,11 @@
}
}
//init can be called more than once and we don't want to have multiple bound events
for (var e in evts) {
eventsService.unsubscribe(evts[e]);
}
evts.push(eventsService.on("editors.content.changePublishDate", function (event, args) {
createButtons(args.node);
}));
@@ -53,61 +58,6 @@
// We don't get the info tab from the server from version 7.8 so we need to manually add it
//contentEditingHelper.addInfoTab($scope.content.tabs);
// prototype variants
$scope.content.variants = [
{
"cultureDisplayName": "English (United States)",
"culture": "en-US",
"current": true,
"segments" : [
{
"name": "Mobile"
},
{
"name": "Job campign"
}
],
"state": "Published"
},
{
"cultureDisplayName": "Danish",
"culture": "da-DK",
"current": false,
"segments" : [
{
"name": "Mobile"
}
],
"state": "Published"
},
{
"cultureDisplayName": "Spanish (Spain)",
"culture": "es-ES",
"current": false,
"state": "Published (pending changes)"
},
{
"cultureDisplayName": "French (France)",
"culture": "fr-FR",
"current": false,
"segments" : [
{
"name": "Mobile"
},
{
"name": "Job campign"
}
],
"state": "Draft"
},
{
"cultureDisplayName": "German (Germany)",
"culture": "de-DE",
"current": false,
"state": "Draft"
}
];
// prototype content and info apps
var contentApp = {
"name": "Content",
@@ -152,20 +102,30 @@
$scope.content.apps[0].active = true;
// create new editor for split view
if($scope.editors.length === 0) {
var editor = {};
editor.content = $scope.content;
$scope.editors.push(editor);
}
if ($scope.editors.length === 0) {
var editor = {
content: $scope.content
};
$scope.editors.push(editor);
}
else if ($scope.editors.length === 1) {
$scope.editors[0].content = $scope.content
}
else {
//fixme - need to fix something here if we are re-loading a content item that is in a split view
}
}
function getNode() {
/**
* This does the content loading and initializes everything, called on load and changing variants
* @param {any} languageId
*/
function getNode(languageId) {
$scope.page.loading = true;
//we are editing so get the content item from the server
$scope.getMethod()($scope.contentId)
$scope.getMethod()($scope.contentId, languageId)
.then(function (data) {
$scope.content = data;
@@ -470,7 +430,11 @@
angular.forEach(variants, function(variant) {
variant.current = false;
});
selectedVariant.current = true;
//go get the variant
getNode(selectedVariant.language.id);
}
$scope.closeSplitView = function(index, editor) {
@@ -497,6 +461,7 @@
}, 100);
// fake loading of content
// TODO: Make this real, but how do we deal with saving since currently we only save one variant at a time?!
$timeout(function(){
$scope.editors[editorIndex].content = angular.copy($scope.content);
$scope.editors[editorIndex].content.name = "What a variant";
@@ -136,13 +136,13 @@
}
// published node
if(node.hasPublishedVersion === true && node.publishDate && node.published === true) {
if(node.publishDate && node.published === true) {
scope.publishStatus.label = localizationService.localize("content_published");
scope.publishStatus.color = "success";
}
// published node with pending changes
if(node.hasPublishedVersion === true && node.publishDate && node.published === false) {
if (node.edited === true && node.publishDate) {
scope.publishStatus.label = localizationService.localize("content_publishedPendingChanges");
scope.publishStatus.color = "success"
}
@@ -313,17 +313,18 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
* });
* </pre>
*
* @param {Int} id id of content item to return
* @param {Int} id id of content item to return
* @param {Int} languageId optional ID of the language to retrieve the item in
* @returns {Promise} resourcePromise object containing the content item.
*
*/
getById: function (id) {
getById: function (id, languageId) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetById",
[{ id: id }])),
{ id: id, languageId: languageId })),
'Failed to retrieve data for content id ' + id);
},
@@ -181,11 +181,13 @@
var firstAllowedLayout = {};
for (var i = 0; layouts.length > i; i++) {
var layout = layouts[i];
if (layout.selected === true) {
firstAllowedLayout = layout;
break;
if (layouts != null) {
for (var i = 0; layouts.length > i; i++) {
var layout = layouts[i];
if (layout.selected === true) {
firstAllowedLayout = layout;
break;
}
}
}
@@ -324,6 +324,16 @@
//this is basically the same as for media but we need to explicitly add some extra properties
var saveModel = this.formatMediaPostData(displayModel, action);
//get the selected variant
var currVariant = _.find(displayModel.variants,
function(v) {
return v.current === true;
});
if (currVariant) {
saveModel.languageId = currVariant.language.id;
}
var propExpireDate = displayModel.removeDate;
var propReleaseDate = displayModel.releaseDate;
var propTemplate = displayModel.template;
@@ -50,15 +50,15 @@
server-validation-field="Alias">
</umb-generate-alias>
<a ng-if="variants.length > 0" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen">
<span>{{vm.currentVariant.cultureDisplayName}}</span>
<a ng-if="variants.length > 1" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen">
<span>{{vm.currentVariant.language.name}}</span>
<ins class="umb-variant-switcher__expand" ng-class="{'icon-navigation-down': !vm.dropdownOpen, 'icon-navigation-up': vm.dropdownOpen}">&nbsp;</ins>
</a>
<umb-dropdown ng-if="vm.dropdownOpen" style="width: 100%; max-height: 250px; overflow-y: scroll; margin-top: 5px;" on-close="vm.dropdownOpen = false" umb-keyboard-list>
<umb-dropdown-item class="umb-variant-switcher__item" ng-class="{'umb-variant-switcher_item--current': variant.current}" ng-repeat="variant in variants">
<a href="" class="umb-variant-switcher__name-wrapper" ng-click="selectVariant($event, variant)" prevent-default>
<span class="umb-variant-switcher__name">{{variant.cultureDisplayName}}</span>
<span class="umb-variant-switcher__name">{{variant.language.name}}</span>
<span class="umb-variant-switcher__state">{{variant.state}}</span>
</a>
<div ng-if="splitViewOpen !== true" class="umb-variant-switcher__split-view" ng-click="openInSplitView($event, variant)">Open in split view</div>
@@ -115,4 +115,4 @@
view="dialogModel.view">
</umb-overlay>
</div>
</div>
+55 -13
View File
@@ -25,8 +25,10 @@ using Umbraco.Core.Persistence.Querying;
using Umbraco.Web.PublishedCache;
using Umbraco.Core.Events;
using Umbraco.Core.Models.Validation;
using Umbraco.Web.Models;
using Umbraco.Web._Legacy.Actions;
using Constants = Umbraco.Core.Constants;
using ContentVariation = Umbraco.Core.Models.ContentVariation;
namespace Umbraco.Web.Editors
{
@@ -71,7 +73,7 @@ namespace Umbraco.Web.Editors
public IEnumerable<ContentItemDisplay> GetByIds([FromUri]int[] ids)
{
var foundContent = Services.ContentService.GetByIds(ids);
return foundContent.Select(x => ContextMapper.Map<IContent, ContentItemDisplay>(x, UmbracoContext));
return foundContent.Select(x => MapToDisplay(x));
}
/// <summary>
@@ -223,7 +225,7 @@ namespace Umbraco.Web.Editors
HandleContentNotFound(id);
}
var content = ContextMapper.Map<IContent, ContentItemDisplay>(foundContent, UmbracoContext);
var content = MapToDisplay(foundContent);
SetupBlueprint(content, foundContent);
@@ -250,18 +252,20 @@ namespace Umbraco.Web.Editors
/// Gets the content json for the content id
/// </summary>
/// <param name="id"></param>
/// <param name="languageId"></param>
/// <returns></returns>
[OutgoingEditorModelEvent]
[EnsureUserPermissionForContent("id")]
public ContentItemDisplay GetById(int id)
public ContentItemDisplay GetById(int id, int? languageId = null)
{
var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
if (foundContent == null)
{
HandleContentNotFound(id);
return null;//irrelevant since the above throws
}
var content = ContextMapper.Map<IContent, ContentItemDisplay>(foundContent, UmbracoContext);
var content = MapToDisplay(foundContent, languageId);
return content;
}
@@ -274,7 +278,7 @@ namespace Umbraco.Web.Editors
HandleContentNotFound(id);
}
var content = ContextMapper.Map<IContent, ContentItemDisplay>(foundContent, UmbracoContext);
var content = MapToDisplay(foundContent);
return content;
}
@@ -297,11 +301,16 @@ namespace Umbraco.Web.Editors
}
var emptyContent = Services.ContentService.Create("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0));
var mapped = ContextMapper.Map<IContent, ContentItemDisplay>(emptyContent, UmbracoContext);
var mapped = MapToDisplay(emptyContent);
//remove this tab if it exists: umbContainerView
var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName);
mapped.Tabs = mapped.Tabs.Except(new[] { containerTab });
mapped.Tabs = mapped.Tabs.Except(new[] { containerTab });
//Remove all variants except for the default since currently the default must be saved before other variants can be edited
//TODO: Allow for editing all variants at once ... this will be a future task
mapped.Variants = new[] {mapped.Variants.First(x => x.IsCurrent)};
return mapped;
}
@@ -536,7 +545,16 @@ namespace Umbraco.Web.Editors
[OutgoingEditorModelEvent]
public ContentItemDisplay PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem)
{
return PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id));
var contentItemDisplay = PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id));
//ensure the active language is still selected
if (contentItem.LanguageId.HasValue)
{
foreach (var contentVariation in contentItemDisplay.Variants)
{
contentVariation.IsCurrent = contentVariation.Language.Id == contentItem.LanguageId;
}
}
return contentItemDisplay;
}
private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func<IContent, OperationResult> saveMethod)
@@ -561,7 +579,7 @@ namespace Umbraco.Web.Editors
{
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
// add the modelstate to the outgoing object and throw a validation message
var forDisplay = ContextMapper.Map<IContent, ContentItemDisplay>(contentItem.PersistedContent, UmbracoContext);
var forDisplay = MapToDisplay(contentItem.PersistedContent, contentItem.LanguageId);
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
@@ -598,13 +616,13 @@ namespace Umbraco.Web.Editors
else
{
//publish the item and check if it worked, if not we will show a diff msg below
contentItem.PersistedContent.PublishValues(); // fixme variants?
contentItem.PersistedContent.PublishValues(contentItem.LanguageId);
publishStatus = Services.ContentService.SaveAndPublish(contentItem.PersistedContent, Security.CurrentUser.Id);
wasCancelled = publishStatus.Result == PublishResultType.FailedCancelledByEvent;
}
//return the updated model
var display = ContextMapper.Map<IContent, ContentItemDisplay>(contentItem.PersistedContent, UmbracoContext);
var display = MapToDisplay(contentItem.PersistedContent, contentItem.LanguageId);
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
HandleInvalidModelState(display);
@@ -858,7 +876,7 @@ namespace Umbraco.Web.Editors
var unpublishResult = Services.ContentService.Unpublish(foundContent, Security.CurrentUser.Id);
var content = ContextMapper.Map<IContent, ContentItemDisplay>(foundContent, UmbracoContext);
var content = MapToDisplay(foundContent);
if (unpublishResult.Success == false)
{
@@ -903,7 +921,10 @@ namespace Umbraco.Web.Editors
}
}
base.MapPropertyValues(contentItem);
base.MapPropertyValues<IContent, ContentItemSave>(
contentItem,
(save, property) => property.GetValue(save.LanguageId), //get prop val
(save, property, v) => property.SetValue(v, save.LanguageId)); //set prop val
}
/// <summary>
@@ -1089,6 +1110,27 @@ namespace Umbraco.Web.Editors
}
return allowed;
}
/// <summary>
/// Used to map an <see cref="IContent"/> instance to a <see cref="ContentItemDisplay"/> and ensuring a language is present if required
/// </summary>
/// <param name="content"></param>
/// <param name="languageId"></param>
/// <returns></returns>
private ContentItemDisplay MapToDisplay(IContent content, int? languageId = null)
{
//a languageId must exist in the mapping context if this content item has any property type that can be varied by language
//otherwise the property validation will fail since it's expecting to be get/set with a language ID. If a languageId is not explicitly
//sent up, then it means that the user is editing the default variant language.
if (!languageId.HasValue && content.HasLanguageVariantPropertyType())
{
languageId = Services.LocalizationService.GetDefaultVariantLanguage().Id;
}
var display = ContextMapper.Map<IContent, ContentItemDisplay>(content, UmbracoContext,
new Dictionary<string, object> { { ContextMapper.LanguageKey, languageId } });
return display;
}
}
}
@@ -25,7 +25,7 @@ namespace Umbraco.Web.Editors
{
protected HttpResponseMessage HandleContentNotFound(object id, bool throwException = true)
{
ModelState.AddModelError("id", string.Format("content with id: {0} was not found", id));
ModelState.AddModelError("id", $"content with id: {id} was not found");
var errorResponse = Request.CreateErrorResponse(
HttpStatusCode.NotFound,
ModelState);
@@ -66,9 +66,16 @@ namespace Umbraco.Web.Editors
/// Maps the dto property values to the persisted model
/// </summary>
/// <typeparam name="TPersisted"></typeparam>
/// <typeparam name="TSaved"></typeparam>
/// <param name="contentItem"></param>
protected virtual void MapPropertyValues<TPersisted>(ContentBaseItemSave<TPersisted> contentItem)
where TPersisted : IContentBase
/// <param name="getPropertyValue"></param>
/// <param name="savePropertyValue"></param>
protected void MapPropertyValues<TPersisted, TSaved>(
TSaved contentItem,
Func<TSaved, Property, object> getPropertyValue,
Action<TSaved, Property, object> savePropertyValue)
where TPersisted : IContentBase
where TSaved : ContentBaseItemSave<TPersisted>
{
// map the property values
foreach (var propertyDto in contentItem.ContentDto.Properties)
@@ -102,7 +109,7 @@ namespace Umbraco.Web.Editors
};
// let the editor convert the value that was received, deal with files, etc
var value = valueEditor.FromEditor(data, property.GetValue());
var value = valueEditor.FromEditor(data, getPropertyValue(contentItem, property));
// set the value - tags are special
var tagAttribute = propertyDto.PropertyEditor.GetTagAttribute();
@@ -110,10 +117,11 @@ namespace Umbraco.Web.Editors
{
var tagConfiguration = ConfigurationEditor.ConfigurationAs<TagConfiguration>(propertyDto.DataType.Configuration);
if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = tagAttribute.Delimiter;
//fixme how is this supposed to work with variants?
property.SetTagsValue(value, tagConfiguration);
}
else
property.SetValue(value);
savePropertyValue(contentItem, property, value);
}
}
@@ -40,6 +40,7 @@ namespace Umbraco.Web.Editors
if (imageProp == null)
return Request.CreateResponse(HttpStatusCode.NotFound);
//fixme doesn't take into account variants
var imagePath = imageProp.GetValue().ToString();
return GetBigThumbnail(imagePath);
}
@@ -78,6 +79,7 @@ namespace Umbraco.Web.Editors
if (imageProp == null)
return new HttpResponseMessage(HttpStatusCode.NotFound);
//fixme doesn't take into account variants
var imagePath = imageProp.GetValue().ToString();
return GetResized(imagePath, width);
}
+4 -17
View File
@@ -7,11 +7,12 @@ using System.Net.Http;
using System.Web.Http;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Language = Umbraco.Web.Models.ContentEditing.Language;
namespace Umbraco.Web.Editors
{
@@ -42,23 +43,9 @@ namespace Umbraco.Web.Editors
[HttpGet]
public IEnumerable<Language> GetAllLanguages()
{
var allLanguages = Services.LocalizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
var langs = Mapper.Map<IEnumerable<Language>>(allLanguages).ToList();
var allLanguages = Services.LocalizationService.GetAllLanguages();
//if there's only one language, by default it is the default
if (langs.Count == 1)
{
langs[0].IsDefaultVariantLanguage = true;
langs[0].Mandatory = true;
}
else if (allLanguages.All(x => !x.IsDefaultVariantLanguage))
{
//if no language has the default flag, then the defaul language is the one with the lowest id
langs[0].IsDefaultVariantLanguage = true;
langs[0].Mandatory = true;
}
return langs.OrderBy(x => x.Name);
return Mapper.Map<IEnumerable<ILanguage>, IEnumerable<Language>>(allLanguages);
}
[HttpGet]
+6 -14
View File
@@ -470,7 +470,11 @@ namespace Umbraco.Web.Editors
// * we have a reference to the DTO object and the persisted object
// * Permissions are valid
MapPropertyValues(contentItem);
UpdateName(contentItem);
MapPropertyValues<IMedia, MediaItemSave>(
contentItem,
(save, property) => property.GetValue(), //get prop val
(save, property, v) => property.SetValue(v)); //set prop val
//We need to manually check the validation results here because:
// * We still need to save the entity even if there are validation value errors
@@ -529,19 +533,7 @@ namespace Umbraco.Web.Editors
return display;
}
/// <summary>
/// Maps the property values to the persisted entity
/// </summary>
/// <param name="contentItem"></param>
protected override void MapPropertyValues<TPersisted>(ContentBaseItemSave<TPersisted> contentItem)
{
UpdateName(contentItem);
//use the base method to map the rest of the properties
base.MapPropertyValues(contentItem);
}
/// <summary>
/// Empties the recycle bin
/// </summary>
+4 -1
View File
@@ -379,7 +379,10 @@ namespace Umbraco.Web.Editors
contentItem.PersistedContent.Username = contentItem.Username;
//use the base method to map the rest of the properties
base.MapPropertyValues(contentItem);
base.MapPropertyValues<IMember, MemberSave>(
contentItem,
(save, property) => property.GetValue(), //get prop val
(save, property, v) => property.SetValue(v)); //set prop val
}
/// <summary>
@@ -23,6 +23,12 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "published")]
public bool Published { get; set; }
/// <summary>
/// Determines if the content item is a draft
/// </summary>
[DataMember(Name = "edited")]
public bool Edited { get; set; }
[DataMember(Name = "owner")]
public UserProfile Owner { get; set; }
@@ -75,8 +81,8 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "properties")]
public virtual IEnumerable<T> Properties
{
get { return _properties; }
set { _properties = value; }
get => _properties;
set => _properties = value;
}
/// <summary>
@@ -23,8 +23,14 @@ namespace Umbraco.Web.Models.ContentEditing
public DateTime? ReleaseDate { get; set; }
[DataMember(Name = "removeDate")]
public DateTime? ExpireDate { get; set; }
public DateTime? ExpireDate { get; set; }
/// <summary>
/// Represents the variant info for a content item
/// </summary>
[DataMember(Name = "variants")]
public IEnumerable<ContentVariation> Variants { get; set; }
[DataMember(Name = "template")]
public string TemplateAlias { get; set; }
@@ -10,7 +10,13 @@ namespace Umbraco.Web.Models.ContentEditing
/// </summary>
[DataContract(Name = "content", Namespace = "")]
public class ContentItemSave : ContentBaseItemSave<IContent>
{
{
/// <summary>
/// The language Id for the content variation being saved
/// </summary>
[DataMember(Name = "languageId")]
public int? LanguageId { get; set; }
/// <summary>
/// The template alias to save
/// </summary>
@@ -0,0 +1,40 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models;
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// Represents the variant info for a content item
/// </summary>
[DataContract(Name = "contentVariant", Namespace = "")]
public class ContentVariation
{
/// <summary>
/// The content name of the variant
/// </summary>
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "language")]
public Language Language { get; set; }
[DataMember(Name = "segment")]
public string Segment { get; set; }
[DataMember(Name = "state")]
public string PublishedState { get; set; }
/// <summary>
/// Determines if the content variant for this culture has been created
/// </summary>
[DataMember(Name = "exists")]
public bool Exists { get; set; }
/// <summary>
/// Determines if this is the variant currently being edited
/// </summary>
[DataMember(Name = "current")]
public bool IsCurrent { get; set; }
}
}
@@ -11,6 +11,30 @@ namespace Umbraco.Web.Models
{
public static class ContentExtensions
{
/// <summary>
/// Returns true if the content has any property type that allows language variants
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
public static bool HasLanguageVariantPropertyType(this IContent content)
{
return content.PropertyTypes.Any(x => x.Variations == ContentVariation.CultureNeutral);
}
/// <summary>
/// Returns true if the content has a variation for the language/segment combination
/// </summary>
/// <param name="content"></param>
/// <param name="langId"></param>
/// <param name="segment"></param>
/// <returns></returns>
public static bool HasVariation(this IContent content, int langId, string segment = null)
{
//TODO: Wire up with new APIs
return false;
//return content.Languages.FirstOrDefault(x => x == langId);
}
/// <summary>
/// Gets the culture that would be selected to render a specified content,
/// within the context of a specified current request.
@@ -1,33 +0,0 @@
using System;
using AutoMapper;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Extends AutoMapper's <see cref="Mapper"/> class to handle Umbraco's context.
/// </summary>
internal static class ContextMapper
{
private const string UmbracoContextKey = "ContextMapper.UmbracoContext";
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext)
=> Mapper.Map<TSource, TDestination>(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext);
public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true)
{
if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext)
return umbracoContext;
// fixme - not a good idea at all
// because this falls back to magic singletons
// so really we should remove this line, but then some tests+app breaks ;(
return Umbraco.Web.Composing.Current.UmbracoContext;
// better fail fast
if (throwIfMissing)
throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext.");
return null;
}
}
}
@@ -13,7 +13,7 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentMapperProfile : Profile
{
public ContentMapperProfile(IUserService userService, ILocalizedTextService textService, IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService)
public ContentMapperProfile(IUserService userService, ILocalizedTextService textService, IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService, ILocalizationService localizationService)
{
// create, capture, cache
var contentOwnerResolver = new OwnerResolver<IContent>(userService);
@@ -25,12 +25,14 @@ namespace Umbraco.Web.Models.Mapping
var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver<IContent, ContentTreeController>();
var defaultTemplateResolver = new DefaultTemplateResolver();
var contentUrlResolver = new ContentUrlResolver();
var variantResolver = new VariationResolver(localizationService);
//FROM IContent TO ContentItemDisplay
CreateMap<IContent, ContentItemDisplay>()
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key)))
.ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src)))
.ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src)))
.ForMember(dest => dest.Variants, opt => opt.ResolveUsing(variantResolver))
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon))
.ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias))
.ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(src => src.ContentType.Name))
@@ -3,9 +3,11 @@ using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.ContentEditing;
using ContentVariation = Umbraco.Core.Models.ContentVariation;
namespace Umbraco.Web.Models.Mapping
{
@@ -15,10 +17,14 @@ namespace Umbraco.Web.Models.Mapping
internal class ContentPropertyBasicConverter<TDestination> : ITypeConverter<Property, TDestination>
where TDestination : ContentPropertyBasic, new()
{
private readonly ILogger _logger;
private readonly PropertyEditorCollection _propertyEditors;
protected IDataTypeService DataTypeService { get; }
public ContentPropertyBasicConverter(IDataTypeService dataTypeService)
public ContentPropertyBasicConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors)
{
_logger = logger;
_propertyEditors = propertyEditors;
DataTypeService = dataTypeService;
}
@@ -28,19 +34,28 @@ namespace Umbraco.Web.Models.Mapping
/// <returns></returns>
public virtual TDestination Convert(Property property, TDestination dest, ResolutionContext context)
{
var editor = Current.PropertyEditors[property.PropertyType.PropertyEditorAlias];
var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias];
if (editor == null)
{
Current.Logger.Error<ContentPropertyBasicConverter<TDestination>>(
_logger.Error<ContentPropertyBasicConverter<TDestination>>(
"No property editor found, converting to a Label",
new NullReferenceException("The property editor with alias " + property.PropertyType.PropertyEditorAlias + " does not exist"));
editor = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit];
editor = _propertyEditors[Constants.PropertyEditors.Aliases.NoEdit];
}
var languageId = context.GetLanguageId();
if (!languageId.HasValue && property.PropertyType.Variations == ContentVariation.CultureNeutral)
{
//a language Id needs to be set for a property type that can be varried by language
throw new InvalidOperationException($"No languageId found in mapping operation when one is required for the culture neutral property type {property.PropertyType.Alias}");
}
var result = new TDestination
{
Id = property.Id,
Value = editor.GetValueEditor().ToEditor(property, DataTypeService),
Value = editor.GetValueEditor().ToEditor(property, DataTypeService, languageId),
Alias = property.Alias,
PropertyEditor = editor,
Editor = editor.Alias
@@ -3,6 +3,7 @@ using System.Linq;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
@@ -17,8 +18,8 @@ namespace Umbraco.Web.Models.Mapping
{
private readonly ILocalizedTextService _textService;
public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService)
: base(dataTypeService)
public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors)
: base(dataTypeService, logger, propertyEditors)
{
_textService = textService;
}
@@ -1,6 +1,8 @@
using System;
using AutoMapper;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
@@ -11,8 +13,8 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter<ContentPropertyDto>
{
public ContentPropertyDtoConverter(IDataTypeService dataTypeService)
: base(dataTypeService)
public ContentPropertyDtoConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors)
: base(dataTypeService, logger, propertyEditors)
{ }
public override ContentPropertyDto Convert(Property originalProperty, ContentPropertyDto dest, ResolutionContext context)
@@ -1,5 +1,7 @@
using AutoMapper;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
@@ -11,11 +13,11 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentPropertyMapperProfile : Profile
{
public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService)
public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors)
{
var contentPropertyBasicConverter = new ContentPropertyBasicConverter<ContentPropertyBasic>(dataTypeService);
var contentPropertyDtoConverter = new ContentPropertyDtoConverter(dataTypeService);
var contentPropertyDisplayConverter = new ContentPropertyDisplayConverter(dataTypeService, textService);
var contentPropertyBasicConverter = new ContentPropertyBasicConverter<ContentPropertyBasic>(dataTypeService, logger, propertyEditors);
var contentPropertyDtoConverter = new ContentPropertyDtoConverter(dataTypeService, logger, propertyEditors);
var contentPropertyDisplayConverter = new ContentPropertyDisplayConverter(dataTypeService, textService, logger, propertyEditors);
//FROM Property TO ContentPropertyBasic
CreateMap<PropertyGroup, Tab<ContentPropertyDisplay>>()
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using AutoMapper;
using ClientDependency.Core;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Extends AutoMapper's <see cref="Mapper"/> class to handle Umbraco's context and other contextual data
/// </summary>
internal static class ContextMapper
{
public const string UmbracoContextKey = "ContextMapper.UmbracoContext";
public const string LanguageKey = "ContextMapper.LanguageId";
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext)
=> Mapper.Map<TSource, TDestination>(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext);
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext, IDictionary<string, object> contextVals)
=> Mapper.Map<TSource, TDestination>(obj, opt =>
{
//set the umb ctx
opt.Items[UmbracoContextKey] = umbracoContext;
//set other supplied context vals
if (contextVals != null)
{
foreach (var contextVal in contextVals)
{
opt.Items[contextVal.Key] = contextVal.Value;
}
}
});
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext, object contextVals)
=> Mapper.Map<TSource, TDestination>(obj, opt =>
{
//set the umb ctx
opt.Items[UmbracoContextKey] = umbracoContext;
//set other supplied context vals
if (contextVals != null)
{
foreach (var contextVal in contextVals.ToDictionary())
{
opt.Items[contextVal.Key] = contextVal.Value;
}
}
});
public static TDestination Map<TSource, TDestination>(TSource obj, IDictionary<string, object> contextVals)
=> Mapper.Map<TSource, TDestination>(obj, opt =>
{
//set other supplied context vals
if (contextVals != null)
{
foreach (var contextVal in contextVals)
{
opt.Items[contextVal.Key] = contextVal.Value;
}
}
});
public static TDestination Map<TSource, TDestination>(TSource obj, object contextVals)
=> Mapper.Map<TSource, TDestination>(obj, opt =>
{
//set other supplied context vals
if (contextVals != null)
{
foreach (var contextVal in contextVals.ToDictionary())
{
opt.Items[contextVal.Key] = contextVal.Value;
}
}
});
/// <summary>
/// Returns the language Id in the mapping context if one is found
/// </summary>
/// <param name="resolutionContext"></param>
/// <returns></returns>
public static int? GetLanguageId(this ResolutionContext resolutionContext)
{
if (!resolutionContext.Options.Items.TryGetValue(LanguageKey, out var obj)) return null;
if (obj is int i)
return i;
return null;
}
/// <summary>
/// Returns the <see cref="UmbracoContext"/> in the mapping context if one is found
/// </summary>
/// <param name="resolutionContext"></param>
/// <param name="throwIfMissing"></param>
/// <returns></returns>
public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true)
{
if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext)
return umbracoContext;
// better fail fast
if (throwIfMissing)
throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext.");
// fixme - not a good idea at all
// because this falls back to magic singletons
// so really we should remove this line, but then some tests+app breaks ;(
return Umbraco.Web.Composing.Current.UmbracoContext;
}
}
}
@@ -1,4 +1,6 @@
using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
@@ -12,6 +14,38 @@ namespace Umbraco.Web.Models.Mapping
{
CreateMap<ILanguage, Language>()
.ForMember(l => l.Name, expression => expression.MapFrom(x => x.CultureInfo.DisplayName));
CreateMap<IEnumerable<ILanguage>, IEnumerable<Language>>()
.ConvertUsing<LanguageCollectionTypeConverter>();
}
/// <summary>
/// Converts a list of <see cref="ILanguage"/> to a list of <see cref="Language"/> and ensures the correct order and defaults are set
/// </summary>
// ReSharper disable once ClassNeverInstantiated.Local
private class LanguageCollectionTypeConverter : ITypeConverter<IEnumerable<ILanguage>, IEnumerable<Language>>
{
public IEnumerable<Language> Convert(IEnumerable<ILanguage> source, IEnumerable<Language> destination, ResolutionContext context)
{
var allLanguages = source.OrderBy(x => x.Id).ToList();
var langs = new List<Language>(allLanguages.Select(x => context.Mapper.Map<ILanguage, Language>(x, null, context)));
//if there's only one language, by default it is the default
if (langs.Count == 1)
{
langs[0].IsDefaultVariantLanguage = true;
langs[0].Mandatory = true;
}
else if (allLanguages.All(x => !x.IsDefaultVariantLanguage))
{
//if no language has the default flag, then the defaul language is the one with the lowest id
langs[0].IsDefaultVariantLanguage = true;
langs[0].Mandatory = true;
}
return langs.OrderBy(x => x.Name);
}
}
}
}
@@ -169,10 +169,11 @@ namespace Umbraco.Web.Models.Mapping
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="properties"></param>
/// <param name="context"></param>
/// <returns></returns>
protected override List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties)
protected override List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties, ResolutionContext context)
{
var result = base.MapProperties(umbracoContext, content, properties);
var result = base.MapProperties(umbracoContext, content, properties, context);
var member = (IMember)content;
var memberType = member.ContentType;
@@ -26,7 +26,7 @@ namespace Umbraco.Web.Models.Mapping
{
IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties));
}
/// <summary>
/// Adds the container (listview) tab to the document
/// </summary>
@@ -106,20 +106,29 @@ namespace Umbraco.Web.Models.Mapping
SetChildItemsTabPosition(display, listViewConfig, listViewTab);
}
private static int GetTabNumberFromConfig(IDictionary<string, object> listViewConfig)
{
if (!listViewConfig.TryGetValue("displayAtTabNumber", out var displayTabNum))
return -1;
switch (displayTabNum)
{
case int i:
return i;
case string s when int.TryParse(s, out var parsed):
return parsed;
}
return -1;
}
private static void SetChildItemsTabPosition<TPersisted>(TabbedContentItem<ContentPropertyDisplay, TPersisted> display,
IDictionary<string, object> listViewConfig,
Tab<ContentPropertyDisplay> listViewTab)
where TPersisted : IContentBase
{
// Find position of tab from config
var tabIndexForChildItems = 0;
if (listViewConfig["displayAtTabNumber"] != null)
var tabIndexForChildItems = GetTabNumberFromConfig(listViewConfig);
if (tabIndexForChildItems != -1)
{
var o = listViewConfig["displayAtTabNumber"];
if (o is string s) tabIndexForChildItems = int.Parse(s);
else if (o is int i) tabIndexForChildItems = i;
else throw new Exception("panic");
// Tab position is recorded 1-based but we insert into collection 0-based
tabIndexForChildItems--;
@@ -134,6 +143,7 @@ namespace Umbraco.Web.Models.Mapping
tabIndexForChildItems = display.Tabs.Count();
}
}
else tabIndexForChildItems = 0;
// Recreate tab list with child items tab at configured position
var tabs = new List<Tab<ContentPropertyDisplay>>();
@@ -157,18 +167,19 @@ namespace Umbraco.Web.Models.Mapping
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="tabs"></param>
/// <param name="context"></param>
/// <remarks>
/// The generic properties tab is responsible for
/// setting up the properties such as Created date, updated date, template selected, etc...
/// </remarks>
protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List<Tab<ContentPropertyDisplay>> tabs)
protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List<Tab<ContentPropertyDisplay>> tabs, ResolutionContext context)
{
// 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);
var genericproperties = MapProperties(umbracoContext, content, noGroupProperties, context);
tabs.Add(new Tab<ContentPropertyDisplay>
{
@@ -217,17 +228,21 @@ namespace Umbraco.Web.Models.Mapping
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="properties"></param>
/// <param name="context"></param>
/// <returns></returns>
protected virtual List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties)
protected virtual List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties, ResolutionContext context)
{
var result = Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyDisplay>>(
//we need to map this way to pass the context through, I don't like it but we'll see what AutoMapper says: https://github.com/AutoMapper/AutoMapper/issues/2588
var result = context.Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyDisplay>>(
// Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties.
properties.OrderBy(prop => prop.PropertyType.SortOrder))
properties.OrderBy(prop => prop.PropertyType.SortOrder),
null,
context)
.ToList();
return result;
}
}
}
/// <summary>
/// Creates the tabs collection with properties assigned for display models
@@ -235,14 +250,14 @@ namespace Umbraco.Web.Models.Mapping
internal class TabsAndPropertiesResolver<TSource, TDestination> : TabsAndPropertiesResolver, IValueResolver<TSource, TDestination, IEnumerable<Tab<ContentPropertyDisplay>>>
where TSource : IContentBase
{
public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService)
public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService)
: base(localizedTextService)
{ }
public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable<string> ignoreProperties)
: base(localizedTextService, ignoreProperties)
{ }
public virtual IEnumerable<Tab<ContentPropertyDisplay>> Resolve(TSource source, TDestination destination, IEnumerable<Tab<ContentPropertyDisplay>> destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext();
@@ -270,7 +285,7 @@ namespace Umbraco.Web.Models.Mapping
continue;
//map the properties
var mappedProperties = MapProperties(umbracoContext, source, properties);
var mappedProperties = MapProperties(umbracoContext, source, properties, context);
// add the tab
// we need to pick an identifier... there is no "right" way...
@@ -288,7 +303,7 @@ namespace Umbraco.Web.Models.Mapping
});
}
MapGenericProperties(umbracoContext, source, tabs);
MapGenericProperties(umbracoContext, source, tabs, context);
// activate the first tab, if any
if (tabs.Count > 0)
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using ContentVariation = Umbraco.Web.Models.ContentEditing.ContentVariation;
using Language = Umbraco.Web.Models.ContentEditing.Language;
namespace Umbraco.Web.Models.Mapping
{
internal class VariationResolver : IValueResolver<IContent, ContentItemDisplay, IEnumerable<ContentVariation>>
{
private readonly ILocalizationService _localizationService;
public VariationResolver(ILocalizationService localizationService)
{
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
}
public IEnumerable<ContentVariation> Resolve(IContent source, ContentItemDisplay destination, IEnumerable<ContentVariation> destMember, ResolutionContext context)
{
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
if (allLanguages.Count == 0) return Enumerable.Empty<ContentVariation>();
var langs = context.Mapper.Map<IEnumerable<ILanguage>, IEnumerable<Language>>(allLanguages, null, context);
var variants = langs.Select(x => new ContentVariation
{
Language = x,
//fixme these all need to the variant values but we need to wait for the db/service changes
Name = source.Name ,
Exists = source.HasVariation(x.Id), //TODO: This needs to be wired up with new APIs when they are ready
PublishedState = source.PublishedState.ToString()
}).ToList();
var langId = context.GetLanguageId();
//set the current variant being edited to the one found in the context or the default, whichever matches
variants.First(x => (langId.HasValue && langId.Value == x.Language.Id) || x.Language.IsDefaultVariantLanguage).IsCurrent = true;
return variants;
}
}
}
@@ -50,7 +50,7 @@ namespace Umbraco.Web.PropertyEditors
return jobject.Property("color").Value.Value<string>();
}
public override ColorPickerConfiguration FromConfigurationEditor(Dictionary<string, object> editorValues, ColorPickerConfiguration configuration)
public override ColorPickerConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, ColorPickerConfiguration configuration)
{
var output = new ColorPickerConfiguration();
@@ -12,7 +12,7 @@ namespace Umbraco.Web.PropertyEditors
.Config = new Dictionary<string, object> { { "idType", "udi" } };
}
public override Dictionary<string, object> ToValueEditor(object configuration)
public override IDictionary<string, object> ToValueEditor(object configuration)
{
// these are not configuration fields, but constants required by the value editor
var d = base.ToValueEditor(configuration);
@@ -22,4 +22,4 @@ namespace Umbraco.Web.PropertyEditors
return d;
}
}
}
}
@@ -8,7 +8,7 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
public class DateConfigurationEditor : ConfigurationEditor<DateConfiguration>
{
public override Dictionary<string, object> ToValueEditor(object configuration)
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);
d["pickTime"] = false;
@@ -8,7 +8,7 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
public class DateTimeConfigurationEditor : ConfigurationEditor<DateTimeConfiguration>
{
public override Dictionary<string, object> ToValueEditor(object configuration)
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);
d["pickTime"] = true;
@@ -18,9 +18,9 @@ namespace Umbraco.Web.PropertyEditors
Validators.Add(new DateTimeValidator());
}
public override object ToEditor(Property property, IDataTypeService dataTypeService)
public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null)
{
var date = property.GetValue().TryConvertTo<DateTime?>();
var date = property.GetValue(languageId, segment).TryConvertTo<DateTime?>();
if (date.Success == false || date.Result == null)
{
return String.Empty;
@@ -29,4 +29,4 @@ namespace Umbraco.Web.PropertyEditors
return date.Result.Value.ToString("yyyy-MM-dd");
}
}
}
}
@@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors
items.Validators.Add(new ValueListUniqueValueValidator());
}
public override DropDownFlexibleConfiguration FromConfigurationEditor(Dictionary<string, object> editorValues, DropDownFlexibleConfiguration configuration)
public override DropDownFlexibleConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, DropDownFlexibleConfiguration configuration)
{
var output = new DropDownFlexibleConfiguration();
@@ -36,30 +36,12 @@ namespace Umbraco.Web.PropertyEditors
/// Gets a value indicating whether a property is an upload field.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="ensureValue">A value indicating whether to check that the property has a non-empty value.</param>
/// <returns>A value indicating whether a property is an upload field, and (optionaly) has a non-empty value.</returns>
private static bool IsUploadField(Property property, bool ensureValue)
private static bool IsUploadField(Property property)
{
if (property.PropertyType.PropertyEditorAlias != Constants.PropertyEditors.Aliases.UploadField)
return false;
if (ensureValue == false)
return true;
var stringValue = property.GetValue() as string;
return string.IsNullOrWhiteSpace(stringValue) == false;
return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField;
}
/// <summary>
/// Ensures any files associated are removed
/// </summary>
/// <param name="allPropertyData"></param>
internal IEnumerable<string> ServiceEmptiedRecycleBin(Dictionary<int, IEnumerable<Property>> allPropertyData)
{
return allPropertyData.SelectMany(x => x.Value)
.Where (x => IsUploadField(x, true))
.Select(x => _mediaFileSystem.GetRelativePath((string)x.GetValue()))
.ToList();
}
/// <summary>
/// Ensures any files associated are removed
/// </summary>
@@ -67,10 +49,32 @@ namespace Umbraco.Web.PropertyEditors
internal IEnumerable<string> ServiceDeleted(IEnumerable<ContentBase> deletedEntities)
{
return deletedEntities.SelectMany(x => x.Properties)
.Where(x => IsUploadField(x, true))
.Select(x => _mediaFileSystem.GetRelativePath((string) x.GetValue()))
.ToList();
.Where(IsUploadField)
.SelectMany(GetFilePathsFromPropertyValues)
.Distinct();
}
/// <summary>
/// Look through all propery values stored against the property and resolve any file paths stored
/// </summary>
/// <param name="prop"></param>
/// <returns></returns>
private IEnumerable<string> GetFilePathsFromPropertyValues(Property prop)
{
var propVals = prop.Values;
foreach (var propertyValue in propVals)
{
//check if the published value contains data and return it
var propVal = propertyValue.PublishedValue;
if (propVal != null && propVal is string str1 && !str1.IsNullOrWhiteSpace())
yield return _mediaFileSystem.GetRelativePath(str1);
//check if the edited value contains data and return it
propVal = propertyValue.EditedValue;
if (propVal != null && propVal is string str2 && !str2.IsNullOrWhiteSpace())
yield return _mediaFileSystem.GetRelativePath(str2);
}
}
/// <summary>
/// After a content has been copied, also copy uploaded files.
@@ -80,16 +84,22 @@ namespace Umbraco.Web.PropertyEditors
internal void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs<IContent> args)
{
// get the upload field properties with a value
var properties = args.Original.Properties.Where(x => IsUploadField(x, true));
var properties = args.Original.Properties.Where(IsUploadField);
// copy files
var isUpdated = false;
foreach (var property in properties)
{
var sourcePath = _mediaFileSystem.GetRelativePath((string) property.GetValue());
var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath);
args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath));
isUpdated = true;
//copy each of the property values (variants, segments) to the destination
foreach (var propertyValue in property.Values)
{
var propVal = property.GetValue(propertyValue.LanguageId, propertyValue.Segment);
if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) continue;
var sourcePath = _mediaFileSystem.GetRelativePath(str);
var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath);
args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.LanguageId, propertyValue.Segment);
isUpdated = true;
}
}
// if updated, re-save the copy with the updated value
@@ -134,7 +144,7 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
private void AutoFillProperties(IContentBase model)
{
var properties = model.Properties.Where(x => IsUploadField(x, false));
var properties = model.Properties.Where(IsUploadField);
foreach (var property in properties)
{
@@ -9,7 +9,7 @@ namespace Umbraco.Web.PropertyEditors
internal class ImageCropperConfigurationEditor : ConfigurationEditor<ImageCropperConfiguration>
{
/// <inheritdoc />
public override Dictionary<string, object> ToValueEditor(object configuration)
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);
if (!d.ContainsKey("focalPoint")) d["focalPoint"] = new { left = 0.5, top = 0.5 };
@@ -50,16 +50,11 @@ namespace Umbraco.Web.PropertyEditors
/// <summary>
/// Gets a value indicating whether a property is an image cropper field.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="ensureValue">A value indicating whether to check that the property has a non-empty value.</param>
/// <param name="property">The property.</param>
/// <returns>A value indicating whether a property is an image cropper field, and (optionaly) has a non-empty value.</returns>
private static bool IsCropperField(Property property, bool ensureValue)
private static bool IsCropperField(Property property)
{
if (property.PropertyType.PropertyEditorAlias != Constants.PropertyEditors.Aliases.ImageCropper)
return false;
if (ensureValue == false)
return true;
return property.GetValue() is string && string.IsNullOrWhiteSpace((string) property.GetValue()) == false;
return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper;
}
/// <summary>
@@ -86,22 +81,6 @@ namespace Umbraco.Web.PropertyEditors
}
}
/// <summary>
/// Ensures any files associated are removed
/// </summary>
/// <param name="allPropertyData"></param>
internal IEnumerable<string> ServiceEmptiedRecycleBin(Dictionary<int, IEnumerable<Property>> allPropertyData)
{
return allPropertyData.SelectMany(x => x.Value)
.Where(x => IsCropperField(x, true)).Select(x =>
{
var jo = GetJObject((string) x.GetValue(), true);
if (jo?["src"] == null) return null;
var src = jo["src"].Value<string>();
return string.IsNullOrWhiteSpace(src) ? null : _mediaFileSystem.GetRelativePath(src);
}).WhereNotNull();
}
/// <summary>
/// Ensures any files associated are removed
/// </summary>
@@ -109,13 +88,47 @@ namespace Umbraco.Web.PropertyEditors
internal IEnumerable<string> ServiceDeleted(IEnumerable<ContentBase> deletedEntities)
{
return deletedEntities.SelectMany(x => x.Properties)
.Where(x => IsCropperField(x, true)).Select(x =>
{
var jo = GetJObject((string) x.GetValue(), true);
if (jo?["src"] == null) return null;
var src = jo["src"].Value<string>();
return string.IsNullOrWhiteSpace(src) ? null : _mediaFileSystem.GetRelativePath(src);
}).WhereNotNull();
.Where(IsCropperField)
.SelectMany(GetFilePathsFromPropertyValues)
.Distinct();
}
/// <summary>
/// Look through all propery values stored against the property and resolve any file paths stored
/// </summary>
/// <param name="prop"></param>
/// <returns></returns>
private IEnumerable<string> GetFilePathsFromPropertyValues(Property prop)
{
//parses out the src from a json string
foreach (var propertyValue in prop.Values)
{
//check if the published value contains data and return it
var src = GetFileSrcFromPropertyValue(propertyValue.PublishedValue, out var _);
if (src != null) yield return _mediaFileSystem.GetRelativePath(src);
//check if the edited value contains data and return it
src = GetFileSrcFromPropertyValue(propertyValue.EditedValue, out var _);
if (src != null) yield return _mediaFileSystem.GetRelativePath(src);
}
}
/// <summary>
/// Returns the "src" property from the json structure if the value is formatted correctly
/// </summary>
/// <param name="propVal"></param>
/// <param name="deserializedValue">The deserialized <see cref="JObject"/> value</param>
/// <returns></returns>
private string GetFileSrcFromPropertyValue(object propVal, out JObject deserializedValue)
{
deserializedValue = null;
if (propVal == null || !(propVal is string str)) return null;
if (!str.DetectIsJson()) return null;
deserializedValue = GetJObject(str, true);
if (deserializedValue?["src"] == null) return null;
var src = deserializedValue["src"].Value<string>();
return _mediaFileSystem.GetRelativePath(src);
}
/// <summary>
@@ -125,23 +138,25 @@ namespace Umbraco.Web.PropertyEditors
/// <param name="args">The event arguments.</param>
public void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs<IContent> args)
{
// get the image cropper field properties with a value
var properties = args.Original.Properties.Where(x => IsCropperField(x, true));
// get the image cropper field properties
var properties = args.Original.Properties.Where(IsCropperField);
// copy files
var isUpdated = false;
foreach (var property in properties)
{
var jo = GetJObject((string) property.GetValue(), true);
var src = jo?["src"]?.Value<string>();
if (string.IsNullOrWhiteSpace(src)) continue;
var sourcePath = _mediaFileSystem.GetRelativePath(src);
var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath);
jo["src"] = _mediaFileSystem.GetUrl(copyPath);
args.Copy.SetValue(property.Alias, jo.ToString());
isUpdated = true;
//copy each of the property values (variants, segments) to the destination by using the edited value
foreach (var propertyValue in property.Values)
{
var propVal = property.GetValue(propertyValue.LanguageId, propertyValue.Segment);
var src = GetFileSrcFromPropertyValue(propVal, out var jo);
if (src == null) continue;
var sourcePath = _mediaFileSystem.GetRelativePath(src);
var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath);
jo["src"] = _mediaFileSystem.GetUrl(copyPath);
args.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.LanguageId, propertyValue.Segment);
isUpdated = true;
}
}
// if updated, re-save the copy with the updated value
if (isUpdated)
@@ -185,7 +200,7 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
private void AutoFillProperties(IContentBase model)
{
var properties = model.Properties.Where(x => IsCropperField(x, false));
var properties = model.Properties.Where(IsCropperField);
foreach (var property in properties)
{
@@ -32,17 +32,19 @@ namespace Umbraco.Web.PropertyEditors
/// This is called to merge in the prevalue crops with the value that is saved - similar to the property value converter for the front-end
/// </summary>
public override object ToEditor(Property property, IDataTypeService dataTypeService)
public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null)
{
var val = property.GetValue(languageId, segment);
if (val == null) return null;
ImageCropperValue value;
try
{
// fixme - ignoring variants here?!
value = JsonConvert.DeserializeObject<ImageCropperValue>(property.GetValue().ToString());
value = JsonConvert.DeserializeObject<ImageCropperValue>(val.ToString());
}
catch
{
value = new ImageCropperValue { Src = property.GetValue().ToString() };
value = new ImageCropperValue { Src = val.ToString() };
}
var dataType = dataTypeService.GetDataType(property.PropertyType.DataTypeId);
@@ -10,7 +10,7 @@ namespace Umbraco.Web.PropertyEditors
public class LabelConfigurationEditor : ConfigurationEditor<LabelConfiguration>
{
/// <inheritdoc />
public override LabelConfiguration FromConfigurationEditor(Dictionary<string, object> editorValues, LabelConfiguration configuration)
public override LabelConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, LabelConfiguration configuration)
{
var newConfiguration = new LabelConfiguration();
@@ -20,7 +20,7 @@ namespace Umbraco.Web.PropertyEditors
};
}
public override Dictionary<string, object> ToValueEditor(object configuration)
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);
d["idType"] = "udi";
@@ -29,7 +29,7 @@ namespace Umbraco.Web.PropertyEditors
}
/// <inheritdoc />
public override Dictionary<string, object> ToValueEditor(object configuration)
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);
d["multiPicker"] = true;
@@ -32,7 +32,7 @@ namespace Umbraco.Web.PropertyEditors
}
/// <inheritdoc />
public override MultipleTestStringConfiguration FromConfigurationEditor(Dictionary<string, object> editorValues, MultipleTestStringConfiguration configuration)
public override MultipleTestStringConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, MultipleTestStringConfiguration configuration)
{
// fixme this isn't pretty
//the values from the editor will be min/max fieds and we need to format to json in one field
@@ -78,18 +78,18 @@ namespace Umbraco.Web.PropertyEditors
/// cannot have 2 way binding, so to get around that each item in the array needs to be an object with a string.
/// </summary>
/// <param name="property"></param>
/// <param name="propertyType"></param>
/// <param name="dataTypeService"></param>
/// <param name="languageId"></param>
/// <param name="segment"></param>
/// <returns></returns>
/// <remarks>
/// The legacy property editor saved this data as new line delimited! strange but we have to maintain that.
/// </remarks>
public override object ToEditor(Property property, IDataTypeService dataTypeService)
public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null)
{
return property.GetValue() == null
? new JObject[] {}
: property.GetValue().ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => JObject.FromObject(new {value = x}));
var val = property.GetValue(languageId, segment);
return val?.ToString().Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)
.Select(x => JObject.FromObject(new {value = x})) ?? new JObject[] { };
}
@@ -139,12 +139,13 @@ namespace Umbraco.Web.PropertyEditors
// note: there is NO variant support here
public override object ToEditor(Property property, IDataTypeService dataTypeService)
public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null)
{
if (property.GetValue() == null || string.IsNullOrWhiteSpace(property.GetValue().ToString()))
var val = property.GetValue(languageId, segment);
if (val == null || string.IsNullOrWhiteSpace(val.ToString()))
return string.Empty;
var value = JsonConvert.DeserializeObject<List<object>>(property.GetValue().ToString());
var value = JsonConvert.DeserializeObject<List<object>>(val.ToString());
if (value == null)
return string.Empty;
@@ -33,12 +33,8 @@ namespace Umbraco.Web.PropertyEditors
MediaService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
MediaService.EmptiedRecycleBin += (sender, args)
=> args.Files.AddRange(fileUpload.ServiceEmptiedRecycleBin(args.AllPropertyData));
ContentService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
ContentService.EmptiedRecycleBin += (sender, args)
=> args.Files.AddRange(fileUpload.ServiceEmptiedRecycleBin(args.AllPropertyData));
MemberService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
}
@@ -51,12 +47,8 @@ namespace Umbraco.Web.PropertyEditors
MediaService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
MediaService.EmptiedRecycleBin += (sender, args)
=> args.Files.AddRange(imageCropper.ServiceEmptiedRecycleBin(args.AllPropertyData));
ContentService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
ContentService.EmptiedRecycleBin += (sender, args)
=> args.Files.AddRange(imageCropper.ServiceEmptiedRecycleBin(args.AllPropertyData));
MemberService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
}
@@ -69,12 +69,13 @@ namespace Umbraco.Web.PropertyEditors
/// Override so that we can return a json array to the editor for multi-select values
/// </summary>
/// <param name="property"></param>
/// <param name="propertyType"></param>
/// <param name="dataTypeService"></param>
/// <param name="languageId"></param>
/// <param name="segment"></param>
/// <returns></returns>
public override object ToEditor(Property property, IDataTypeService dataTypeService)
public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null)
{
var delimited = base.ToEditor(property, dataTypeService).ToString();
var delimited = base.ToEditor(property, dataTypeService, languageId, segment).ToString();
return delimited.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
@@ -8,7 +8,7 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
public class RelatedLinksConfigurationEditor : ConfigurationEditor
{
public override Dictionary<string, object> ToValueEditor(object configuration)
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);
d["idType"] = "udi";
@@ -60,15 +60,17 @@ namespace Umbraco.Web.PropertyEditors
/// Format the data for the editor
/// </summary>
/// <param name="property"></param>
/// <param name="propertyType"></param>
/// <param name="dataTypeService"></param>
/// <param name="languageId"></param>
/// <param name="segment"></param>
/// <returns></returns>
public override object ToEditor(Property property, IDataTypeService dataTypeService)
public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null)
{
if (property.GetValue() == null)
var val = property.GetValue(languageId, segment);
if (val == null)
return null;
var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(property.GetValue().ToString(), new Dictionary<string, string>());
var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(val.ToString(), new Dictionary<string, string>());
return parsed;
}
@@ -46,7 +46,7 @@ namespace Umbraco.Web.PropertyEditors
return dictionary;
}
public override TagConfiguration FromConfigurationEditor(Dictionary<string, object> editorValues, TagConfiguration configuration)
public override TagConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, TagConfiguration configuration)
{
// the front-end editor retuns the string value of the storage type
// pure Json could do with
@@ -19,21 +19,24 @@ namespace Umbraco.Web.PropertyEditors
/// A method used to format the database value to a value that can be used by the editor
/// </summary>
/// <param name="property"></param>
/// <param name="propertyType"></param>
/// <param name="dataTypeService"></param>
/// <param name="languageId"></param>
/// <param name="segment"></param>
/// <returns></returns>
/// <remarks>
/// The object returned will always be a string and if the database type is not a valid string type an exception is thrown
/// </remarks>
public override object ToEditor(Property property, IDataTypeService dataTypeService)
public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null)
{
if (property.GetValue() == null) return string.Empty;
var val = property.GetValue(languageId, segment);
if (val == null) return string.Empty;
switch (ValueTypes.ToStorageType(ValueType))
{
case ValueStorageType.Ntext:
case ValueStorageType.Nvarchar:
return property.GetValue().ToString();
return val.ToString();
case ValueStorageType.Integer:
case ValueStorageType.Decimal:
case ValueStorageType.Date:
@@ -64,7 +64,7 @@ namespace Umbraco.Web.PropertyEditors
}
/// <inheritdoc />
public override ValueListConfiguration FromConfigurationEditor(Dictionary<string, object> editorValues, ValueListConfiguration configuration)
public override ValueListConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, ValueListConfiguration configuration)
{
var output = new ValueListConfiguration();
+3 -1
View File
@@ -229,6 +229,7 @@
<Compile Include="Media\ImageUrlProviderCollectionBuilder.cs" />
<Compile Include="Media\ThumbnailProviders\ThumbnailProviderCollection.cs" />
<Compile Include="Media\ThumbnailProviders\ThumbnailProviderCollectionBuilder.cs" />
<Compile Include="Models\ContentEditing\ContentVariation.cs" />
<Compile Include="Models\ContentEditing\EditorNavigation.cs" />
<Compile Include="Models\ContentEditing\GetAvailableCompositionsFilter.cs" />
<Compile Include="Editors\IEditorValidator.cs" />
@@ -250,7 +251,7 @@
<Compile Include="Models\ContentEditing\Language.cs" />
<Compile Include="Models\Mapping\ActionButtonsResolver.cs" />
<Compile Include="Models\Mapping\AuditMapperProfile.cs" />
<Compile Include="Models\Mapping\AutoMapperExtensions.cs" />
<Compile Include="Models\Mapping\ContextMapper.cs" />
<Compile Include="Models\Mapping\ContentChildOfListViewResolver.cs" />
<Compile Include="Models\Mapping\ContentUrlResolver.cs" />
<Compile Include="Models\Mapping\DefaultTemplateResolver.cs" />
@@ -271,6 +272,7 @@
<Compile Include="Models\Mapping\RedirectUrlMapperProfile.cs" />
<Compile Include="Models\Mapping\TemplateMapperProfile.cs" />
<Compile Include="Models\Mapping\UserGroupDefaultPermissionsResolver.cs" />
<Compile Include="Models\Mapping\VariationResolver.cs" />
<Compile Include="Models\RelatedLink.cs" />
<Compile Include="Models\RelatedLinkBase.cs" />
<Compile Include="Models\RelatedLinks.cs" />
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Models;
@@ -26,7 +27,10 @@ namespace Umbraco.Web.WebApi.Binders
protected override ContentItemDto<IContent> MapFromPersisted(ContentItemSave model)
{
return Mapper.Map<IContent, ContentItemDto<IContent>>(model.PersistedContent);
return ContextMapper.Map<IContent, ContentItemDto<IContent>>(model.PersistedContent, new Dictionary<string, object>
{
[ContextMapper.LanguageKey] = model.LanguageId
});
}
}
}