Merge pull request #2558 from umbraco/temp-U4-11127
WIP - U4-11127 Update ContentController for editing variants
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -329,7 +329,7 @@ namespace Umbraco.Core.Services
|
||||
/// <summary>
|
||||
/// Empties the recycle bin.
|
||||
/// </summary>
|
||||
void EmptyRecycleBin();
|
||||
OperationResult EmptyRecycleBin();
|
||||
|
||||
/// <summary>
|
||||
/// Sorts documents.
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("]"));
|
||||
|
||||
@@ -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"),
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+28
-63
@@ -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";
|
||||
|
||||
+2
-2
@@ -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}"> </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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user