Merge branch origin/dev-v7.9 into temp-u4-10795

This commit is contained in:
Stephan
2018-02-07 15:55:41 +01:00
93 changed files with 2218 additions and 783 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
<dependencies>
<dependency id="UmbracoCms.Core" version="[$version$]" />
<dependency id="Newtonsoft.Json" version="[10.0.2, 11.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.8, 4.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.10, 4.0.0)" />
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.1, 3.0.0)" />
<dependency id="ImageProcessor.Web.Config" version="[2.3.1, 3.0.0)" />
</dependencies>
+2 -2
View File
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.8.0")]
[assembly: AssemblyInformationalVersion("7.8.0-beta009")]
[assembly: AssemblyFileVersion("7.9.0")]
[assembly: AssemblyInformationalVersion("7.9.0")]
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Collections
/// <summary>
/// Initializes a new instance of the <see cref="CompositeTypeTypeKey"/> struct.
/// </summary>
public CompositeTypeTypeKey(Type type1, Type type2)
public CompositeTypeTypeKey(Type type1, Type type2) : this()
{
Type1 = type1;
Type2 = type2;
@@ -19,26 +19,35 @@ namespace Umbraco.Core.Collections
/// <summary>
/// Gets the first type.
/// </summary>
public Type Type1 { get; }
public Type Type1 { get; private set; }
/// <summary>
/// Gets the second type.
/// </summary>
public Type Type2 { get; }
public Type Type2 { get; private set; }
/// <inheritdoc/>
public bool Equals(CompositeTypeTypeKey other)
=> Type1 == other.Type1 && Type2 == other.Type2;
{
return Type1 == other.Type1 && Type2 == other.Type2;
}
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is CompositeTypeTypeKey other && Type1 == other.Type1 && Type2 == other.Type2;
{
var other = obj is CompositeTypeTypeKey ? (CompositeTypeTypeKey)obj : default(CompositeTypeTypeKey);
return Type1 == other.Type1 && Type2 == other.Type2;
}
public static bool operator ==(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2)
=> key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2;
{
return key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2;
}
public static bool operator !=(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2)
=> key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2;
{
return key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2;
}
/// <inheritdoc/>
public override int GetHashCode()
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("7.8.0");
private static readonly Version Version = new Version("7.9.0");
/// <summary>
/// Gets the current version of Umbraco.
@@ -40,4 +40,4 @@ namespace Umbraco.Core.Configuration
Current.Revision > 0 ? Current.Revision.ToInvariantString() : null);
}
}
}
}
+2 -1
View File
@@ -9,6 +9,7 @@ namespace Umbraco.Core
{
public const string AdminGroupAlias = "admin";
public const string SensitiveDataGroupAlias = "sensitiveData";
public const string TranslatorGroupAlias = "translator";
public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
@@ -36,4 +37,4 @@ namespace Umbraco.Core
}
}
}
}
+102
View File
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a consent.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal class Consent : Entity, IConsent
{
private static PropertySelectors _selector;
private bool _current;
private string _source;
private string _context;
private string _action;
private ConsentState _state;
private string _comment;
// ReSharper disable once ClassNeverInstantiated.Local
private class PropertySelectors
{
public readonly PropertyInfo Current = ExpressionHelper.GetPropertyInfo<Consent, bool>(x => x.Current);
public readonly PropertyInfo Source = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Source);
public readonly PropertyInfo Context = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Context);
public readonly PropertyInfo Action = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Action);
public readonly PropertyInfo State = ExpressionHelper.GetPropertyInfo<Consent, ConsentState>(x => x.State);
public readonly PropertyInfo Comment = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Comment);
}
private static PropertySelectors Selectors => _selector ?? (_selector = new PropertySelectors());
/// <inheritdoc />
public bool Current
{
get => _current;
set => SetPropertyValueAndDetectChanges(value, ref _current, Selectors.Current);
}
/// <inheritdoc />
public string Source
{
get => _source;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
SetPropertyValueAndDetectChanges(value, ref _source, Selectors.Source);
}
}
/// <inheritdoc />
public string Context
{
get => _context;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
SetPropertyValueAndDetectChanges(value, ref _context, Selectors.Context);
}
}
/// <inheritdoc />
public string Action
{
get => _action;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
SetPropertyValueAndDetectChanges(value, ref _action, Selectors.Action);
}
}
/// <inheritdoc />
public ConsentState State
{
get => _state;
// note: we probably should validate the state here, but since the
// enum is [Flags] with many combinations, this could be expensive
set => SetPropertyValueAndDetectChanges(value, ref _state, Selectors.State);
}
/// <inheritdoc />
public string Comment
{
get => _comment;
set => SetPropertyValueAndDetectChanges(value, ref _comment, Selectors.Comment);
}
/// <inheritdoc />
public IEnumerable<IConsent> History => HistoryInternal;
/// <summary>
/// Gets the previous states of this consent.
/// </summary>
public List<IConsent> HistoryInternal { get; set; }
}
}
@@ -0,0 +1,18 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Provides extension methods for the <see cref="IConsent"/> interface.
/// </summary>
public static class ConsentExtensions
{
/// <summary>
/// Determines whether the consent is granted.
/// </summary>
public static bool IsGranted(this IConsent consent) => (consent.State & ConsentState.Granted) > 0;
/// <summary>
/// Determines whether the consent is revoked.
/// </summary>
public static bool IsRevoked(this IConsent consent) => (consent.State & ConsentState.Revoked) > 0;
}
}
+38
View File
@@ -0,0 +1,38 @@
using System;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents the state of a consent.
/// </summary>
[Flags]
public enum ConsentState // : int
{
// note - this is a [Flags] enumeration
// on can create detailed flags such as:
//GrantedOptIn = Granted | 0x0001
//GrandedByForce = Granted | 0x0002
//
// 16 situations for each Pending/Granted/Revoked should be ok
/// <summary>
/// There is no consent.
/// </summary>
None = 0,
/// <summary>
/// Consent is pending and has not been granted yet.
/// </summary>
Pending = 0x10000,
/// <summary>
/// Consent has been granted.
/// </summary>
Granted = 0x20000,
/// <summary>
/// Consent has been revoked.
/// </summary>
Revoked = 0x40000
}
}
+55
View File
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a consent state.
/// </summary>
/// <remarks>
/// <para>A consent is fully identified by a source (whoever is consenting), a context (for
/// example, an application), and an action (whatever is consented).</para>
/// <para>A consent state registers the state of the consent (granted, revoked...).</para>
/// </remarks>
public interface IConsent : IAggregateRoot, IRememberBeingDirty
{
/// <summary>
/// Determines whether the consent entity represents the current state.
/// </summary>
bool Current { get; }
/// <summary>
/// Gets the unique identifier of whoever is consenting.
/// </summary>
string Source { get; }
/// <summary>
/// Gets the unique identifier of the context of the consent.
/// </summary>
/// <remarks>
/// <para>Represents the domain, application, scope... of the action.</para>
/// <para>When the action is a Udi, this should be the Udi type.</para>
/// </remarks>
string Context { get; }
/// <summary>
/// Gets the unique identifier of the consented action.
/// </summary>
string Action { get; }
/// <summary>
/// Gets the state of the consent.
/// </summary>
ConsentState State { get; }
/// <summary>
/// Gets some additional free text.
/// </summary>
string Comment { get; }
/// <summary>
/// Gets the previous states of this consent.
/// </summary>
IEnumerable<IConsent> History { get; }
}
}
+15 -1
View File
@@ -19,6 +19,13 @@
/// <returns></returns>
bool MemberCanViewProperty(string propertyTypeAlias);
/// <summary>
/// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
bool IsSensitiveProperty(string propertyTypeAlias);
/// <summary>
/// Sets a boolean indicating whether a Property is editable by the Member.
/// </summary>
@@ -32,5 +39,12 @@
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetMemberCanViewProperty(string propertyTypeAlias, bool value);
/// <summary>
/// Sets a boolean indicating whether a Property is a sensitive value on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetIsSensitiveProperty(string propertyTypeAlias, bool value);
}
}
}
+49 -13
View File
@@ -64,7 +64,7 @@ namespace Umbraco.Core.Models
}
/// <summary>
/// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias.
/// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias.
/// </summary>
[DataMember]
internal IDictionary<string, MemberTypePropertyProfileAccess> MemberTypePropertyTypes { get; private set; }
@@ -76,11 +76,11 @@ namespace Umbraco.Core.Models
/// <returns></returns>
public bool MemberCanEditProperty(string propertyTypeAlias)
{
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
return MemberTypePropertyTypes[propertyTypeAlias].IsEditable;
return propertyProfile.IsEditable;
}
return false;
}
@@ -91,11 +91,26 @@ namespace Umbraco.Core.Models
/// <returns></returns>
public bool MemberCanViewProperty(string propertyTypeAlias)
{
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
return MemberTypePropertyTypes[propertyTypeAlias].IsVisible;
return propertyProfile.IsVisible;
}
return false;
}
/// <summary>
/// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
public bool IsSensitiveProperty(string propertyTypeAlias)
{
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
return propertyProfile.IsSensitive;
}
return false;
}
@@ -106,13 +121,14 @@ namespace Umbraco.Core.Models
/// <param name="value">Boolean value, true or false</param>
public void SetMemberCanEditProperty(string propertyTypeAlias, bool value)
{
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
MemberTypePropertyTypes[propertyTypeAlias].IsEditable = value;
propertyProfile.IsEditable = value;
}
else
{
var tuple = new MemberTypePropertyProfileAccess(false, value);
var tuple = new MemberTypePropertyProfileAccess(false, value, false);
MemberTypePropertyTypes.Add(propertyTypeAlias, tuple);
}
}
@@ -124,15 +140,35 @@ namespace Umbraco.Core.Models
/// <param name="value">Boolean value, true or false</param>
public void SetMemberCanViewProperty(string propertyTypeAlias, bool value)
{
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
MemberTypePropertyTypes[propertyTypeAlias].IsVisible = value;
propertyProfile.IsVisible = value;
}
else
{
var tuple = new MemberTypePropertyProfileAccess(value, false);
var tuple = new MemberTypePropertyProfileAccess(value, false, false);
MemberTypePropertyTypes.Add(propertyTypeAlias, tuple);
}
}
/// <summary>
/// Sets a boolean indicating whether a Property is a sensitive value on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
public void SetIsSensitiveProperty(string propertyTypeAlias, bool value)
{
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
propertyProfile.IsSensitive = value;
}
else
{
var tuple = new MemberTypePropertyProfileAccess(false, false, true);
MemberTypePropertyTypes.Add(propertyTypeAlias, tuple);
}
}
}
}
}
@@ -5,13 +5,15 @@ namespace Umbraco.Core.Models
/// </summary>
internal class MemberTypePropertyProfileAccess
{
public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable)
public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable, bool isSenstive)
{
IsVisible = isVisible;
IsEditable = isEditable;
IsSensitive = isSenstive;
}
public bool IsVisible { get; set; }
public bool IsEditable { get; set; }
public bool IsSensitive { get; set; }
}
}
}
@@ -0,0 +1,44 @@
using System;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Core.Models.Rdbms
{
[TableName(TableName)]
[PrimaryKey("id")]
[ExplicitColumns]
public class ConsentDto
{
internal const string TableName = "umbracoConsent";
[Column("id")]
[PrimaryKeyColumn]
public int Id { get; set; }
[Column("current")]
public bool Current { get; set; }
[Column("source")]
[Length(512)]
public string Source { get; set; }
[Column("context")]
[Length(128)]
public string Context { get; set; }
[Column("action")]
[Length(512)]
public string Action { get; set; }
[Column("createDate")]
[Constraint(Default = SystemMethods.CurrentDateTime)]
public DateTime CreateDate { get; set; }
[Column("state")]
public int State { get; set; }
[Column("comment")]
public string Comment { get; set; }
}
}
@@ -27,5 +27,9 @@ namespace Umbraco.Core.Models.Rdbms
[Column("viewOnProfile")]
[Constraint(Default = "0")]
public bool ViewOnProfile { get; set; }
[Column("isSensitive")]
[Constraint(Default = "0")]
public bool IsSensitive { get; set; }
}
}
}
@@ -45,6 +45,9 @@ namespace Umbraco.Core.Models.Rdbms
[Column("viewOnProfile")]
public bool ViewOnProfile { get; set; }
[Column("isSensitive")]
public bool IsSensitive { get; set; }
/* cmsDataType */
[Column("propertyEditorAlias")]
public string PropertyEditorAlias { get; set; }
@@ -52,7 +55,8 @@ namespace Umbraco.Core.Models.Rdbms
[Column("dbType")]
public string DbType { get; set; }
[Column("UniqueID")]
[Column("UniqueID")]
public Guid UniqueId { get; set; }
}
}
}
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Models.Rdbms
}
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = 5)]
[PrimaryKeyColumn(IdentitySeed = 6)]
public int Id { get; set; }
[Column("userGroupAlias")]
@@ -68,4 +68,4 @@ namespace Umbraco.Core.Models.Rdbms
[ResultColumn]
public int UserCount { get; set; }
}
}
}
+11 -1
View File
@@ -261,6 +261,16 @@ namespace Umbraco.Core.Models
return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias);
}
/// <summary>
/// Determines whether this user has access to view sensitive data
/// </summary>
/// <param name="user"></param>
public static bool HasAccessToSensitiveData(this IUser user)
{
if (user == null) throw new ArgumentNullException("user");
return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias);
}
// calc. start nodes, combining groups' and user's, and excluding what's in the bin
public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService)
{
@@ -413,4 +423,4 @@ namespace Umbraco.Core.Models
return lsn.ToArray();
}
}
}
}
+57 -29
View File
@@ -140,7 +140,8 @@ namespace Umbraco.Core
if (underlying != null)
{
// Special case for empty strings for bools/dates which should return null if an empty string.
if (input is string inputString)
var inputString = input as string;
if (inputString != null)
{
if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool)))
{
@@ -166,7 +167,8 @@ namespace Umbraco.Core
{
// target is not a generic type
if (input is string inputString)
var inputString = input as string;
if (inputString != null)
{
// Try convert from string, returns an Attempt if the string could be
// processed (either succeeded or failed), else null if we need to try
@@ -207,7 +209,8 @@ namespace Umbraco.Core
}
// Re-check convertables since we altered the input through recursion
if (input is IConvertible convertible2)
var convertible2 = input as IConvertible;
if (convertible2 != null)
{
return Attempt.Succeed(Convert.ChangeType(convertible2, target));
}
@@ -265,7 +268,8 @@ namespace Umbraco.Core
{
if (target == typeof(int))
{
if (int.TryParse(input, out var value))
int value;
if (int.TryParse(input, out value))
{
return Attempt<object>.Succeed(value);
}
@@ -273,26 +277,30 @@ namespace Umbraco.Core
// Because decimal 100.01m will happily convert to integer 100, it
// makes sense that string "100.01" *also* converts to integer 100.
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2));
decimal value2;
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt32(value2));
}
if (target == typeof(long))
{
if (long.TryParse(input, out var value))
long value;
if (long.TryParse(input, out value))
{
return Attempt<object>.Succeed(value);
}
// Same as int
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2));
decimal value2;
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt64(value2));
}
// TODO: Should we do the decimal trick for short, byte, unsigned?
if (target == typeof(bool))
{
if (bool.TryParse(input, out var value))
bool value;
if (bool.TryParse(input, out value))
{
return Attempt<object>.Succeed(value);
}
@@ -305,42 +313,53 @@ namespace Umbraco.Core
switch (Type.GetTypeCode(target))
{
case TypeCode.Int16:
return Attempt<object>.SucceedIf(short.TryParse(input, out var value), value);
short value;
return Attempt<object>.SucceedIf(short.TryParse(input, out value), value);
case TypeCode.Double:
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(double.TryParse(input2, out var valueD), valueD);
double valueD;
return Attempt<object>.SucceedIf(double.TryParse(input2, out valueD), valueD);
case TypeCode.Single:
var input3 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(float.TryParse(input3, out var valueF), valueF);
float valueF;
return Attempt<object>.SucceedIf(float.TryParse(input3, out valueF), valueF);
case TypeCode.Char:
return Attempt<object>.SucceedIf(char.TryParse(input, out var valueC), valueC);
char valueC;
return Attempt<object>.SucceedIf(char.TryParse(input, out valueC), valueC);
case TypeCode.Byte:
return Attempt<object>.SucceedIf(byte.TryParse(input, out var valueB), valueB);
byte valueB;
return Attempt<object>.SucceedIf(byte.TryParse(input, out valueB), valueB);
case TypeCode.SByte:
return Attempt<object>.SucceedIf(sbyte.TryParse(input, out var valueSb), valueSb);
sbyte valueSb;
return Attempt<object>.SucceedIf(sbyte.TryParse(input, out valueSb), valueSb);
case TypeCode.UInt32:
return Attempt<object>.SucceedIf(uint.TryParse(input, out var valueU), valueU);
uint valueU;
return Attempt<object>.SucceedIf(uint.TryParse(input, out valueU), valueU);
case TypeCode.UInt16:
return Attempt<object>.SucceedIf(ushort.TryParse(input, out var valueUs), valueUs);
ushort valueUs;
return Attempt<object>.SucceedIf(ushort.TryParse(input, out valueUs), valueUs);
case TypeCode.UInt64:
return Attempt<object>.SucceedIf(ulong.TryParse(input, out var valueUl), valueUl);
ulong valueUl;
return Attempt<object>.SucceedIf(ulong.TryParse(input, out valueUl), valueUl);
}
}
else if (target == typeof(Guid))
{
return Attempt<object>.SucceedIf(Guid.TryParse(input, out var value), value);
Guid value;
return Attempt<object>.SucceedIf(Guid.TryParse(input, out value), value);
}
else if (target == typeof(DateTime))
{
if (DateTime.TryParse(input, out var value))
DateTime value;
if (DateTime.TryParse(input, out value))
{
switch (value.Kind)
{
@@ -360,20 +379,24 @@ namespace Umbraco.Core
}
else if (target == typeof(DateTimeOffset))
{
return Attempt<object>.SucceedIf(DateTimeOffset.TryParse(input, out var value), value);
DateTimeOffset value;
return Attempt<object>.SucceedIf(DateTimeOffset.TryParse(input, out value), value);
}
else if (target == typeof(TimeSpan))
{
return Attempt<object>.SucceedIf(TimeSpan.TryParse(input, out var value), value);
TimeSpan value;
return Attempt<object>.SucceedIf(TimeSpan.TryParse(input, out value), value);
}
else if (target == typeof(decimal))
{
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value), value);
decimal value;
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out value), value);
}
else if (input != null && target == typeof(Version))
{
return Attempt<object>.SucceedIf(Version.TryParse(input, out var value), value);
Version value;
return Attempt<object>.SucceedIf(Version.TryParse(input, out value), value);
}
// E_NOTIMPL IPAddress, BigInteger
@@ -658,7 +681,8 @@ namespace Umbraco.Core
{
var key = new CompositeTypeTypeKey(source, target);
if (InputTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter))
TypeConverter typeConverter;
if (InputTypeConverterCache.TryGetValue(key, out typeConverter))
{
return typeConverter;
}
@@ -678,7 +702,8 @@ namespace Umbraco.Core
{
var key = new CompositeTypeTypeKey(source, target);
if (DestinationTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter))
TypeConverter typeConverter;
if (DestinationTypeConverterCache.TryGetValue(key, out typeConverter))
{
return typeConverter;
}
@@ -696,7 +721,8 @@ namespace Umbraco.Core
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Type GetCachedGenericNullableType(Type type)
{
if (NullableGenericCache.TryGetValue(type, out Type underlyingType))
Type underlyingType;
if (NullableGenericCache.TryGetValue(type, out underlyingType))
{
return underlyingType;
}
@@ -715,7 +741,8 @@ namespace Umbraco.Core
private static bool GetCachedCanAssign(object input, Type source, Type target)
{
var key = new CompositeTypeTypeKey(source, target);
if (AssignableTypeCache.TryGetValue(key, out bool canConvert))
bool canConvert;
if (AssignableTypeCache.TryGetValue(key, out canConvert))
{
return canConvert;
}
@@ -734,7 +761,8 @@ namespace Umbraco.Core
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetCachedCanConvertToBoolean(Type type)
{
if (BoolConvertCache.TryGetValue(type, out bool result))
bool result;
if (BoolConvertCache.TryGetValue(type, out result))
{
return result;
}
@@ -747,4 +775,4 @@ namespace Umbraco.Core
return BoolConvertCache[type] = false;
}
}
}
}
@@ -0,0 +1,65 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Factories
{
internal static class ConsentFactory
{
public static IEnumerable<IConsent> BuildEntities(IEnumerable<ConsentDto> dtos)
{
var ix = new Dictionary<string, Consent>();
var output = new List<Consent>();
foreach (var dto in dtos)
{
var k = dto.Source + "::" + dto.Context + "::" + dto.Action;
var consent = new Consent
{
Id = dto.Id,
Current = dto.Current,
CreateDate = dto.CreateDate,
Source = dto.Source,
Context = dto.Context,
Action = dto.Action,
State = (ConsentState) dto.State, // assume value is valid
Comment = dto.Comment
};
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
consent.ResetDirtyProperties(false);
if (ix.TryGetValue(k, out var current))
{
if (current.HistoryInternal == null)
current.HistoryInternal = new List<IConsent>();
current.HistoryInternal.Add(consent);
}
else
{
ix[k] = consent;
output.Add(consent);
}
}
return output;
}
public static ConsentDto BuildDto(IConsent entity)
{
return new ConsentDto
{
Id = entity.Id,
Current = entity.Current,
CreateDate = entity.CreateDate,
Source = entity.Source,
Context = entity.Context,
Action = entity.Action,
State = (int) entity.State,
Comment = entity.Comment
};
}
}
}
@@ -84,7 +84,8 @@ namespace Umbraco.Core.Persistence.Factories
NodeId = entity.Id,
PropertyTypeId = x.Id,
CanEdit = memberType.MemberCanEditProperty(x.Alias),
ViewOnProfile = memberType.MemberCanViewProperty(x.Alias)
ViewOnProfile = memberType.MemberCanViewProperty(x.Alias),
IsSensitive = memberType.IsSensitiveProperty(x.Alias)
}).ToList();
return dtos;
}
@@ -159,4 +160,4 @@ namespace Umbraco.Core.Persistence.Factories
#endregion
}
}
}
@@ -56,10 +56,10 @@ namespace Umbraco.Core.Persistence.Factories
//Add the standard PropertyType to the current list
propertyTypes.Add(standardPropertyType.Value);
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
//Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType
memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key,
new MemberTypePropertyProfileAccess(false, false));
new MemberTypePropertyProfileAccess(false, false, false));
}
memberType.NoGroupPropertyTypes = propertyTypes;
@@ -102,7 +102,7 @@ namespace Umbraco.Core.Persistence.Factories
{
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
memberType.MemberTypePropertyTypes.Add(typeDto.Alias,
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit));
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive));
var tempGroupDto = groupDto;
@@ -157,7 +157,7 @@ namespace Umbraco.Core.Persistence.Factories
{
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
memberType.MemberTypePropertyTypes.Add(typeDto.Alias,
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit));
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive));
//ensures that any built-in membership properties have their correct dbtype assigned no matter
//what the underlying data type is
@@ -198,4 +198,4 @@ namespace Umbraco.Core.Persistence.Factories
}
}
}
}
@@ -0,0 +1,40 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Mappers
{
/// <summary>
/// Represents a mapper for consent entities.
/// </summary>
[MapperFor(typeof(IConsent))]
[MapperFor(typeof(Consent))]
public sealed class ConsentMapper : BaseMapper
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance
= new ConcurrentDictionary<string, DtoMapModel>();
/// <summary>
/// Initializes a new instance of the <see cref="ConsentMapper"/> class.
/// </summary>
public ConsentMapper()
{
// note: why the base ctor does not invoke BuildMap is a mystery to me
BuildMap();
}
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache => PropertyInfoCacheInstance;
internal override void BuildMap()
{
CacheMap<Consent, ConsentDto>(entity => entity.Id, dto => dto.Id);
CacheMap<Consent, ConsentDto>(entity => entity.Current, dto => dto.Current);
CacheMap<Consent, ConsentDto>(entity => entity.CreateDate, dto => dto.CreateDate);
CacheMap<Consent, ConsentDto>(entity => entity.Source, dto => dto.Source);
CacheMap<Consent, ConsentDto>(entity => entity.Context, dto => dto.Context);
CacheMap<Consent, ConsentDto>(entity => entity.Action, dto => dto.Action);
CacheMap<Consent, ConsentDto>(entity => entity.State, dto => dto.State);
CacheMap<Consent, ConsentDto>(entity => entity.Comment, dto => dto.Comment);
}
}
}
@@ -178,11 +178,13 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" });
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" });
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" });
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" });
}
private void CreateUmbracoUser2UserGroupData()
{
_database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 });
_database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); //add admin to admins
_database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = 0 }); //add admin to sensitive data
}
private void CreateUmbracoUserGroup2AppData()
@@ -337,4 +339,4 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
_database.Insert("umbracoMigration", "pk", false, dto);
}
}
}
}
@@ -93,7 +93,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{53, typeof (UserGroup2AppDto) },
{54, typeof (UserStartNodeDto) },
{55, typeof (UserLoginDto)},
{56, typeof (AuditEntryDto)}
{56, typeof (ConsentDto)},
{57, typeof (AuditEntryDto)}
};
#endregion
@@ -154,6 +154,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
return new Version(7, 7, 0);
}
//if the error is for isSensitive column it must be the previous version to 7.9 since that is when it is added
if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMemberType,isSensitive"))))
{
return new Version(7, 8, 0);
}
return UmbracoVersion.Current;
}
@@ -214,4 +220,4 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
return sb.ToString();
}
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
{
[Migration("7.9.0", 1, Constants.System.UmbracoMigrationName)]
public class AddIsSensitiveMemberTypeColumn : MigrationBase
{
public AddIsSensitiveMemberTypeColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{
}
public override void Up()
{
//Don't exeucte if the column is already there
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
if (columns.Any(x => x.TableName.InvariantEquals("cmsMemberType") && x.ColumnName.InvariantEquals("isSensitive")) == false)
{
Create.Column("isSensitive").OnTable("cmsMemberType").AsBoolean().WithDefaultValue(0).NotNullable();
}
}
public override void Down()
{
}
}
}
@@ -0,0 +1,29 @@
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenNineZero
{
[Migration("7.9.0", 1, Constants.System.UmbracoMigrationName)]
public class AddUmbracoConsentTable : MigrationBase
{
public AddUmbracoConsentTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
{
}
public override void Up()
{
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
if (tables.InvariantContains(ConsentDto.TableName))
return;
Create.Table<ConsentDto>();
}
public override void Down()
{
}
}
}
@@ -0,0 +1,37 @@
using System;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenNineZero
{
[Migration("7.9.0", 2, Constants.System.UmbracoMigrationName)]
public class CreateSensitiveDataUserGroup : MigrationBase
{
public CreateSensitiveDataUserGroup(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{
}
public override void Up()
{
Execute.Code(database =>
{
//Don't exeucte if the group is already there
var exists = database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoUserGroup WHERE userGroupAlias = @userGroupAlias",
new {userGroupAlias = Constants.Security.SensitiveDataGroupAlias });
if (exists == 0)
{
var resultId = database.Insert("umbracoUserGroup", "id", new UserGroupDto { StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" });
database.Insert(new User2UserGroupDto { UserGroupId = Convert.ToInt32(resultId), UserId = 0 }); //add admin to sensitive data
}
return string.Empty;
});
}
public override void Down()
{
}
}
}
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
/// <summary>
/// Represents the PetaPoco implementation of <see cref="IConsentRepository"/>.
/// </summary>
internal class ConsentRepository : PetaPocoRepositoryBase<int, IConsent>, IConsentRepository
{
/// <summary>
/// Initializes a new instance of the <see cref="ConsentRepository"/> class.
/// </summary>
public ConsentRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{ }
/// <inheritdoc />
protected override Guid NodeObjectTypeId => throw new NotSupportedException();
/// <inheritdoc />
public void ClearCurrent(string source, string context, string action)
{
var sql = new Sql($@"UPDATE {SqlSyntax.GetQuotedTableName(ConsentDto.TableName)}
SET {SqlSyntax.GetQuotedColumnName("current")} = @0
WHERE {SqlSyntax.GetQuotedColumnName("source")} = @1 AND {SqlSyntax.GetQuotedColumnName("context")} = @2 AND {SqlSyntax.GetQuotedColumnName("action")} = @3
AND {SqlSyntax.GetQuotedColumnName("current")} <> @0 ", false, source, context, action);
Database.Execute(sql);
}
/// <inheritdoc />
protected override IConsent PerformGet(int id)
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override IEnumerable<IConsent> PerformGetAll(params int[] ids)
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override IEnumerable<IConsent> PerformGetByQuery(IQuery<IConsent> query)
{
var sqlClause = new Sql().Select("*").From<ConsentDto>(SqlSyntax);
var translator = new SqlTranslator<IConsent>(sqlClause, query);
var sql = translator.Translate().OrderByDescending<ConsentDto>(x => x.CreateDate, SqlSyntax);
return ConsentFactory.BuildEntities(Database.Fetch<ConsentDto>(sql));
}
/// <inheritdoc />
protected override Sql GetBaseQuery(bool isCount)
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override string GetBaseWhereClause()
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override IEnumerable<string> GetDeleteClauses()
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override void PersistNewItem(IConsent entity)
{
((Entity) entity).AddingEntity();
var dto = ConsentFactory.BuildDto(entity);
Database.Insert(dto);
entity.Id = dto.Id;
entity.ResetDirtyProperties();
}
/// <inheritdoc />
protected override void PersistUpdatedItem(IConsent entity)
{
((Entity) entity).UpdatingEntity();
var dto = ConsentFactory.BuildDto(entity);
Database.Update(dto);
entity.ResetDirtyProperties();
IsolatedCache.ClearCacheItem(GetCacheIdKey<IConsent>(entity.Id));
}
}
}
@@ -0,0 +1,15 @@
using Umbraco.Core.Models;
namespace Umbraco.Core.Persistence.Repositories
{
/// <summary>
/// Represents a repository for <see cref="IConsent"/> entities.
/// </summary>
public interface IConsentRepository : IRepositoryQueryable<int, IConsent>
{
/// <summary>
/// Clears the current flag.
/// </summary>
void ClearCurrent(string source, string context, string action);
}
}
@@ -90,7 +90,8 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias",
"cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", "cmsPropertyType.UniqueID",
"cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder",
"cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile",
"cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId",
"cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsMemberType.isSensitive",
"cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId",
"cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.uniqueID AS PropertyGroupUniqueID",
"cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId")
@@ -362,4 +363,4 @@ namespace Umbraco.Core.Persistence.Repositories
return Attempt<Guid>.Fail(propertyEditor);
}
}
}
}
@@ -158,11 +158,11 @@ namespace Umbraco.Core.Persistence
CreateTemplateRepository(uow),
CreateTagRepository(uow),
_settings.Content)
{
{
//duplicates are allowed
EnsureUniqueNaming = false
};
}
}
public virtual IContentTypeRepository CreateContentTypeRepository(IScopeUnitOfWork uow)
{
@@ -319,7 +319,7 @@ namespace Umbraco.Core.Persistence
var userMembershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider();
var passwordConfig = userMembershipProvider == null || userMembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed
? null
: new System.Collections.Generic.Dictionary<string, string> {{"hashAlgorithm", Membership.HashAlgorithmType}};
: new System.Collections.Generic.Dictionary<string, string> {{"hashAlgorithm", Membership.HashAlgorithmType}};
return new UserRepository(
uow,
@@ -398,6 +398,11 @@ namespace Umbraco.Core.Persistence
_sqlSyntax);
}
public IConsentRepository CreateConsentRepository(IScopeUnitOfWork uow)
{
return new ConsentRepository(uow, _cacheHelper, _logger, _sqlSyntax);
}
public IAuditEntryRepository CreateAuditEntryRepository(IScopeUnitOfWork uow)
{
return new AuditEntryRepository(uow, _cacheHelper, _logger, _sqlSyntax);
@@ -401,9 +401,10 @@ namespace Umbraco.Core.Publishing
content.Name, content.Id));
}
// if newest is published, unpublish
if (content.Published)
content.ChangePublishedState(PublishedState.Unpublished);
// make sure we dirty .Published and always unpublish
// the version we have here could be the newest, !Published
content.ChangePublishedState(PublishedState.Published);
content.ChangePublishedState(PublishedState.Unpublished);
_logger.Info<PublishingStrategy>(
string.Format("Content '{0}' with Id '{1}' has been unpublished.",
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
{
/// <summary>
/// Implements <see cref="IContentService"/>.
/// </summary>
internal class ConsentService : ScopeRepositoryService, IConsentService
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentService"/> class.
/// </summary>
public ConsentService(IScopeUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory)
: base(provider, repositoryFactory, logger, eventMessagesFactory)
{ }
/// <inheritdoc />
public IConsent Register(string source, string context, string action, ConsentState state, string comment = null)
{
// prevent stupid states
var v = 0;
if ((state & ConsentState.Pending) > 0) v++;
if ((state & ConsentState.Granted) > 0) v++;
if ((state & ConsentState.Revoked) > 0) v++;
if (v != 1)
throw new ArgumentException("Invalid state.", nameof(state));
var consent = new Consent
{
Current = true,
Source = source,
Context = context,
Action = action,
CreateDate = DateTime.Now,
State = state,
Comment = comment
};
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateConsentRepository(uow);
repository.ClearCurrent(source, context, action);
repository.AddOrUpdate(consent);
uow.Commit();
}
return consent;
}
/// <inheritdoc />
public IEnumerable<IConsent> Get(string source = null, string context = null, string action = null,
bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
bool includeHistory = false)
{
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateConsentRepository(uow);
IQuery<IConsent> query = new Query<IConsent>();
if (string.IsNullOrWhiteSpace(source) == false)
query = sourceStartsWith ? query.Where(x => x.Source.StartsWith(source)) : query.Where(x => x.Source == source);
if (string.IsNullOrWhiteSpace(context) == false)
query = contextStartsWith ? query.Where(x => x.Context.StartsWith(context)) : query.Where(x => x.Context == context);
if (string.IsNullOrWhiteSpace(action) == false)
query = actionStartsWith ? query.Where(x => x.Action.StartsWith(action)) : query.Where(x => x.Action == action);
if (includeHistory == false)
query = query.Where(x => x.Current);
return repository.GetByQuery(query);
}
}
}
}
+2 -2
View File
@@ -884,8 +884,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query<IContent>.Builder.Where(x => x.Published && x.ExpireDate <= DateTime.Now);
return repository.GetByQuery(query);
var query = Query<IContent>.Builder.Where(x => x.ExpireDate <= DateTime.Now);
return repository.GetByQuery(query).Where(x => x.HasPublishedVersion);
}
}
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
{
/// <summary>
/// Represents a service for handling <see cref="IConsent"/> entities.
/// </summary>
/// <remarks>
/// <para>Consent can be given or revoked or changed via the <see cref="Register"/> method, which
/// creates a new <see cref="IConsent"/> entity to track the consent. Revoking a consent is performed by
/// registering a revoked consent.</para>
/// <para>A consent can be revoked, by registering a revoked consent, but cannot be deleted.</para>
/// <para>Getter methods return the current state of a consent, i.e. the latest <see cref="IConsent"/>
/// entity that was created.</para>
/// </remarks>
public interface IConsentService : IService
{
/// <summary>
/// Registers consent.
/// </summary>
/// <param name="source">The source, i.e. whoever is consenting.</param>
/// <param name="context"></param>
/// <param name="action"></param>
/// <param name="state">The state of the consent.</param>
/// <param name="comment">Additional free text.</param>
/// <returns>The corresponding consent entity.</returns>
IConsent Register(string source, string context, string action, ConsentState state, string comment = null);
/// <summary>
/// Retrieves consents.
/// </summary>
/// <param name="source">The optional source.</param>
/// <param name="context">The optional context.</param>
/// <param name="action">The optional action.</param>
/// <param name="sourceStartsWith">Determines whether <paramref name="source"/> is a start pattern.</param>
/// <param name="contextStartsWith">Determines whether <paramref name="context"/> is a start pattern.</param>
/// <param name="actionStartsWith">Determines whether <paramref name="action"/> is a start pattern.</param>
/// <param name="includeHistory">Determines whether to include the history of consents.</param>
/// <returns>Consents matching the paramters.</returns>
IEnumerable<IConsent> Get(string source = null, string context = null, string action = null,
bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
bool includeHistory = false);
}
}
+13 -28
View File
@@ -62,38 +62,13 @@ namespace Umbraco.Core.Services
private Lazy<INotificationService> _notificationService;
private Lazy<IExternalLoginService> _externalLoginService;
private Lazy<IRedirectUrlService> _redirectUrlService;
private Lazy<IConsentService> _consentService;
internal IdkMap IdkMap { get; private set; }
/// <summary>
/// public ctor - will generally just be used for unit testing all items are optional and if not specified, the defaults will be used
/// </summary>
/// <param name="contentService"></param>
/// <param name="mediaService"></param>
/// <param name="contentTypeService"></param>
/// <param name="dataTypeService"></param>
/// <param name="fileService"></param>
/// <param name="localizationService"></param>
/// <param name="packagingService"></param>
/// <param name="entityService"></param>
/// <param name="relationService"></param>
/// <param name="memberGroupService"></param>
/// <param name="memberTypeService"></param>
/// <param name="memberService"></param>
/// <param name="userService"></param>
/// <param name="sectionService"></param>
/// <param name="treeService"></param>
/// <param name="tagService"></param>
/// <param name="notificationService"></param>
/// <param name="localizedTextService"></param>
/// <param name="auditService"></param>
/// <param name="domainService"></param>
/// <param name="taskService"></param>
/// <param name="macroService"></param>
/// <param name="publicAccessService"></param>
/// <param name="externalLoginService"></param>
/// <param name="migrationEntryService"></param>
/// <param name="redirectUrlService"></param>
public ServiceContext(
IContentService contentService = null,
IMediaService mediaService = null,
@@ -120,7 +95,8 @@ namespace Umbraco.Core.Services
IPublicAccessService publicAccessService = null,
IExternalLoginService externalLoginService = null,
IMigrationEntryService migrationEntryService = null,
IRedirectUrlService redirectUrlService = null)
IRedirectUrlService redirectUrlService = null,
IConsentService consentService = null)
{
if (migrationEntryService != null) _migrationEntryService = new Lazy<IMigrationEntryService>(() => migrationEntryService);
if (externalLoginService != null) _externalLoginService = new Lazy<IExternalLoginService>(() => externalLoginService);
@@ -148,6 +124,7 @@ namespace Umbraco.Core.Services
if (macroService != null) _macroService = new Lazy<IMacroService>(() => macroService);
if (publicAccessService != null) _publicAccessService = new Lazy<IPublicAccessService>(() => publicAccessService);
if (redirectUrlService != null) _redirectUrlService = new Lazy<IRedirectUrlService>(() => redirectUrlService);
if (consentService != null) _consentService = new Lazy<IConsentService>(() => consentService);
}
/// <summary>
@@ -310,6 +287,9 @@ namespace Umbraco.Core.Services
if (_redirectUrlService == null)
_redirectUrlService = new Lazy<IRedirectUrlService>(() => new RedirectUrlService(provider, repositoryFactory, logger, eventMessagesFactory));
if (_consentService == null)
_consentService = new Lazy<IConsentService>(() => new ConsentService(provider, repositoryFactory, logger, eventMessagesFactory));
}
internal IEventMessagesFactory EventMessagesFactory { get; private set; }
@@ -526,5 +506,10 @@ namespace Umbraco.Core.Services
{
get { return _redirectUrlService.Value; }
}
/// <summary>
/// Gets the <see cref="IConsentService"/> implementation.
/// </summary>
public IConsentService ConsentService => _consentService.Value;
}
}
}
@@ -55,7 +55,7 @@ namespace Umbraco.Core.Sync
if (newApplicationUrl)
{
appContext._umbracoApplicationDomains.Add(applicationUrl);
LogHelper.Info(typeof(ApplicationUrlHelper), $"New ApplicationUrl detected: {applicationUrl}");
LogHelper.Info(typeof(ApplicationUrlHelper), string.Format("New ApplicationUrl detected: {0}", applicationUrl));
}
}
@@ -169,4 +169,4 @@ namespace Umbraco.Core.Sync
return url.TrimEnd('/');
}
}
}
}
+15
View File
@@ -26,6 +26,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -366,10 +367,14 @@
<Compile Include="IHttpContextAccessor.cs" />
<Compile Include="Logging\RollingFileCleanupAppender.cs" />
<Compile Include="Models\AuditEntry.cs" />
<Compile Include="Models\Consent.cs" />
<Compile Include="Models\ConsentExtensions.cs" />
<Compile Include="Models\ConsentState.cs" />
<Compile Include="Models\EntityBase\EntityPath.cs" />
<Compile Include="Models\EntityBase\IDeletableEntity.cs" />
<Compile Include="Models\IAuditEntry.cs" />
<Compile Include="Models\IAuditItem.cs" />
<Compile Include="Models\IConsent.cs" />
<Compile Include="Models\IUserControl.cs" />
<Compile Include="Models\Membership\ContentPermissionSet.cs" />
<Compile Include="Models\Membership\EntityPermissionCollection.cs" />
@@ -382,6 +387,7 @@
<Compile Include="Models\Membership\UserType.cs" />
<Compile Include="Models\PublishedContent\PublishedContentTypeConverter.cs" />
<Compile Include="Models\Rdbms\AuditEntryDto.cs" />
<Compile Include="Models\Rdbms\ConsentDto.cs" />
<Compile Include="Models\Rdbms\MediaDto.cs" />
<Compile Include="Models\Rdbms\UserLoginDto.cs" />
<Compile Include="Models\Rdbms\UserStartNodeDto.cs" />
@@ -548,6 +554,7 @@
<Compile Include="Persistence\Mappers\AccessMapper.cs" />
<Compile Include="Persistence\Mappers\AuditEntryMapper.cs" />
<Compile Include="Persistence\Mappers\AuditMapper.cs" />
<Compile Include="Persistence\Mappers\ConsentMapper.cs" />
<Compile Include="Persistence\Mappers\DomainMapper.cs" />
<Compile Include="Persistence\Mappers\ExternalLoginMapper.cs" />
<Compile Include="Persistence\Mappers\MigrationEntryMapper.cs" />
@@ -560,6 +567,9 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddCmsMediaTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddUserLoginTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenNineZero\AddUmbracoAuditTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenNineZero\AddUmbracoConsentTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenNineZero\AddIsSensitiveMemberTypeColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenNineZero\CreateSensitiveDataUserGroup.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\AddIndexToDictionaryKeyColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\EnsureContentTemplatePermissions.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\ReduceDictionaryKeyColumnsSize.cs" />
@@ -633,11 +643,14 @@
<Compile Include="Persistence\Repositories\AuditEntryRepository.cs" />
<Compile Include="Persistence\Repositories\AuditRepository.cs" />
<Compile Include="Persistence\Repositories\BaseQueryType.cs" />
<Compile Include="Persistence\Factories\ConsentFactory.cs" />
<Compile Include="Persistence\Repositories\ConsentRepository.cs" />
<Compile Include="Persistence\Repositories\ContentBlueprintRepository.cs" />
<Compile Include="Persistence\Repositories\EntityContainerRepository.cs" />
<Compile Include="Persistence\Repositories\DomainRepository.cs" />
<Compile Include="Persistence\Repositories\ExternalLoginRepository.cs" />
<Compile Include="Persistence\Repositories\IAuditEntryRepository.cs" />
<Compile Include="Persistence\Repositories\IConsentRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IAuditRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IContentTypeCompositionRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IDomainRepository.cs" />
@@ -734,8 +747,10 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\CreateCacheInstructionTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\MigrateStylesheetDataToFile.cs" />
<Compile Include="Services\AuditService.cs" />
<Compile Include="Services\ConsentService.cs" />
<Compile Include="Services\ContentTypeServiceExtensions.cs" />
<Compile Include="Models\RedirectUrl.cs" />
<Compile Include="Services\IConsentService.cs" />
<Compile Include="Services\IContentServiceBase.cs" />
<Compile Include="Services\IdkMap.cs" />
<Compile Include="Services\MediaServiceExtensions.cs" />
@@ -37,7 +37,8 @@ namespace Umbraco.Tests.Benchmarks
{
// This method is 10% faster
var key = new CompositeTypeTypeKey(source, target);
if (AssignableTypeCache.TryGetValue(key, out bool canConvert))
bool canConvert;
if (AssignableTypeCache.TryGetValue(key, out canConvert))
{
return canConvert;
}
@@ -120,6 +120,7 @@ namespace Umbraco.Tests.Models.Mapping
Assert.AreEqual(propTypes.ElementAt(j).DataTypeId, result.PropertyTypes.ElementAt(j).DataTypeDefinitionId);
Assert.AreEqual(propTypes.ElementAt(j).MemberCanViewProperty, result.MemberCanViewProperty(result.PropertyTypes.ElementAt(j).Alias));
Assert.AreEqual(propTypes.ElementAt(j).MemberCanEditProperty, result.MemberCanEditProperty(result.PropertyTypes.ElementAt(j).Alias));
Assert.AreEqual(propTypes.ElementAt(j).IsSensitiveData, result.IsSensitiveProperty(result.PropertyTypes.ElementAt(j).Alias));
}
}
@@ -335,7 +336,7 @@ namespace Umbraco.Tests.Models.Mapping
.Returns(new[] { new TextboxPropertyEditor() });
var memberType = MockedContentTypes.CreateSimpleMemberType();
memberType.MemberTypePropertyTypes[memberType.PropertyTypes.Last().Alias] = new MemberTypePropertyProfileAccess(true, true);
memberType.MemberTypePropertyTypes[memberType.PropertyTypes.Last().Alias] = new MemberTypePropertyProfileAccess(true, true, true);
MockedContentTypes.EnsureAllIds(memberType, 8888);
@@ -539,6 +540,7 @@ namespace Umbraco.Tests.Models.Mapping
{
MemberCanEditProperty = true,
MemberCanViewProperty = true,
IsSensitiveData = true,
Id = 33,
SortOrder = 1,
Alias = "prop1",
@@ -556,6 +558,7 @@ namespace Umbraco.Tests.Models.Mapping
{
MemberCanViewProperty = false,
MemberCanEditProperty = false,
IsSensitiveData = false,
Id = 34,
SortOrder = 2,
Alias = "prop2",
@@ -944,6 +947,7 @@ namespace Umbraco.Tests.Models.Mapping
Label = "Prop 1",
MemberCanViewProperty = true,
MemberCanEditProperty = true,
IsSensitiveData = true,
Validation = new PropertyTypeValidation()
{
Mandatory = true,
@@ -963,6 +967,7 @@ namespace Umbraco.Tests.Models.Mapping
Assert.AreEqual(basic.Validation, result.Validation);
Assert.AreEqual(basic.MemberCanViewProperty, result.MemberCanViewProperty);
Assert.AreEqual(basic.MemberCanEditProperty, result.MemberCanEditProperty);
Assert.AreEqual(basic.IsSensitiveData, result.IsSensitiveData);
}
[Test]
@@ -1029,6 +1034,7 @@ namespace Umbraco.Tests.Models.Mapping
{
MemberCanEditProperty = true,
MemberCanViewProperty = true,
IsSensitiveData = true,
Alias = "property1",
Description = "this is property 1",
Inherited = false,
@@ -271,7 +271,7 @@ AnotherContentFinder
public void Resolves_Assigned_Mappers()
{
var foundTypes1 = _manager.ResolveAssignedMapperTypes();
Assert.AreEqual(29, foundTypes1.Count());
Assert.AreEqual(30, foundTypes1.Count());
}
[Test]
@@ -390,4 +390,4 @@ AnotherContentFinder
}
}
}
}
@@ -21,7 +21,7 @@ namespace Umbraco.Tests.PropertyEditors
[TestFixture]
public class ImageCropperTest
{
private const string cropperJson1 = "{\"focalPoint\": {\"left\": 0.96,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}";
private const string cropperJson1 = "{\"focalPoint\": {\"left\": 0.96,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}";
private const string cropperJson2 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}";
private const string cropperJson3 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": []}";
private const string mediaPath = "/media/1005/img_0671.jpg";
@@ -56,7 +56,7 @@ namespace Umbraco.Tests.PropertyEditors
index++;
}
Assert.AreEqual(index, 1);
Assert.AreEqual(index, 1);
}
[Test]
@@ -136,7 +136,7 @@ namespace Umbraco.Tests.PropertyEditors
}
[TestCase(cropperJson1, cropperJson1, true)]
[TestCase(cropperJson1, cropperJson2, false)]
[TestCase(cropperJson1, cropperJson2, false)]
public void CanConvertImageCropperPropertyEditor(string val1, string val2, bool expected)
{
try
@@ -266,7 +266,7 @@ namespace Umbraco.Tests.PropertyEditors
var urlStringPad = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Pad);
var urlStringMax = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Max);
var urlStringStretch = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Stretch);
Assert.AreEqual(mediaPath + "?mode=min&width=300&height=150", urlStringMin);
Assert.AreEqual(mediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad);
Assert.AreEqual(mediaPath + "?mode=pad&width=300&height=150", urlStringPad);
@@ -380,4 +380,4 @@ namespace Umbraco.Tests.PropertyEditors
Assert.AreEqual(mediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString);
}
}
}
}
@@ -0,0 +1,125 @@
using System;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Services
{
[TestFixture]
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)]
public class ConsentServiceTests : BaseServiceTest
{
[SetUp]
public override void Initialize()
{
base.Initialize();
}
[TearDown]
public override void TearDown()
{
base.TearDown();
}
[Test]
public void CanCrudConsent()
{
// fixme - why isn't this set by the test base class?
Database.Mapper = new PetaPocoMapper();
var consentService = ServiceContext.ConsentService;
// can register
var consent = consentService.Register("user/1234", "app1", "do-something", ConsentState.Granted, "no comment");
Assert.AreNotEqual(0, consent.Id);
Assert.IsTrue(consent.Current);
Assert.AreEqual("user/1234", consent.Source);
Assert.AreEqual("app1", consent.Context);
Assert.AreEqual("do-something", consent.Action);
Assert.AreEqual(ConsentState.Granted, consent.State);
Assert.AreEqual("no comment", consent.Comment);
Assert.IsTrue(consent.IsGranted());
// can register more
consentService.Register("user/1234", "app1", "do-something-else", ConsentState.Granted, "no comment");
consentService.Register("user/1236", "app1", "do-something", ConsentState.Granted, "no comment");
consentService.Register("user/1237", "app2", "do-something", ConsentState.Granted, "no comment");
// can get by source
var consents = consentService.Get(source: "user/1235").ToArray();
Assert.IsEmpty(consents);
consents = consentService.Get(source: "user/1234").ToArray();
Assert.AreEqual(2, consents.Length);
Assert.IsTrue(consents.All(x => x.Source == "user/1234"));
Assert.IsTrue(consents.Any(x => x.Action == "do-something"));
Assert.IsTrue(consents.Any(x => x.Action == "do-something-else"));
// can get by context
consents = consentService.Get(context: "app3").ToArray();
Assert.IsEmpty(consents);
consents = consentService.Get(context: "app2").ToArray();
Assert.AreEqual(1, consents.Length);
consents = consentService.Get(context: "app1").ToArray();
Assert.AreEqual(3, consents.Length);
Assert.IsTrue(consents.Any(x => x.Action == "do-something"));
Assert.IsTrue(consents.Any(x => x.Action == "do-something-else"));
// can get by action
consents = consentService.Get(action: "do-whatever").ToArray();
Assert.IsEmpty(consents);
consents = consentService.Get(context: "app1", action: "do-something").ToArray();
Assert.AreEqual(2, consents.Length);
Assert.IsTrue(consents.All(x => x.Action == "do-something"));
Assert.IsTrue(consents.Any(x => x.Source == "user/1234"));
Assert.IsTrue(consents.Any(x => x.Source == "user/1236"));
// can revoke
consent = consentService.Register("user/1234", "app1", "do-something", ConsentState.Revoked, "no comment");
consents = consentService.Get(source: "user/1234", context: "app1", action: "do-something").ToArray();
Assert.AreEqual(1, consents.Length);
Assert.IsTrue(consents[0].Current);
Assert.AreEqual(ConsentState.Revoked, consents[0].State);
// can filter
consents = consentService.Get(context: "app1", action: "do-", actionStartsWith: true).ToArray();
Assert.AreEqual(3, consents.Length);
Assert.IsTrue(consents.All(x => x.Context == "app1"));
Assert.IsTrue(consents.All(x => x.Action.StartsWith("do-")));
// can get history
consents = consentService.Get(source: "user/1234", context: "app1", action: "do-something", includeHistory: true).ToArray();
Assert.AreEqual(1, consents.Length);
Assert.IsTrue(consents[0].Current);
Assert.AreEqual(ConsentState.Revoked, consents[0].State);
Assert.IsTrue(consents[0].IsRevoked());
Assert.IsNotNull(consents[0].History);
var history = consents[0].History.ToArray();
Assert.AreEqual(1, history.Length);
Assert.IsFalse(history[0].Current);
Assert.AreEqual(ConsentState.Granted, history[0].State);
// cannot be stupid
Assert.Throws<ArgumentException>(() =>
consentService.Register("user/1234", "app1", "do-something", ConsentState.Granted | ConsentState.Revoked, "no comment"));
}
}
}
@@ -47,6 +47,10 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
var mockedContentService = Mock.Of<IContentService>();
var mockedMediaService = Mock.Of<IMediaService>();
var mockedEntityService = Mock.Of<IEntityService>();
var mockedMemberService = Mock.Of<IMemberService>();
var mockedMemberTypeService = Mock.Of<IMemberTypeService>();
var mockedDataTypeService = Mock.Of<IDataTypeService>();
var mockedContentTypeService = Mock.Of<IContentTypeService>();
var mockedMigrationService = new Mock<IMigrationEntryService>();
//set it up to return anything so that the app ctx is 'Configured'
@@ -57,6 +61,10 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
contentService: mockedContentService,
mediaService: mockedMediaService,
entityService: mockedEntityService,
memberService: mockedMemberService,
memberTypeService: mockedMemberTypeService,
dataTypeService: mockedDataTypeService,
contentTypeService: mockedContentTypeService,
migrationEntryService: mockedMigrationService.Object,
localizedTextService:Mock.Of<ILocalizedTextService>(),
sectionService:Mock.Of<ISectionService>());
@@ -156,4 +164,4 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
protected abstract ApiController CreateController(Type controllerType, HttpRequestMessage msg, UmbracoHelper helper);
}
}
}
+1
View File
@@ -191,6 +191,7 @@
<Compile Include="Packaging\PackageExtractionTests.cs" />
<Compile Include="Persistence\Repositories\SimilarNodeNameTests.cs" />
<Compile Include="Services\AuditServiceTests.cs" />
<Compile Include="Services\ConsentServiceTests.cs" />
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingExtensions.cs" />
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingMiddleware.cs" />
<Compile Include="TestHelpers\ControllerTesting\SpecificAssemblyResolver.cs" />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 386 KiB

@@ -25,6 +25,7 @@ The tour object consist of two parts - The overall tour configuration and a list
"group": "My Custom Group" // Used to group tours in the help drawer
"groupOrder": 200 // Control the order of tour groups
"allowDisable": // Adds a "Don't" show this tour again"-button to the intro step
"requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load.
"steps": [] // tour steps - see next example
}
</pre>
@@ -489,7 +489,7 @@
scope.editPropertyTypeSettings = function(property, group) {
if (!property.inherited && !property.locked) {
if (!property.inherited) {
scope.propertySettingsDialogModel = {};
scope.propertySettingsDialogModel.title = "Property settings";
@@ -547,6 +547,7 @@
property.validation.pattern = oldModel.property.validation.pattern;
property.showOnMemberProfile = oldModel.property.showOnMemberProfile;
property.memberCanEdit = oldModel.property.memberCanEdit;
property.isSensitiveValue = oldModel.property.isSensitiveValue;
// because we set state to active, to show a preview, we have to check if has been filled out
// label is required so if it is not filled we know it is a placeholder
@@ -212,6 +212,9 @@
throw "Tour " + tour.alias + " is missing tour steps";
}
if (tour.requiredSections.length === 0) {
throw "Tour " + tour.alias + " is missing the required sections";
}
}
/**
@@ -275,4 +278,4 @@
angular.module("umbraco.services").factory("tourService", tourService);
})();
})();
@@ -56,7 +56,7 @@
});
var saveProperties = _.map(realProperties, function (p) {
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile');
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData');
return saveProperty;
});
@@ -304,14 +304,14 @@
_.each(tab.properties, function (prop) {
//don't include the custom generic tab properties
if (!prop.alias.startsWith("_umb_")) {
//don't include a property that is marked readonly
if (!prop.alias.startsWith("_umb_") && !prop.readonly) {
saveModel.properties.push({
id: prop.id,
alias: prop.alias,
value: prop.value
});
}
});
});
@@ -338,4 +338,4 @@
}
angular.module('umbraco.services').factory('umbDataFormatter', umbDataFormatter);
})();
})();
@@ -14,7 +14,6 @@ function SearchController($scope, searchService, $log, $location, navigationServ
$scope.isSearching = false;
$scope.selectedResult = -1;
$scope.navigateResults = function (ev) {
//38: up 40: down, 13: enter
@@ -34,24 +33,37 @@ function SearchController($scope, searchService, $log, $location, navigationServ
}
};
var group = undefined;
var groupNames = [];
var groupIndex = -1;
var itemIndex = -1;
$scope.selectedItem = undefined;
$scope.clearSearch = function () {
$scope.searchTerm = null;
};
function iterateResults(up) {
//default group
if (!group) {
group = $scope.groups[0];
for (var g in $scope.groups) {
if ($scope.groups.hasOwnProperty(g)) {
groupNames.push(g);
}
}
//Sorting to match the groups order
groupNames.sort();
group = $scope.groups[groupNames[0]];
groupIndex = 0;
}
if (up) {
if (itemIndex === 0) {
if (groupIndex === 0) {
gotoGroup($scope.groups.length - 1, true);
gotoGroup(Object.keys($scope.groups).length - 1, true);
} else {
gotoGroup(groupIndex - 1, true);
}
@@ -62,7 +74,7 @@ function SearchController($scope, searchService, $log, $location, navigationServ
if (itemIndex < group.results.length - 1) {
gotoItem(itemIndex + 1);
} else {
if (groupIndex === $scope.groups.length - 1) {
if (groupIndex === Object.keys($scope.groups).length - 1) {
gotoGroup(0);
} else {
gotoGroup(groupIndex + 1);
@@ -73,7 +85,7 @@ function SearchController($scope, searchService, $log, $location, navigationServ
function gotoGroup(index, up) {
groupIndex = index;
group = $scope.groups[groupIndex];
group = $scope.groups[groupNames[groupIndex]];
if (up) {
gotoItem(group.results.length - 1);
@@ -95,6 +107,13 @@ function SearchController($scope, searchService, $log, $location, navigationServ
$scope.hasResults = false;
if ($scope.searchTerm) {
if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
//Resetting for brand new search
group = undefined;
groupNames = [];
groupIndex = -1;
itemIndex = -1;
$scope.isSearching = true;
navigationService.showSearch();
$scope.selectedItem = undefined;
@@ -114,7 +133,7 @@ function SearchController($scope, searchService, $log, $location, navigationServ
var filtered = {};
_.each(result, function (value, key) {
if (value.results.length > 0) {
filtered[key] = value;
filtered[key] = value;
}
});
$scope.groups = filtered;
@@ -426,103 +426,114 @@ input.umb-group-builder__group-title-input {
.content-type-editor-dialog.edit-property-settings {
.validation-wrapper {
position: relative;
}
.validation-label {
position: absolute;
top: 50%;
right: 0;
font-size: 12px;
color: @red;
transform: translate(0, -50%);
}
textarea.editor-label {
border-color:transparent;
box-shadow: none;
width: 100%;
box-sizing: border-box;
margin-bottom: 0;
font-size: 16px;
font-weight: bold;
resize: none;
line-height: 1.5em;
padding-left: 0;
border: none;
&:focus {
outline: none;
box-shadow: none !important;
.validation-wrapper {
position: relative;
}
}
.editor-placeholder {
border: 1px dashed @gray-8;
width: 100%;
height: 80px;
line-height: 80px;
text-align: center;
display: block;
border-radius: 5px;
color: @gray-3;
font-weight: bold;
font-size: 14px;
color: @turquoise-d1;
&:hover {
text-decoration: none;
}
}
.editor {
margin-bottom: 10px;
.editor-icon-wrapper {
border: 1px solid @gray-8;
width: 60px;
height: 60px;
text-align: center;
line-height: 60px;
border-radius: 5px;
float: left;
margin-right: 20px;
.icon {
font-size: 26px;
}
}
.editor-details {
float: left;
margin-top: 10px;
.editor-name {
display: block;
font-weight: bold;
}
.editor-editor {
display: block;
.validation-label {
position: absolute;
top: 50%;
right: 0;
font-size: 12px;
}
color: @red;
transform: translate(0, -50%);
}
.editor-settings-icon {
font-size: 18px;
margin-top: 8px;
}
}
.checkbox {
margin-bottom: 20px;
}
textarea.editor-label {
border-color: transparent;
box-shadow: none;
width: 100%;
box-sizing: border-box;
margin-bottom: 0;
font-size: 16px;
font-weight: bold;
resize: none;
line-height: 1.5em;
padding-left: 0;
border: none;
.editor-description,
.editor-validation-pattern {
min-width: 100%;
min-height: 25px;
resize: none;
box-sizing: border-box;
border: none;
overflow: hidden;
}
&:focus {
outline: none;
box-shadow: none !important;
}
}
.umb-dropdown {
width: 100%;
}
.editor-placeholder {
border: 1px dashed @gray-8;
width: 100%;
height: 80px;
line-height: 80px;
text-align: center;
display: block;
border-radius: 5px;
color: @gray-3;
font-weight: bold;
font-size: 14px;
color: @turquoise-d1;
&:hover {
text-decoration: none;
}
}
.editor {
margin-bottom: 10px;
.editor-icon-wrapper {
border: 1px solid @gray-8;
width: 60px;
height: 60px;
text-align: center;
line-height: 60px;
border-radius: 5px;
float: left;
margin-right: 20px;
.icon {
font-size: 26px;
}
}
.editor-details {
float: left;
margin-top: 10px;
.editor-name {
display: block;
font-weight: bold;
}
.editor-editor {
display: block;
font-size: 12px;
}
}
.editor-settings-icon {
font-size: 18px;
margin-top: 8px;
}
}
.checkbox {
margin-bottom: 20px;
}
.editor-description,
.editor-validation-pattern {
min-width: 100%;
min-height: 25px;
resize: none;
box-sizing: border-box;
border: none;
overflow: hidden;
}
.umb-dropdown {
width: 100%;
}
label.checkbox.no-indent {
width: 100%;
}
}
@@ -7,196 +7,200 @@
* The controller for the content type editor property dialog
*/
(function() {
"use strict";
(function () {
"use strict";
function PropertySettingsOverlay($scope, contentTypeResource, dataTypeResource, dataTypeHelper, localizationService) {
function PropertySettingsOverlay($scope, contentTypeResource, dataTypeResource, dataTypeHelper, localizationService, userService) {
var vm = this;
var vm = this;
vm.showValidationPattern = false;
vm.focusOnPatternField = false;
vm.focusOnMandatoryField = false;
vm.selectedValidationType = {};
vm.validationTypes = [
{
"name": localizationService.localize("validation_validateAsEmail"),
"key": "email",
"pattern": "[a-zA-Z0-9_\.\+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-\.]+",
"enableEditing": true
},
{
"name": localizationService.localize("validation_validateAsNumber"),
"key": "number",
"pattern": "^[0-9]*$",
"enableEditing": true
},
{
"name": localizationService.localize("validation_validateAsUrl"),
"key": "url",
"pattern": "https?\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}",
"enableEditing": true
},
{
"name": localizationService.localize("validation_enterCustomValidation"),
"key": "custom",
"pattern": "",
"enableEditing": true
}
];
vm.showValidationPattern = false;
vm.focusOnPatternField = false;
vm.focusOnMandatoryField = false;
vm.selectedValidationType = {};
vm.validationTypes = [
{
"name": localizationService.localize("validation_validateAsEmail"),
"key": "email",
"pattern": "[a-zA-Z0-9_\.\+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-\.]+",
"enableEditing": true
},
{
"name": localizationService.localize("validation_validateAsNumber"),
"key": "number",
"pattern": "^[0-9]*$",
"enableEditing": true
},
{
"name": localizationService.localize("validation_validateAsUrl"),
"key": "url",
"pattern": "https?\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}",
"enableEditing": true
},
{
"name": localizationService.localize("validation_enterCustomValidation"),
"key": "custom",
"pattern": "",
"enableEditing": true
}
];
vm.changeValidationType = changeValidationType;
vm.changeValidationPattern = changeValidationPattern;
vm.openEditorPickerOverlay = openEditorPickerOverlay;
vm.openEditorSettingsOverlay = openEditorSettingsOverlay;
vm.changeValidationType = changeValidationType;
vm.changeValidationPattern = changeValidationPattern;
vm.openEditorPickerOverlay = openEditorPickerOverlay;
vm.openEditorSettingsOverlay = openEditorSettingsOverlay;
function activate() {
userService.getCurrentUser().then(function(user) {
vm.showSensitiveData = user.userGroups.indexOf("sensitiveData") != -1;
});
matchValidationType();
function activate() {
}
matchValidationType();
function changeValidationPattern() {
matchValidationType();
}
}
function openEditorPickerOverlay(property) {
function changeValidationPattern() {
matchValidationType();
}
vm.focusOnMandatoryField = false;
function openEditorPickerOverlay(property) {
vm.editorPickerOverlay = {};
vm.editorPickerOverlay.property = $scope.model.property;
vm.editorPickerOverlay.contentTypeName = $scope.model.contentTypeName;
vm.editorPickerOverlay.view = "views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html";
vm.editorPickerOverlay.show = true;
vm.focusOnMandatoryField = false;
vm.editorPickerOverlay.submit = function(model) {
vm.editorPickerOverlay = {};
vm.editorPickerOverlay.property = $scope.model.property;
vm.editorPickerOverlay.contentTypeName = $scope.model.contentTypeName;
vm.editorPickerOverlay.view = "views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html";
vm.editorPickerOverlay.show = true;
$scope.model.updateSameDataTypes = model.updateSameDataTypes;
vm.editorPickerOverlay.submit = function (model) {
vm.focusOnMandatoryField = true;
$scope.model.updateSameDataTypes = model.updateSameDataTypes;
// update property
property.config = model.property.config;
property.editor = model.property.editor;
property.view = model.property.view;
property.dataTypeId = model.property.dataTypeId;
property.dataTypeIcon = model.property.dataTypeIcon;
property.dataTypeName = model.property.dataTypeName;
vm.focusOnMandatoryField = true;
vm.editorPickerOverlay.show = false;
vm.editorPickerOverlay = null;
};
vm.editorPickerOverlay.close = function(model) {
vm.editorPickerOverlay.show = false;
vm.editorPickerOverlay = null;
};
}
function openEditorSettingsOverlay(property) {
vm.focusOnMandatoryField = false;
// get data type
dataTypeResource.getById(property.dataTypeId).then(function(dataType) {
vm.editorSettingsOverlay = {};
vm.editorSettingsOverlay.title = "Editor settings";
vm.editorSettingsOverlay.view = "views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html";
vm.editorSettingsOverlay.dataType = dataType;
vm.editorSettingsOverlay.show = true;
vm.editorSettingsOverlay.submit = function(model) {
var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues);
dataTypeResource.save(model.dataType, preValues, false).then(function(newDataType) {
contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function(propertyType) {
// update editor
property.config = propertyType.config;
property.editor = propertyType.editor;
property.view = propertyType.view;
property.dataTypeId = newDataType.id;
property.dataTypeIcon = newDataType.icon;
property.dataTypeName = newDataType.name;
// set flag to update same data types
$scope.model.updateSameDataTypes = true;
vm.focusOnMandatoryField = true;
vm.editorSettingsOverlay.show = false;
vm.editorSettingsOverlay = null;
});
});
// update property
property.config = model.property.config;
property.editor = model.property.editor;
property.view = model.property.view;
property.dataTypeId = model.property.dataTypeId;
property.dataTypeIcon = model.property.dataTypeIcon;
property.dataTypeName = model.property.dataTypeName;
vm.editorPickerOverlay.show = false;
vm.editorPickerOverlay = null;
};
vm.editorSettingsOverlay.close = function(oldModel) {
vm.editorSettingsOverlay.show = false;
vm.editorSettingsOverlay = null;
vm.editorPickerOverlay.close = function (model) {
vm.editorPickerOverlay.show = false;
vm.editorPickerOverlay = null;
};
});
}
}
function openEditorSettingsOverlay(property) {
function matchValidationType() {
vm.focusOnMandatoryField = false;
if($scope.model.property.validation.pattern !== null && $scope.model.property.validation.pattern !== "" && $scope.model.property.validation.pattern !== undefined) {
// get data type
dataTypeResource.getById(property.dataTypeId).then(function (dataType) {
var match = false;
vm.editorSettingsOverlay = {};
vm.editorSettingsOverlay.title = "Editor settings";
vm.editorSettingsOverlay.view = "views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html";
vm.editorSettingsOverlay.dataType = dataType;
vm.editorSettingsOverlay.show = true;
vm.editorSettingsOverlay.submit = function (model) {
var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues);
dataTypeResource.save(model.dataType, preValues, false).then(function (newDataType) {
contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function (propertyType) {
// update editor
property.config = propertyType.config;
property.editor = propertyType.editor;
property.view = propertyType.view;
property.dataTypeId = newDataType.id;
property.dataTypeIcon = newDataType.icon;
property.dataTypeName = newDataType.name;
// set flag to update same data types
$scope.model.updateSameDataTypes = true;
vm.focusOnMandatoryField = true;
vm.editorSettingsOverlay.show = false;
vm.editorSettingsOverlay = null;
});
});
};
vm.editorSettingsOverlay.close = function (oldModel) {
vm.editorSettingsOverlay.show = false;
vm.editorSettingsOverlay = null;
};
// find and show if a match from the list has been chosen
angular.forEach(vm.validationTypes, function(validationType, index){
if($scope.model.property.validation.pattern === validationType.pattern) {
vm.selectedValidationType = vm.validationTypes[index];
vm.showValidationPattern = true;
match = true;
}
});
// if there is no match - choose the custom validation option.
if(!match) {
angular.forEach(vm.validationTypes, function(validationType){
if(validationType.key === "custom") {
vm.selectedValidationType = validationType;
vm.showValidationPattern = true;
}
});
}
}
}
}
function matchValidationType() {
function changeValidationType(selectedValidationType) {
if ($scope.model.property.validation.pattern !== null && $scope.model.property.validation.pattern !== "" && $scope.model.property.validation.pattern !== undefined) {
if(selectedValidationType) {
$scope.model.property.validation.pattern = selectedValidationType.pattern;
vm.showValidationPattern = true;
var match = false;
// set focus on textarea
if(selectedValidationType.key === "custom") {
vm.focusOnPatternField = true;
// find and show if a match from the list has been chosen
angular.forEach(vm.validationTypes, function (validationType, index) {
if ($scope.model.property.validation.pattern === validationType.pattern) {
vm.selectedValidationType = vm.validationTypes[index];
vm.showValidationPattern = true;
match = true;
}
});
// if there is no match - choose the custom validation option.
if (!match) {
angular.forEach(vm.validationTypes, function (validationType) {
if (validationType.key === "custom") {
vm.selectedValidationType = validationType;
vm.showValidationPattern = true;
}
});
}
}
} else {
$scope.model.property.validation.pattern = "";
vm.showValidationPattern = false;
}
}
}
function changeValidationType(selectedValidationType) {
activate();
if (selectedValidationType) {
$scope.model.property.validation.pattern = selectedValidationType.pattern;
vm.showValidationPattern = true;
}
// set focus on textarea
if (selectedValidationType.key === "custom") {
vm.focusOnPatternField = true;
}
angular.module("umbraco").controller("Umbraco.Overlay.PropertySettingsOverlay", PropertySettingsOverlay);
} else {
$scope.model.property.validation.pattern = "";
vm.showValidationPattern = false;
}
}
activate();
}
angular.module("umbraco").controller("Umbraco.Overlay.PropertySettingsOverlay", PropertySettingsOverlay);
})();
@@ -1,6 +1,6 @@
<div class="content-type-editor-dialog edit-property-settings" ng-controller="Umbraco.Overlay.PropertySettingsOverlay as vm">
<div class="umb-control-group">
<div class="umb-control-group" ng-if="!model.property.locked">
<div class="control-group">
<textarea class="editor-label"
data-element="property-name"
@@ -33,7 +33,7 @@
</textarea>
</div>
<div class="editor-wrapper umb-control-group control-group" ng-model="model.property.editor" val-require-component>
<div class="editor-wrapper umb-control-group control-group" ng-model="model.property.editor" val-require-component ng-if="!model.property.locked">
<a data-element="editor-add" href="" ng-if="!model.property.editor" class="editor-placeholder" hotkey="alt+shift+e" ng-click="vm.openEditorPickerOverlay(model.property)">
<localize key="shortcuts_addEditor"></localize>
@@ -61,7 +61,7 @@
</div>
<div class="umb-control-group clearfix">
<div class="umb-control-group clearfix" ng-if="!model.property.locked">
<h5><localize key="validation_validation"></localize></h5>
@@ -90,17 +90,25 @@
<div class="umb-control-group clearfix" ng-if="model.contentType === 'memberType'">
<h5><localize key="general_rights"></localize></h5>
<h5><localize key="general_options"></localize></h5>
<label class="checkbox no-indent">
<input type="checkbox" ng-model="model.property.showOnMemberProfile">
<localize key="contentTypeEditor_showOnMemberProfile"></localize>
<input type="checkbox" ng-model="model.property.showOnMemberProfile">
<localize key="contentTypeEditor_showOnMemberProfile"></localize>
<small><localize key="contentTypeEditor_showOnMemberProfileDescription"></localize></small>
</label>
<label class="checkbox no-indent">
<input type="checkbox" ng-model="model.property.memberCanEdit">
<localize key="contentTypeEditor_memberCanEdit"></localize>
</label>
<label class="checkbox no-indent">
<input type="checkbox" ng-model="model.property.memberCanEdit">
<localize key="contentTypeEditor_memberCanEdit"></localize>
<small><localize key="contentTypeEditor_memberCanEditDescription"></localize></small>
</label>
<label ng-if="vm.showSensitiveData" class="checkbox no-indent">
<input type="checkbox" ng-model="model.property.isSensitiveData">
<localize key="contentTypeEditor_isSensitiveData"></localize>
<small><localize key="contentTypeEditor_isSensitiveDataDescription"></localize></small>
</label>
</div>
@@ -221,6 +221,11 @@
<localize key="contentTypeEditor_memberCanEdit"></localize>
</div>
<div class="umb-group-builder__property-tag -white" ng-if="property.isSensitiveData">
<i class="icon-lock umb-group-builder__property-tag-icon"></i>
<localize key="contentTypeEditor_isSensitiveData"></localize>
</div>
</div>
<div class="umb-group-builder__property-tags -right">
@@ -251,7 +256,7 @@
<!-- row tools -->
<div class="umb-group-builder__property-actions" ng-if="!sortingMode">
<div ng-if="!property.inherited && !property.locked">
<div ng-if="!property.inherited">
<!-- settings for property -->
<div class="umb-group-builder__property-action" ng-click="editPropertyTypeSettings(property, tab)">
@@ -259,7 +264,7 @@
</div>
<!-- delete property -->
<div class="umb-group-builder__property-action">
<div ng-if="!property.locked" class="umb-group-builder__property-action">
<i class="icon-trash" ng-click="togglePrompt(property)"></i>
<umb-confirm-action
ng-if="property.deletePrompt"
@@ -0,0 +1,5 @@
<div class="umb-editor umb-readonlyvalue">
<em class="muted">
<localize key="content_isSensitiveValue">Hide this property value from content editors that don't have access to view sensitive information</localize>
</em>
</div>
+7 -5
View File
@@ -332,8 +332,8 @@
<Name>umbraco.providers</Name>
</ProjectReference>
<Reference Include="System.Xml.Linq" />
<Reference Include="Umbraco.ModelsBuilder, Version=3.0.8.100, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Umbraco.ModelsBuilder.3.0.8\lib\Umbraco.ModelsBuilder.dll</HintPath>
<Reference Include="Umbraco.ModelsBuilder, Version=3.0.10.102, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Umbraco.ModelsBuilder.3.0.10\lib\Umbraco.ModelsBuilder.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -1024,12 +1024,14 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\"
<WebProjectProperties>
<UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>7900</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:7900</IISUrl>
<DevelopmentServerPort>7790</DevelopmentServerPort>
<IISUrl>http://localhost:7800</IISUrl>
<DevelopmentServerPort>7800</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:7800</IISUrl>
<DevelopmentServerPort>7790</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:7790</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@@ -5,6 +5,15 @@
"group": "Getting Started",
"groupOrder": 100,
"allowDisable": true,
"requiredSections": [
"content",
"media",
"settings",
"developer",
"users",
"member",
"forms"
],
"steps": [
{
"title": "Welcome to Umbraco - The Friendly CMS",
@@ -25,7 +34,6 @@
"content": "Each area in Umbraco is called a <b>Section</b>. Right now you are in the Content section, when you want to go to another section simply click on the appropriate icon in the main menu and you'll be there in no time.",
"backdropOpacity": 0.6
},
{
"element": "#tree",
"elementPreventClick": true,
@@ -88,6 +96,15 @@
"alias": "umbIntroCreateDocType",
"group": "Getting Started",
"groupOrder": 100,
"requiredSections": [
"content",
"media",
"settings",
"developer",
"users",
"member",
"forms"
],
"steps": [
{
"title": "Create your first Document Type",
@@ -203,6 +220,15 @@
"alias": "umbIntroCreateContent",
"group": "Getting Started",
"groupOrder": 100,
"requiredSections": [
"content",
"media",
"settings",
"developer",
"users",
"member",
"forms"
],
"steps": [
{
"title": "Creating your first content node",
@@ -253,6 +279,15 @@
"alias": "umbIntroRenderInTemplate",
"group": "Getting Started",
"groupOrder": 100,
"requiredSections": [
"content",
"media",
"settings",
"developer",
"users",
"member",
"forms"
],
"steps": [
{
"title": "Render your content in a template",
@@ -299,6 +334,15 @@
"alias": "umbIntroViewHomePage",
"group": "Getting Started",
"groupOrder": 100,
"requiredSections": [
"content",
"media",
"settings",
"developer",
"users",
"member",
"forms"
],
"steps": [
{
"title": "View your Umbraco site",
@@ -339,6 +383,15 @@
"alias": "umbIntroMediaSection",
"group": "Getting Started",
"groupOrder": 100,
"requiredSections": [
"content",
"media",
"settings",
"developer",
"users",
"member",
"forms"
],
"steps": [
{
"title": "How to use the media library",
+1 -1
View File
@@ -36,5 +36,5 @@
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
<package id="System.Collections.Immutable" version="1.1.36" targetFramework="net45" />
<package id="System.Reflection.Metadata" version="1.0.21" targetFramework="net45" />
<package id="Umbraco.ModelsBuilder" version="3.0.8" targetFramework="net45" />
<package id="Umbraco.ModelsBuilder" version="3.0.10" targetFramework="net45" />
</packages>
@@ -43,6 +43,7 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="robots" content="noindex, nofollow">
<meta name="pinterest" content="nopin" />
<title ng-bind="$root.locationTitle">Umbraco</title>
@@ -221,6 +221,7 @@
<key alias="addTextBox">Add another text box</key>
<key alias="removeTextBox">Remove this text box</key>
<key alias="contentRoot">Content root</key>
<key alias="isSensitiveValue">This value is hidden. If you need access to view this value please contact your website administrator.</key>
</area>
<area alias="blueprints">
<key alias="createBlueprintFrom">Create a new Content Template from '%0%'</key>
@@ -510,6 +511,7 @@
<key alias="missingPropertyEditorErrorMessage">There is a configuration error with the data type used for this property, please check the data type</key>
</area>
<area alias="general">
<key alias="options">Options</key>
<key alias="about">About</key>
<key alias="action">Action</key>
<key alias="actions">Actions</key>
@@ -1609,7 +1611,12 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="thisEditorUpdateSettings">using this editor will get updated with the new settings</key>
<key alias="memberCanEdit">Member can edit</key>
<key alias="memberCanEditDescription">Allow this property value to be edited by the member on their profile page</key>
<key alias="isSensitiveData">Is sensitive data</key>
<key alias="isSensitiveDataDescription">Hide this property value from content editors that don't have access to view sensitive information</key>
<key alias="showOnMemberProfile">Show on member profile</key>
<key alias="showOnMemberProfileDescription">Allow this property value to be displayed on the member profile page</key>
<key alias="tabHasNoSortOrder">tab has no sort order</key>
</area>
@@ -227,6 +227,7 @@
<key alias="addTextBox">Add another text box</key>
<key alias="removeTextBox">Remove this text box</key>
<key alias="contentRoot">Content root</key>
<key alias="isSensitiveValue">This value is hidden. If you need access to view this value please contact your website administrator.</key>
</area>
<area alias="blueprints">
<key alias="createBlueprintFrom">Create a new Content Template from '%0%'</key>
@@ -516,6 +517,7 @@
<key alias="missingPropertyEditorErrorMessage">There is a configuration error with the data type used for this property, please check the data type</key>
</area>
<area alias="general">
<key alias="options">Options</key>
<key alias="about">About</key>
<key alias="action">Action</key>
<key alias="actions">Actions</key>
@@ -1613,7 +1615,12 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="thisEditorUpdateSettings">using this editor will get updated with the new settings</key>
<key alias="memberCanEdit">Member can edit</key>
<key alias="memberCanEditDescription">Allow this property value to be edited by the member on their profile page</key>
<key alias="isSensitiveData">Is sensitive data</key>
<key alias="isSensitiveDataDescription">Hide this property value from content editors that don't have access to view sensitive information</key>
<key alias="showOnMemberProfile">Show on member profile</key>
<key alias="showOnMemberProfileDescription">Allow this property value to be displayed on the member profile page</key>
<key alias="tabHasNoSortOrder">tab has no sort order</key>
</area>
@@ -1,6 +1,6 @@
using System;
using System.Linq;
using System.Web.Script.Serialization;
using System.Web;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Cache;
@@ -83,36 +83,28 @@ namespace Umbraco.Web.Cache
public override void Refresh(string jsonPayload)
{
ClearCache(DeserializeFromJsonPayload(jsonPayload));
ClearCache();
base.Refresh(jsonPayload);
}
public override void Refresh(int id)
{
ClearCache(FromMemberGroup(ApplicationContext.Current.Services.MemberGroupService.GetById(id)));
ClearCache();
base.Refresh(id);
}
public override void Remove(int id)
{
ClearCache(FromMemberGroup(ApplicationContext.Current.Services.MemberGroupService.GetById(id)));
ClearCache();
base.Remove(id);
}
private void ClearCache(params JsonPayload[] payloads)
private void ClearCache()
{
if (payloads == null) return;
var memberGroupCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache<IMemberGroup>();
payloads.ForEach(payload =>
{
if (payload != null && memberGroupCache)
{
memberGroupCache.Result.ClearCacheByKeySearch(string.Format("{0}.{1}", typeof(IMemberGroup).FullName, payload.Name));
memberGroupCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey<IMemberGroup>(payload.Id));
}
});
// Since we cache by group name, it could be problematic when renaming to
// previously existing names - see http://issues.umbraco.org/issue/U4-10846.
// To work around this, just clear all the cache items
ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.ClearCache<IMemberGroup>();
}
}
}
}
+10 -10
View File
@@ -80,7 +80,7 @@ namespace Umbraco.Web.Editors
public IEnumerable<ContentItemDisplay> GetByIds([FromUri]int[] ids)
{
var foundContent = Services.ContentService.GetByIds(ids);
return foundContent.Select(Mapper.Map<IContent, ContentItemDisplay>);
return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext<IContent, ContentItemDisplay>(content, UmbracoContext));
}
/// <summary>
@@ -219,7 +219,7 @@ namespace Umbraco.Web.Editors
Path = "-1," + Constants.System.RecycleBinContent
};
TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService);
TabsAndPropertiesResolver<IContent>.AddListView(display, "content", Services.DataTypeService, Services.TextService);
return display;
}
@@ -232,7 +232,7 @@ namespace Umbraco.Web.Editors
HandleContentNotFound(id);
}
var content = Mapper.Map<IContent, ContentItemDisplay>(foundContent);
var content = AutoMapperExtensions.MapWithUmbracoContext<IContent, ContentItemDisplay>(foundContent, UmbracoContext);
SetupBlueprint(content, foundContent);
@@ -270,7 +270,7 @@ namespace Umbraco.Web.Editors
HandleContentNotFound(id);
}
var content = Mapper.Map<IContent, ContentItemDisplay>(foundContent);
var content = AutoMapperExtensions.MapWithUmbracoContext<IContent, ContentItemDisplay>(foundContent, UmbracoContext);
return content;
}
@@ -283,7 +283,7 @@ namespace Umbraco.Web.Editors
HandleContentNotFound(id);
}
var content = Mapper.Map<IContent, ContentItemDisplay>(foundContent);
var content = AutoMapperExtensions.MapWithUmbracoContext<IContent, ContentItemDisplay>(foundContent, UmbracoContext);
return content;
}
@@ -306,7 +306,7 @@ namespace Umbraco.Web.Editors
}
var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id);
var mapped = Mapper.Map<IContent, ContentItemDisplay>(emptyContent);
var mapped = AutoMapperExtensions.MapWithUmbracoContext<IContent, ContentItemDisplay>(emptyContent, UmbracoContext);
//remove this tab if it exists: umbContainerView
var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName);
@@ -563,7 +563,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 = Mapper.Map<IContent, ContentItemDisplay>(contentItem.PersistedContent);
var forDisplay = AutoMapperExtensions.MapWithUmbracoContext<IContent, ContentItemDisplay>(contentItem.PersistedContent, UmbracoContext);
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
@@ -605,7 +605,7 @@ namespace Umbraco.Web.Editors
}
//return the updated model
var display = Mapper.Map<IContent, ContentItemDisplay>(contentItem.PersistedContent);
var display = AutoMapperExtensions.MapWithUmbracoContext<IContent, ContentItemDisplay>(contentItem.PersistedContent, UmbracoContext);
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
HandleInvalidModelState(display);
@@ -858,7 +858,7 @@ namespace Umbraco.Web.Editors
var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id);
var content = Mapper.Map<IContent, ContentItemDisplay>(foundContent);
var content = AutoMapperExtensions.MapWithUmbracoContext<IContent, ContentItemDisplay>(foundContent, UmbracoContext);
if (unpublishResult == false)
{
@@ -1092,4 +1092,4 @@ namespace Umbraco.Web.Editors
}
}
}
}
+8 -8
View File
@@ -89,7 +89,7 @@ namespace Umbraco.Web.Editors
}
var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, UmbracoUser.Id);
var mapped = Mapper.Map<IMedia, MediaItemDisplay>(emptyContent);
var mapped = AutoMapperExtensions.MapWithUmbracoContext<IMedia, MediaItemDisplay>(emptyContent, UmbracoContext);
//remove this tab if it exists: umbContainerView
var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName);
@@ -115,7 +115,7 @@ namespace Umbraco.Web.Editors
Path = "-1," + Constants.System.RecycleBinMedia
};
TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService);
TabsAndPropertiesResolver<IMedia>.AddListView(display, "media", Services.DataTypeService, Services.TextService);
return display;
}
@@ -137,7 +137,7 @@ namespace Umbraco.Web.Editors
//HandleContentNotFound will throw an exception
return null;
}
return Mapper.Map<IMedia, MediaItemDisplay>(foundContent);
return AutoMapperExtensions.MapWithUmbracoContext<IMedia, MediaItemDisplay>(foundContent, UmbracoContext);
}
/// <summary>
@@ -157,7 +157,7 @@ namespace Umbraco.Web.Editors
//HandleContentNotFound will throw an exception
return null;
}
return Mapper.Map<IMedia, MediaItemDisplay>(foundContent);
return AutoMapperExtensions.MapWithUmbracoContext<IMedia, MediaItemDisplay>(foundContent, UmbracoContext);
}
/// <summary>
@@ -186,7 +186,7 @@ namespace Umbraco.Web.Editors
public IEnumerable<MediaItemDisplay> GetByIds([FromUri]int[] ids)
{
var foundMedia = Services.MediaService.GetByIds(ids);
return foundMedia.Select(Mapper.Map<IMedia, MediaItemDisplay>);
return foundMedia.Select(media => AutoMapperExtensions.MapWithUmbracoContext<IMedia, MediaItemDisplay>(media, UmbracoContext));
}
/// <summary>
@@ -488,7 +488,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 validation response
var forDisplay = Mapper.Map<IMedia, MediaItemDisplay>(contentItem.PersistedContent);
var forDisplay = AutoMapperExtensions.MapWithUmbracoContext<IMedia, MediaItemDisplay>(contentItem.PersistedContent, UmbracoContext);
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
}
@@ -498,7 +498,7 @@ namespace Umbraco.Web.Editors
var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id);
//return the updated model
var display = Mapper.Map<IMedia, MediaItemDisplay>(contentItem.PersistedContent);
var display = AutoMapperExtensions.MapWithUmbracoContext<IMedia, MediaItemDisplay>(contentItem.PersistedContent, UmbracoContext);
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
HandleInvalidModelState(display);
@@ -607,7 +607,7 @@ namespace Umbraco.Web.Editors
var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder);
mediaService.Save(f, Security.CurrentUser.Id);
return Mapper.Map<IMedia, MediaItemDisplay>(f);
return AutoMapperExtensions.MapWithUmbracoContext<IMedia, MediaItemDisplay>(f, UmbracoContext);
}
/// <summary>
+7 -7
View File
@@ -154,7 +154,7 @@ namespace Umbraco.Web.Editors
ParentId = -1
};
TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService);
TabsAndPropertiesResolver<IMember>.AddListView(display, "member", Services.DataTypeService, Services.TextService);
return display;
}
@@ -178,7 +178,7 @@ namespace Umbraco.Web.Editors
{
HandleContentNotFound(key);
}
return Mapper.Map<IMember, MemberDisplay>(foundMember);
return AutoMapperExtensions.MapWithUmbracoContext<IMember, MemberDisplay>(foundMember, UmbracoContext);
case MembershipScenario.CustomProviderWithUmbracoLink:
//TODO: Support editing custom properties for members with a custom membership provider here.
@@ -238,7 +238,7 @@ namespace Umbraco.Web.Editors
emptyContent = new Member(contentType);
emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(provider.MinRequiredPasswordLength, provider.MinRequiredNonAlphanumericCharacters);
return Mapper.Map<IMember, MemberDisplay>(emptyContent);
return AutoMapperExtensions.MapWithUmbracoContext<IMember, MemberDisplay>(emptyContent, UmbracoContext);
case MembershipScenario.CustomProviderWithUmbracoLink:
//TODO: Support editing custom properties for members with a custom membership provider here.
@@ -247,7 +247,7 @@ namespace Umbraco.Web.Editors
//we need to return a scaffold of a 'simple' member - basically just what a membership provider can edit
emptyContent = MemberService.CreateGenericMembershipProviderMember("", "", "", "");
emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters);
return Mapper.Map<IMember, MemberDisplay>(emptyContent);
return AutoMapperExtensions.MapWithUmbracoContext<IMember, MemberDisplay>(emptyContent, UmbracoContext);
}
}
@@ -287,7 +287,7 @@ namespace Umbraco.Web.Editors
//Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors
if (ModelState.IsValid == false)
{
var forDisplay = Mapper.Map<IMember, MemberDisplay>(contentItem.PersistedContent);
var forDisplay = AutoMapperExtensions.MapWithUmbracoContext<IMember, MemberDisplay>(contentItem.PersistedContent, UmbracoContext);
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
}
@@ -329,7 +329,7 @@ namespace Umbraco.Web.Editors
//If we've had problems creating/updating the user with the provider then return the error
if (ModelState.IsValid == false)
{
var forDisplay = Mapper.Map<IMember, MemberDisplay>(contentItem.PersistedContent);
var forDisplay = AutoMapperExtensions.MapWithUmbracoContext<IMember, MemberDisplay>(contentItem.PersistedContent, UmbracoContext);
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
}
@@ -367,7 +367,7 @@ namespace Umbraco.Web.Editors
contentItem.PersistedContent.AdditionalData["GeneratedPassword"] = generatedPassword;
//return the updated model
var display = Mapper.Map<IMember, MemberDisplay>(contentItem.PersistedContent);
var display = AutoMapperExtensions.MapWithUmbracoContext<IMember, MemberDisplay>(contentItem.PersistedContent, UmbracoContext);
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
HandleInvalidModelState(display);
@@ -131,9 +131,43 @@ namespace Umbraco.Web.Editors
public MemberTypeDisplay PostSave(MemberTypeSave contentTypeSave)
{
//get the persisted member type
var ctId = Convert.ToInt32(contentTypeSave.Id);
var ct = ctId > 0 ? Services.MemberTypeService.Get(ctId) : null;
if (UmbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false)
{
//We need to validate if any properties on the contentTypeSave have had their IsSensitiveValue changed,
//and if so, we need to check if the current user has access to sensitive values. If not, we have to return an error
var props = contentTypeSave.Groups.SelectMany(x => x.Properties);
if (ct != null)
{
foreach (var prop in props)
{
var foundOnContentType = ct.PropertyTypes.FirstOrDefault(x => x.Id == prop.Id);
if (foundOnContentType == null)
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, "No property type with id " + prop.Id + " found on the content type"));
if (ct.IsSensitiveProperty(foundOnContentType.Alias) && prop.IsSensitiveData == false)
{
//if these don't match, then we cannot continue, this user is not allowed to change this value
throw new HttpResponseException(HttpStatusCode.Forbidden);
}
}
}
else
{
//if it is new, then we can just verify if any property has sensitive data turned on which is not allowed
if (props.Any(prop => prop.IsSensitiveData))
{
throw new HttpResponseException(HttpStatusCode.Forbidden);
}
}
}
var savedCt = PerformPostSave<IMemberType, MemberTypeDisplay, MemberTypeSave, MemberPropertyTypeBasic>(
contentTypeSave: contentTypeSave,
getContentType: i => Services.MemberTypeService.Get(i),
getContentType: i => ct,
saveContentType: type => Services.MemberTypeService.Save(type));
var display = Mapper.Map<MemberTypeDisplay>(savedCt);
@@ -145,4 +179,4 @@ namespace Umbraco.Web.Editors
return display;
}
}
}
}
+24 -6
View File
@@ -7,8 +7,6 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Editors
{
@@ -23,7 +21,7 @@ namespace Umbraco.Web.Editors
return result;
var filters = TourFilterResolver.Current.Filters.ToList();
//get all filters that will be applied to all tour aliases
var aliasOnlyFilters = filters.Where(x => x.PluginName == null && x.TourFileName == null).ToList();
@@ -64,8 +62,28 @@ namespace Umbraco.Web.Editors
}
}
}
//Get all allowed sections for the current user
var allowedSections = UmbracoContext.Current.Security.CurrentUser.AllowedSections.ToList();
return result.OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase);
var toursToBeRemoved = new List<BackOfficeTourFile>();
//Checking to see if the user has access to the required tour sections, else we remove the tour
foreach (var backOfficeTourFile in result)
{
foreach (var tour in backOfficeTourFile.Tours)
{
foreach (var toursRequiredSection in tour.RequiredSections)
{
if (allowedSections.Contains(toursRequiredSection) == false)
{
toursToBeRemoved.Add(backOfficeTourFile);
break;
}
}
}
}
return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase);
}
private void TryParseTourFile(string tourFile,
@@ -79,7 +97,7 @@ namespace Umbraco.Web.Editors
//get the filters specific to this file
var fileFilters = filters.Where(x => x.TourFileName != null && x.TourFileName.IsMatch(fileName)).ToList();
//If there is any filter applied to match the file only (no tour alias) then ignore the file entirely
var isFileFiltered = fileFilters.Any(x => x.TourAlias == null);
if (isFileFiltered) return;
@@ -117,4 +135,4 @@ namespace Umbraco.Web.Editors
}
}
}
}
}
+4 -1
View File
@@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models
{
@@ -18,6 +19,8 @@ namespace Umbraco.Web.Models
public int GroupOrder { get; set; }
[DataMember(Name = "allowDisable")]
public bool AllowDisable { get; set; }
[DataMember(Name = "requiredSections")]
public List<string> RequiredSections { get; set; }
[DataMember(Name = "steps")]
public BackOfficeTourStep[] Steps { get; set; }
}
@@ -34,6 +34,9 @@ namespace Umbraco.Web.Models.ContentEditing
public bool HideLabel { get; set; }
[DataMember(Name = "validation")]
public PropertyTypeValidation Validation { get; set; }
public PropertyTypeValidation Validation { get; set; }
[DataMember(Name = "readonly")]
public bool Readonly { get; set; }
}
}
}
@@ -13,5 +13,8 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "memberCanEdit")]
public bool MemberCanEditProperty { get; set; }
[DataMember(Name = "isSensitiveData")]
public bool IsSensitiveData { get; set; }
}
}
}
@@ -10,5 +10,8 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "memberCanEdit")]
public bool MemberCanEditProperty { get; set; }
[DataMember(Name = "isSensitiveData")]
public bool IsSensitiveData { get; set; }
}
}
}
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
internal static class AutoMapperExtensions
{
/// <summary>
/// This maps an object and passes in the current <see cref="UmbracoContext"/> so the mapping logic can use it
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="obj"></param>
/// <param name="umbCtx"></param>
/// <returns></returns>
public static TOut MapWithUmbracoContext<TIn, TOut>(TIn obj, UmbracoContext umbCtx)
{
return Mapper.Map<TIn, TOut>(obj, opt => opt.Items["UmbracoContext"] = umbCtx);
}
/// <summary>
/// Returns an <see cref="UmbracoContext"/> from the mapping options
/// </summary>
/// <param name="res"></param>
/// <returns></returns>
/// <remarks>
/// If an UmbracoContext is not found in the mapping options, it will try to retrieve it from the singleton
/// </remarks>
public static UmbracoContext GetUmbracoContext(this ResolutionContext res)
{
//get the context from the mapping options set during a mapping operation
object umbCtx;
if (res.Options.Items.TryGetValue("UmbracoContext", out umbCtx))
{
var umbracoContext = umbCtx as UmbracoContext;
if (umbracoContext != null) return umbracoContext;
}
//return the singleton (this could be null)
return UmbracoContext.Current;
}
}
}
@@ -35,19 +35,15 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias))
.ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name))
.ForMember(display => display.IsContainer, expression => expression.MapFrom(content => content.ContentType.IsContainer))
.ForMember(display => display.IsChildOfListView, expression => expression.Ignore())
.ForMember(display => display.IsChildOfListView, expression => expression.ResolveUsing(new ChildOfListViewResolver(applicationContext.Services.ContentService, applicationContext.Services.ContentTypeService)))
.ForMember(display => display.Trashed, expression => expression.MapFrom(content => content.Trashed))
.ForMember(display => display.PublishDate, expression => expression.MapFrom(content => GetPublishedDate(content)))
.ForMember(display => display.TemplateAlias, expression => expression.MapFrom(content => content.Template.Alias))
.ForMember(display => display.TemplateAlias, expression => expression.ResolveUsing<DefaultTemplateResolver>())
.ForMember(display => display.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion))
.ForMember(display => display.Urls,
expression => expression.MapFrom(content =>
UmbracoContext.Current == null
? new[] { "Cannot generate urls without a current Umbraco Context" }
: content.GetContentUrls(UmbracoContext.Current)))
.ForMember(display => display.Urls, expression => expression.ResolveUsing<ContentUrlResolver>())
.ForMember(display => display.Properties, expression => expression.Ignore())
.ForMember(display => display.AllowPreview, expression => expression.Ignore())
.ForMember(display => display.TreeNodeUrl, expression => expression.Ignore())
.ForMember(display => display.TreeNodeUrl, opt => opt.ResolveUsing(new ContentTreeNodeUrlResolver<IContent, ContentTreeController>()))
.ForMember(display => display.Notifications, expression => expression.Ignore())
.ForMember(display => display.Errors, expression => expression.Ignore())
.ForMember(display => display.Alias, expression => expression.Ignore())
@@ -56,11 +52,16 @@ namespace Umbraco.Web.Models.Mapping
expression.MapFrom(content => content.ContentType.AllowedTemplates
.Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false)
.ToDictionary(t => t.Alias, t => t.Name)))
.ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService)))
.ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver<IContent>(applicationContext.Services.TextService)))
.ForMember(display => display.AllowedActions, expression => expression.ResolveUsing(
new ActionButtonsResolver(new Lazy<IUserService>(() => applicationContext.Services.UserService), new Lazy<IContentService>(() => applicationContext.Services.ContentService))))
.AfterMap((content, display) => AfterMap(content, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService,
applicationContext.Services.ContentTypeService));
.AfterMap((content, display) =>
{
if (content.ContentType.IsContainer)
{
TabsAndPropertiesResolver<IContent>.AddListView(display, "content", applicationContext.Services.DataTypeService, applicationContext.Services.TextService);
}
});
//FROM IContent TO ContentItemBasic<ContentPropertyBasic, IContent>
config.CreateMap<IContent, ContentItemBasic<ContentPropertyBasic, IContent>>()
@@ -88,42 +89,58 @@ namespace Umbraco.Web.Models.Mapping
var date = ((Content)content).PublishedDate;
return date == default(DateTime) ? (DateTime?)null : date;
}
/// <summary>
/// Maps the generic tab with custom properties for content
/// </summary>
/// <param name="content"></param>
/// <param name="display"></param>
/// <param name="dataTypeService"></param>
/// <param name="localizedText"></param>
/// <param name="contentTypeService"></param>
private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService,
ILocalizedTextService localizedText, IContentTypeService contentTypeService)
internal class ContentUrlResolver : IValueResolver
{
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
var parent = content.Parent();
display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path));
//map the tree node url
if (HttpContext.Current != null)
public ResolutionResult Resolve(ResolutionResult source)
{
var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
var url = urlHelper.GetUmbracoApiService<ContentTreeController>(controller => controller.GetTreeNode(display.Id.ToString(), null));
display.TreeNodeUrl = url;
var content = (IContent)source.Value;
var umbCtx = source.Context.GetUmbracoContext();
var urls = umbCtx == null
? new[] {"Cannot generate urls without a current Umbraco Context"}
: content.GetContentUrls(umbCtx);
return source.New(urls, typeof(string[]));
}
}
internal class DefaultTemplateResolver : ValueResolver<IContent, string>
{
protected override string ResolveCore(IContent source)
{
if (source == null || source.Template == null) return null;
var alias = source.Template.Alias;
//set default template if template isn't set
if (string.IsNullOrEmpty(alias))
alias = source.ContentType.DefaultTemplate == null
? string.Empty
: source.ContentType.DefaultTemplate.Alias;
return alias;
}
}
private class ChildOfListViewResolver : ValueResolver<IContent, bool>
{
private readonly IContentService _contentService;
private readonly IContentTypeService _contentTypeService;
public ChildOfListViewResolver(IContentService contentService, IContentTypeService contentTypeService)
{
_contentService = contentService;
_contentTypeService = contentTypeService;
}
//set default template if template isn't set
if (string.IsNullOrEmpty(display.TemplateAlias))
display.TemplateAlias = content.ContentType.DefaultTemplate == null
? string.Empty
: content.ContentType.DefaultTemplate.Alias;
if (content.ContentType.IsContainer)
protected override bool ResolveCore(IContent source)
{
TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText);
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
var parent = _contentService.GetParent(source);
return parent != null && (parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(parent.Path));
}
TabsAndPropertiesResolver.MapGenericProperties(content, display, localizedText);
}
/// <summary>
@@ -134,9 +151,7 @@ namespace Umbraco.Web.Models.Mapping
{
protected override ContentTypeBasic ResolveCore(IContent source)
{
//TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons
//If this is a web request and there's a user signed in and the
// user has access to the settings section, we will
//TODO: We can resolve the UmbracoContext from the IValueResolver options!
if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null
&& UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)))
{
@@ -194,4 +209,4 @@ namespace Umbraco.Web.Models.Mapping
}
}
}
}
}
@@ -16,9 +16,9 @@ namespace Umbraco.Web.Models.Mapping
internal class ContentPropertyBasicConverter<T> : TypeConverter<Property, T>
where T : ContentPropertyBasic, new()
{
protected Lazy<IDataTypeService> DataTypeService { get; private set; }
protected IDataTypeService DataTypeService { get; private set; }
public ContentPropertyBasicConverter(Lazy<IDataTypeService> dataTypeService)
public ContentPropertyBasicConverter(IDataTypeService dataTypeService)
{
DataTypeService = dataTypeService;
}
@@ -42,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping
var result = new T
{
Id = property.Id,
Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService.Value),
Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService),
Alias = property.Alias,
PropertyEditor = editor,
Editor = editor.Alias
@@ -51,4 +51,4 @@ namespace Umbraco.Web.Models.Mapping
return result;
}
}
}
}
@@ -13,17 +13,19 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter<ContentPropertyDisplay>
{
public ContentPropertyDisplayConverter(Lazy<IDataTypeService> dataTypeService)
private readonly ILocalizedTextService _textService;
public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService)
: base(dataTypeService)
{
_textService = textService;
}
protected override ContentPropertyDisplay ConvertCore(Property originalProp)
{
var display = base.ConvertCore(originalProp);
var dataTypeService = DataTypeService.Value;
var dataTypeService = DataTypeService;
var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProp.PropertyType.DataTypeDefinitionId);
//configure the editor for display with the pre-values
@@ -54,7 +56,11 @@ namespace Umbraco.Web.Models.Mapping
display.View = valEditor.View;
}
//Translate
display.Label = _textService.UmbracoDictionaryTranslate(display.Label);
display.Description = _textService.UmbracoDictionaryTranslate(display.Description);
return display;
}
}
}
}
@@ -12,7 +12,7 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter<ContentPropertyDto>
{
public ContentPropertyDtoConverter(Lazy<IDataTypeService> dataTypeService)
public ContentPropertyDtoConverter(IDataTypeService dataTypeService)
: base(dataTypeService)
{
}
@@ -21,7 +21,7 @@ namespace Umbraco.Web.Models.Mapping
{
var propertyDto = base.ConvertCore(originalProperty);
var dataTypeService = DataTypeService.Value;
var dataTypeService = DataTypeService;
propertyDto.IsRequired = originalProperty.PropertyType.Mandatory;
propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp;
@@ -35,4 +35,4 @@ namespace Umbraco.Web.Models.Mapping
return propertyDto;
}
}
}
}
@@ -16,8 +16,6 @@ namespace Umbraco.Web.Models.Mapping
{
public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
{
var lazyDataTypeService = new Lazy<IDataTypeService>(() => applicationContext.Services.DataTypeService);
//FROM Property TO ContentPropertyBasic
config.CreateMap<PropertyGroup, Tab<ContentPropertyDisplay>>()
.ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name))
@@ -27,15 +25,15 @@ namespace Umbraco.Web.Models.Mapping
//FROM Property TO ContentPropertyBasic
config.CreateMap<Property, ContentPropertyBasic>()
.ConvertUsing(new ContentPropertyBasicConverter<ContentPropertyBasic>(lazyDataTypeService));
.ConvertUsing(new ContentPropertyBasicConverter<ContentPropertyBasic>(applicationContext.Services.DataTypeService));
//FROM Property TO ContentPropertyDto
config.CreateMap<Property, ContentPropertyDto>()
.ConvertUsing(new ContentPropertyDtoConverter(lazyDataTypeService));
.ConvertUsing(new ContentPropertyDtoConverter(applicationContext.Services.DataTypeService));
//FROM Property TO ContentPropertyDisplay
config.CreateMap<Property, ContentPropertyDisplay>()
.ConvertUsing(new ContentPropertyDisplayConverter(lazyDataTypeService));
.ConvertUsing(new ContentPropertyDisplayConverter(applicationContext.Services.DataTypeService, applicationContext.Services.TextService));
}
}
}
}
@@ -0,0 +1,34 @@
using System.Web.Mvc;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Web.Trees;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Gets the tree node url for the content or media
/// </summary>
internal class ContentTreeNodeUrlResolver<TSource, TController> : IValueResolver
where TSource : IContentBase
where TController : ContentTreeControllerBase
{
public ResolutionResult Resolve(ResolutionResult source)
{
return source.New(ResolveCore(source, (TSource)source.Value), typeof(string));
}
private string ResolveCore(ResolutionResult res, TSource source)
{
var umbCtx = res.Context.GetUmbracoContext();
//map the tree node url
if (umbCtx != null)
{
var urlHelper = new UrlHelper(umbCtx.HttpContext.Request.RequestContext);
var url = urlHelper.GetUmbracoApiService<TController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
return url;
}
return null;
}
}
}
@@ -88,7 +88,7 @@ namespace Umbraco.Web.Models.Mapping
{
ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext);
//map the MemberCanEditProperty,MemberCanViewProperty
//map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
foreach (var propertyType in source.Groups.SelectMany(x => x.Properties))
{
var localCopy = propertyType;
@@ -97,6 +97,7 @@ namespace Umbraco.Web.Models.Mapping
{
dest.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty);
dest.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty);
dest.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData);
}
}
});
@@ -108,7 +109,7 @@ namespace Umbraco.Web.Models.Mapping
.MapBaseContentTypeEntityToDisplay<IMemberType, MemberTypeDisplay, MemberPropertyTypeDisplay>(applicationContext, _propertyEditorResolver)
.AfterMap((memberType, display) =>
{
//map the MemberCanEditProperty,MemberCanViewProperty
//map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
foreach (var propertyType in memberType.PropertyTypes)
{
var localCopy = propertyType;
@@ -117,6 +118,7 @@ namespace Umbraco.Web.Models.Mapping
{
displayProp.MemberCanEditProperty = memberType.MemberCanEditProperty(localCopy.Alias);
displayProp.MemberCanViewProperty = memberType.MemberCanViewProperty(localCopy.Alias);
displayProp.IsSensitiveData = memberType.IsSensitiveProperty(localCopy.Alias);
}
}
});
@@ -284,4 +286,4 @@ namespace Umbraco.Web.Models.Mapping
}
}
}
@@ -19,9 +19,7 @@ namespace Umbraco.Web.Models.Mapping
{
public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
{
var lazyDataTypeService = new Lazy<IDataTypeService>(() => applicationContext.Services.DataTypeService);
config.CreateMap<PropertyEditor, PropertyEditorBasic>();
config.CreateMap<PropertyEditor, PropertyEditorBasic>();
//just maps the standard properties, does not map the value!
config.CreateMap<PreValueField, PreValueFieldDisplay>()
@@ -67,7 +65,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DataType, content.Key)))
.ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver(UmbracoConfig.For.UmbracoSettings().Content)))
.ForMember(display => display.PreValues, expression => expression.ResolveUsing(
new PreValueDisplayResolver(lazyDataTypeService)))
new PreValueDisplayResolver(applicationContext.Services.DataTypeService)))
.ForMember(display => display.SelectedEditor, expression => expression.MapFrom(
definition => definition.PropertyEditorAlias.IsNullOrWhiteSpace() ? null : definition.PropertyEditorAlias))
.ForMember(x => x.HasPrevalues, expression => expression.Ignore())
@@ -90,7 +88,7 @@ namespace Umbraco.Web.Models.Mapping
config.CreateMap<IDataTypeDefinition, IEnumerable<PreValueFieldDisplay>>()
.ConvertUsing(definition =>
{
var resolver = new PreValueDisplayResolver(lazyDataTypeService);
var resolver = new PreValueDisplayResolver(applicationContext.Services.DataTypeService);
return resolver.Convert(definition);
});
@@ -124,4 +122,4 @@ namespace Umbraco.Web.Models.Mapping
});
}
}
}
}
@@ -29,11 +29,11 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver<IMedia>()))
.ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon))
.ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias))
.ForMember(display => display.IsChildOfListView, expression => expression.Ignore())
.ForMember(display => display.IsChildOfListView, expression => expression.ResolveUsing(new ChildOfListViewResolver(applicationContext.Services.MediaService, applicationContext.Services.ContentTypeService)))
.ForMember(display => display.Trashed, expression => expression.MapFrom(content => content.Trashed))
.ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name))
.ForMember(display => display.Properties, expression => expression.Ignore())
.ForMember(display => display.TreeNodeUrl, expression => expression.Ignore())
.ForMember(display => display.TreeNodeUrl, opt => opt.ResolveUsing(new ContentTreeNodeUrlResolver<IMedia, MediaTreeController>()))
.ForMember(display => display.Notifications, expression => expression.Ignore())
.ForMember(display => display.Errors, expression => expression.Ignore())
.ForMember(display => display.Published, expression => expression.Ignore())
@@ -41,11 +41,17 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(display => display.Alias, expression => expression.Ignore())
.ForMember(display => display.IsContainer, expression => expression.Ignore())
.ForMember(display => display.HasPublishedVersion, expression => expression.Ignore())
.ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService)))
.ForMember(display => display.ContentType, expression => expression.ResolveUsing<MediaTypeBasicResolver>())
.ForMember(display => display.MediaLink, expression => expression.ResolveUsing(
.ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver<IMedia>(applicationContext.Services.TextService)))
.ForMember(display => display.ContentType, expression => expression.ResolveUsing<MediaTypeBasicResolver>())
.ForMember(display => display.MediaLink, expression => expression.ResolveUsing(
content => string.Join(",", content.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, applicationContext.ProfilingLogger.Logger))))
.AfterMap((media, display) => AfterMap(media, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, applicationContext.Services.ContentTypeService, applicationContext.ProfilingLogger.Logger));
.AfterMap((media, display) =>
{
if (media.ContentType.IsContainer)
{
TabsAndPropertiesResolver<IMedia>.AddListView(display, "media", applicationContext.Services.DataTypeService, applicationContext.Services.TextService);
}
});
//FROM IMedia TO ContentItemBasic<ContentPropertyBasic, IMedia>
config.CreateMap<IMedia, ContentItemBasic<ContentPropertyBasic, IMedia>>()
@@ -68,31 +74,28 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dto => dto.Icon, expression => expression.Ignore())
.ForMember(dto => dto.Alias, expression => expression.Ignore())
.ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore());
}
}
private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, IContentTypeService contentTypeService, ILogger logger)
private class ChildOfListViewResolver : ValueResolver<IMedia, bool>
{
// Adapted from ContentModelMapper
//map the IsChildOfListView (this is actually if it is a descendant of a list view!)
var parent = media.Parent();
display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path));
private readonly IMediaService _mediaService;
private readonly IContentTypeService _contentTypeService;
//map the tree node url
if (HttpContext.Current != null)
public ChildOfListViewResolver(IMediaService mediaService, IContentTypeService contentTypeService)
{
var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
var url = urlHelper.GetUmbracoApiService<MediaTreeController>(controller => controller.GetTreeNode(display.Id.ToString(), null));
display.TreeNodeUrl = url;
_mediaService = mediaService;
_contentTypeService = contentTypeService;
}
if (media.ContentType.IsContainer)
protected override bool ResolveCore(IMedia source)
{
TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText);
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
var parent = _mediaService.GetParent(source);
return parent != null && (parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(parent.Path));
}
TabsAndPropertiesResolver.MapGenericProperties(media, display, localizedText);
}
/// <summary>
/// Resolves a <see cref="ContentTypeBasic"/> from the <see cref="IContent"/> item and checks if the current user
/// has access to see this data
@@ -101,9 +104,7 @@ namespace Umbraco.Web.Models.Mapping
{
protected override ContentTypeBasic ResolveCore(IMedia source)
{
//TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons
//If this is a web request and there's a user signed in and the
// user has access to the settings section, we will
//TODO: We can resolve the UmbracoContext from the IValueResolver options!
if (HttpContext.Current != null && UmbracoContext.Current != null &&
UmbracoContext.Current.Security.CurrentUser != null
&& UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using AutoMapper;
@@ -14,7 +13,6 @@ using Umbraco.Web.Models.ContentEditing;
using System.Linq;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Security;
using Umbraco.Web.Trees;
using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile;
namespace Umbraco.Web.Models.Mapping
@@ -27,12 +25,7 @@ namespace Umbraco.Web.Models.Mapping
public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
{
//FROM MembershipUser TO MediaItemDisplay - used when using a non-umbraco membership provider
config.CreateMap<MembershipUser, MemberDisplay>()
.ConvertUsing(user =>
{
var member = Mapper.Map<MembershipUser, IMember>(user);
return Mapper.Map<IMember, MemberDisplay>(member);
});
config.CreateMap<MembershipUser, MemberDisplay>().ConvertUsing<MembershipUserTypeConverter>();
//FROM MembershipUser TO IMember - used when using a non-umbraco membership provider
config.CreateMap<MembershipUser, IMember>()
@@ -56,9 +49,9 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(member => member.SortOrder, expression => expression.Ignore())
.ForMember(member => member.AdditionalData, expression => expression.Ignore())
.ForMember(member => member.FailedPasswordAttempts, expression => expression.Ignore())
.ForMember(member => member.DeletedDate, expression => expression.Ignore())
//TODO: Support these eventually
.ForMember(member => member.PasswordQuestion, expression => expression.Ignore())
.ForMember(member => member.DeletedDate, expression => expression.Ignore())
//TODO: Support these eventually
.ForMember(member => member.PasswordQuestion, expression => expression.Ignore())
.ForMember(member => member.RawPasswordAnswerValue, expression => expression.Ignore());
//FROM IMember TO MediaItemDisplay
@@ -69,10 +62,10 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias))
.ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name))
.ForMember(display => display.Properties, expression => expression.Ignore())
.ForMember(display => display.Tabs, expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(applicationContext.Services.TextService)))
.ForMember(display => display.Tabs, expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(applicationContext.Services.TextService, applicationContext.Services.MemberService, applicationContext.Services.UserService)))
.ForMember(display => display.MemberProviderFieldMapping, expression => expression.ResolveUsing(new MemberProviderFieldMappingResolver()))
.ForMember(display => display.MembershipScenario,
expression => expression.ResolveUsing(new MembershipScenarioMappingResolver(new Lazy<IMemberTypeService>(() => applicationContext.Services.MemberTypeService))))
expression => expression.ResolveUsing(new MembershipScenarioMappingResolver(applicationContext.Services.MemberTypeService)))
.ForMember(display => display.Notifications, expression => expression.Ignore())
.ForMember(display => display.Errors, expression => expression.Ignore())
.ForMember(display => display.Published, expression => expression.Ignore())
@@ -81,9 +74,9 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(display => display.IsChildOfListView, expression => expression.Ignore())
.ForMember(display => display.Trashed, expression => expression.Ignore())
.ForMember(display => display.IsContainer, expression => expression.Ignore())
.ForMember(display => display.TreeNodeUrl, expression => expression.Ignore())
.ForMember(display => display.HasPublishedVersion, expression => expression.Ignore())
.AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, applicationContext.Services.UserService, member, display, applicationContext.Services.TextService));
.ForMember(display => display.TreeNodeUrl, opt => opt.ResolveUsing(new MemberTreeNodeUrlResolver()))
.ForMember(display => display.HasPublishedVersion, expression => expression.Ignore());
//.AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, applicationContext.Services.UserService, member, display, applicationContext.Services.TextService));
//FROM IMember TO MemberBasic
config.CreateMap<IMember, MemberBasic>()
@@ -100,14 +93,14 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore());
//FROM MembershipUser TO MemberBasic
config.CreateMap<MembershipUser, MemberBasic>()
config.CreateMap<MembershipUser, MemberBasic>()
//we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity
.ForMember(member => member.Id, expression => expression.MapFrom(user => int.MaxValue))
.ForMember(display => display.Udi, expression => expression.Ignore())
.ForMember(member => member.CreateDate, expression => expression.MapFrom(user => user.CreationDate))
.ForMember(member => member.UpdateDate, expression => expression.MapFrom(user => user.LastActivityDate))
.ForMember(member => member.Key, expression => expression.MapFrom(user => user.ProviderUserKey.TryConvertTo<Guid>().Result.ToString("N")))
.ForMember(member => member.Owner, expression => expression.UseValue(new UserProfile {Name = "Admin", UserId = 0}))
.ForMember(member => member.Owner, expression => expression.UseValue(new UserProfile { Name = "Admin", UserId = 0 }))
.ForMember(member => member.Icon, expression => expression.UseValue("icon-user"))
.ForMember(member => member.Name, expression => expression.MapFrom(user => user.UserName))
.ForMember(member => member.Email, expression => expression.MapFrom(content => content.Email))
@@ -137,119 +130,11 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dto => dto.Properties, expression => expression.ResolveUsing(new MemberDtoPropertiesValueResolver()));
}
/// <summary>
/// Maps the generic tab with custom properties for content
/// </summary>
/// <param name="memberService"></param>
/// <param name="userService"></param>
/// <param name="member"></param>
/// <param name="display"></param>
/// <param name="localizedText"></param>
/// <remarks>
/// If this is a new entity and there is an approved field then we'll set it to true by default.
/// </remarks>
private static void MapGenericCustomProperties(IMemberService memberService, IUserService userService, IMember member, MemberDisplay display, ILocalizedTextService localizedText)
{
var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
//map the tree node url
if (HttpContext.Current != null)
{
var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
var url = urlHelper.GetUmbracoApiService<MemberTreeController>(controller => controller.GetTreeNode(display.Key.ToString("N"), null));
display.TreeNodeUrl = url;
}
var genericProperties = new List<ContentPropertyDisplay>
{
new ContentPropertyDisplay
{
Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("content/membertype"),
Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName),
View = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View
},
GetLoginProperty(memberService, member, display, localizedText),
new ContentPropertyDisplay
{
Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("general/email"),
Value = display.Email,
View = "email",
Validation = {Mandatory = true}
},
new ContentPropertyDisplay
{
Alias = string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("password"),
//NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists
// only when creating a new member and we want to have a generated password pre-filled.
Value = new Dictionary<string, object>
{
{"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)},
{"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)},
},
//TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor
View = "changepassword",
//initialize the dictionary with the configuration from the default membership provider
Config = new Dictionary<string, object>(membersProvider.GetConfiguration(userService))
{
//the password change toggle will only be displayed if there is already a password assigned.
{"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false}
}
},
new ContentPropertyDisplay
{
Alias = string.Format("{0}membergroup", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("content/membergroup"),
Value = GetMemberGroupValue(display.Username),
View = "membergroups",
Config = new Dictionary<string, object> {{"IsRequired", true}}
}
};
TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties, properties =>
{
if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null
&& UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)))
{
var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId);
//Replace the doctype property
var docTypeProperty = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
docTypeProperty.Value = new List<object>
{
new
{
linkText = member.ContentType.Name,
url = memberTypeLink,
target = "_self",
icon = "icon-item-arrangement"
}
};
docTypeProperty.View = "urllist";
}
});
//check if there's an approval field
var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider;
if (member.HasIdentity == false && provider != null)
{
var approvedField = provider.ApprovedPropertyTypeAlias;
var prop = display.Properties.FirstOrDefault(x => x.Alias == approvedField);
if (prop != null)
{
prop.Value = 1;
}
}
}
/// <summary>
/// Returns the login property display field
/// </summary>
/// <param name="memberService"></param>
/// <param name="member"></param>
/// <param name="display"></param>
/// <param name="localizedText"></param>
/// <returns></returns>
/// <remarks>
@@ -257,13 +142,13 @@ namespace Umbraco.Web.Models.Mapping
/// the membership provider is a custom one, we cannot allow chaning the username because MembershipProvider's do not actually natively
/// allow that.
/// </remarks>
internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, MemberDisplay display, ILocalizedTextService localizedText)
internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, ILocalizedTextService localizedText)
{
var prop = new ContentPropertyDisplay
{
Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("login"),
Value = display.Username
Value = member.Username
};
var scenario = memberService.GetMembershipScenario();
@@ -329,33 +214,47 @@ namespace Umbraco.Web.Models.Mapping
/// <remarks>
/// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because
/// an admin cannot actually set isLockedOut = true, they can only unlock.
///
/// This also ensures that the IsSensitive property display value is set based on the configured IMemberType property type
/// </remarks>
internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver
internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver<IMember>
{
private readonly ILocalizedTextService _localizedTextService;
private readonly IMemberService _memberService;
private readonly IUserService _userService;
public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService)
public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService)
: base(localizedTextService)
{
_localizedTextService = localizedTextService;
_memberService = memberService;
_userService = userService;
}
public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService,
IEnumerable<string> ignoreProperties) : base(localizedTextService, ignoreProperties)
IEnumerable<string> ignoreProperties, IMemberService memberService, IUserService userService) : base(localizedTextService, ignoreProperties)
{
_localizedTextService = localizedTextService;
_memberService = memberService;
_userService = userService;
}
protected override IEnumerable<Tab<ContentPropertyDisplay>> ResolveCore(IContentBase content)
/// <summary>
/// Overridden to deal with custom member properties and permissions
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <returns></returns>
protected override List<Tab<ContentPropertyDisplay>> ResolveCore(UmbracoContext umbracoContext, IMember content)
{
var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
IgnoreProperties = content.PropertyTypes
.Where(x => x.HasIdentity == false)
.Select(x => x.Alias)
.ToArray();
var result = base.ResolveCore(content).ToArray();
var result = base.ResolveCore(umbracoContext, content);
if (provider.IsUmbracoMembershipProvider() == false)
{
@@ -366,12 +265,10 @@ namespace Umbraco.Web.Models.Mapping
isLockedOutProperty.View = "readonlyvalue";
isLockedOutProperty.Value = _localizedTextService.Localize("general/no");
}
return result;
}
else
{
var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider;
var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider;
//This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier
// if we just had all of the membeship provider fields on the member table :(
@@ -382,17 +279,139 @@ namespace Umbraco.Web.Models.Mapping
isLockedOutProperty.View = "readonlyvalue";
isLockedOutProperty.Value = _localizedTextService.Localize("general/no");
}
return result;
}
if (umbracoContext != null && umbracoContext.Security.CurrentUser != null
&& umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)))
{
var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", content.ContentTypeId);
//Replace the doctype property
var docTypeProperty = result.SelectMany(x => x.Properties)
.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
docTypeProperty.Value = new List<object>
{
new
{
linkText = content.ContentType.Name,
url = memberTypeLink,
target = "_self",
icon = "icon-item-arrangement"
}
};
docTypeProperty.View = "urllist";
}
//check if there's an approval field
var legacyProvider = provider as global::umbraco.providers.members.UmbracoMembershipProvider;
if (content.HasIdentity == false && legacyProvider != null)
{
var approvedField = legacyProvider.ApprovedPropertyTypeAlias;
var prop = result.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == approvedField);
if (prop != null)
{
prop.Value = 1;
}
}
return result;
}
protected override IEnumerable<ContentPropertyDisplay> GetCustomGenericProperties(IContentBase content)
{
var member = (IMember) content;
var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
var genericProperties = new List<ContentPropertyDisplay>
{
new ContentPropertyDisplay
{
Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = _localizedTextService.Localize("content/membertype"),
//Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName),
Value = _localizedTextService.UmbracoDictionaryTranslate(member.ContentType.Name),
View = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View
},
GetLoginProperty(_memberService, member, _localizedTextService),
new ContentPropertyDisplay
{
Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = _localizedTextService.Localize("general/email"),
Value = member.Email,
View = "email",
Validation = {Mandatory = true}
},
new ContentPropertyDisplay
{
Alias = string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = _localizedTextService.Localize("password"),
//NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists
// only when creating a new member and we want to have a generated password pre-filled.
Value = new Dictionary<string, object>
{
{"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)},
{"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)},
},
//TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor
View = "changepassword",
//initialize the dictionary with the configuration from the default membership provider
Config = new Dictionary<string, object>(membersProvider.GetConfiguration(_userService))
{
//the password change toggle will only be displayed if there is already a password assigned.
{"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false}
}
},
new ContentPropertyDisplay
{
Alias = string.Format("{0}membergroup", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = _localizedTextService.Localize("content/membergroup"),
Value = GetMemberGroupValue(member.Username),
View = "membergroups",
Config = new Dictionary<string, object> {{"IsRequired", true}}
}
};
return genericProperties;
}
/// <summary>
/// Overridden to assign the IsSensitive property values
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="properties"></param>
/// <returns></returns>
protected override List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties)
{
var result = base.MapProperties(umbracoContext, content, properties);
var member = (IMember)content;
var memberType = member.ContentType;
//now update the IsSensitive value
foreach (var prop in result)
{
//check if this property is flagged as sensitive
var isSensitiveProperty = memberType.IsSensitiveProperty(prop.Alias);
//check permissions for viewing sensitive data
if (isSensitiveProperty && umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false)
{
//mark this property as readonly so that it does not post any data
prop.Readonly = true;
//replace this editor with a sensitivevalue
prop.View = "sensitivevalue";
//clear the value
prop.Value = null;
}
}
return result;
}
}
internal class MembershipScenarioMappingResolver : ValueResolver<IMember, MembershipScenario>
{
private readonly Lazy<IMemberTypeService> _memberTypeService;
private readonly IMemberTypeService _memberTypeService;
public MembershipScenarioMappingResolver(Lazy<IMemberTypeService> memberTypeService)
public MembershipScenarioMappingResolver(IMemberTypeService memberTypeService)
{
_memberTypeService = memberTypeService;
}
@@ -405,7 +424,7 @@ namespace Umbraco.Web.Models.Mapping
{
return MembershipScenario.NativeUmbraco;
}
var memberType = _memberTypeService.Value.Get(Constants.Conventions.MemberTypes.DefaultAlias);
var memberType = _memberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias);
return memberType != null
? MembershipScenario.CustomProviderWithUmbracoLink
: MembershipScenario.StandaloneCustomProvider;
@@ -432,7 +451,7 @@ namespace Umbraco.Web.Models.Mapping
}
else
{
var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider;
var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider;
return new Dictionary<string, string>
{
@@ -443,5 +462,23 @@ namespace Umbraco.Web.Models.Mapping
}
}
}
/// <summary>
/// A converter to go from a <see cref="MembershipUser"/> to a <see cref="MemberDisplay"/>
/// </summary>
internal class MembershipUserTypeConverter : ITypeConverter<MembershipUser, MemberDisplay>
{
public MemberDisplay Convert(ResolutionContext context)
{
var source = (MembershipUser)context.SourceValue;
//first convert to IMember
var member = Mapper.Map<MembershipUser, IMember>(source);
//then convert to MemberDisplay
return AutoMapperExtensions.MapWithUmbracoContext<IMember, MemberDisplay>(member, context.GetUmbracoContext());
}
}
}
}
}
@@ -0,0 +1,32 @@
using System.Web.Mvc;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Web.Trees;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Gets the tree node url for the IMember
/// </summary>
internal class MemberTreeNodeUrlResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
return source.New(ResolveCore(source, (IMember)source.Value), typeof(string));
}
private string ResolveCore(ResolutionResult res, IMember source)
{
var umbCtx = res.Context.GetUmbracoContext();
//map the tree node url
if (umbCtx != null)
{
var urlHelper = new UrlHelper(umbCtx.HttpContext.Request.RequestContext);
var url = urlHelper.GetUmbracoApiService<MemberTreeController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
return url;
}
return null;
}
}
}
@@ -13,9 +13,9 @@ namespace Umbraco.Web.Models.Mapping
{
internal class PreValueDisplayResolver : ValueResolver<IDataTypeDefinition, IEnumerable<PreValueFieldDisplay>>
{
private readonly Lazy<IDataTypeService> _dataTypeService;
private readonly IDataTypeService _dataTypeService;
public PreValueDisplayResolver(Lazy<IDataTypeService> dataTypeService)
public PreValueDisplayResolver(IDataTypeService dataTypeService)
{
_dataTypeService = dataTypeService;
}
@@ -55,7 +55,7 @@ namespace Umbraco.Web.Models.Mapping
}
//set up the defaults
var dataTypeService = _dataTypeService.Value;
var dataTypeService = _dataTypeService;
var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id);
IDictionary<string, object> dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value);
var result = Enumerable.Empty<PreValueFieldDisplay>().ToArray();
@@ -77,4 +77,4 @@ namespace Umbraco.Web.Models.Mapping
return Convert(source);
}
}
}
}
@@ -16,7 +16,8 @@ namespace Umbraco.Web.Models.Mapping
/// <summary>
/// Creates the tabs collection with properties assigned for display models
/// </summary>
internal class TabsAndPropertiesResolver : ValueResolver<IContentBase, IEnumerable<Tab<ContentPropertyDisplay>>>
internal class TabsAndPropertiesResolver<TSource> : IValueResolver
where TSource : IContentBase
{
private readonly ILocalizedTextService _localizedTextService;
protected IEnumerable<string> IgnoreProperties { get; set; }
@@ -36,59 +37,23 @@ namespace Umbraco.Web.Models.Mapping
}
/// <summary>
/// Maps properties on to the generic properties tab
/// Implements the <see cref="IValueResolver"/>
/// </summary>
/// <param name="content"></param>
/// <param name="display"></param>
/// <param name="localizedTextService"></param>
/// <param name="customProperties">
/// Any additional custom properties to assign to the generic properties tab.
/// </param>
/// <param name="onGenericPropertiesMapped"></param>
/// <remarks>
/// The generic properties tab is mapped during AfterMap and is responsible for
/// setting up the properties such as Created date, updated date, template selected, etc...
/// </remarks>
public static void MapGenericProperties<TPersisted>(
TPersisted content,
ContentItemDisplayBase<ContentPropertyDisplay, TPersisted> display,
ILocalizedTextService localizedTextService,
IEnumerable<ContentPropertyDisplay> customProperties = null,
Action<List<ContentPropertyDisplay>> onGenericPropertiesMapped = null)
where TPersisted : IContentBase
/// <param name="source"></param>
/// <returns></returns>
public ResolutionResult Resolve(ResolutionResult source)
{
var genericProps = display.Tabs.Single(x => x.Id == 0);
//store the current props to append to the newly inserted ones
var currProps = genericProps.Properties.ToArray();
var contentProps = new List<ContentPropertyDisplay>();
if (customProperties != null)
{
//add the custom ones
contentProps.AddRange(customProperties);
}
//now add the user props
contentProps.AddRange(currProps);
//callback
if (onGenericPropertiesMapped != null)
{
onGenericPropertiesMapped(contentProps);
}
//re-assign
genericProps.Properties = contentProps;
//Show or hide properties tab based on wether it has or not any properties
if (genericProps.Properties.Any() == false)
{
display.Tabs = display.Tabs.Where(x => x.Id != 0);
}
if (source.Value != null && (source.Value is TSource) == false)
throw new AutoMapperMappingException(string.Format("Value supplied is of type {0} but expected {1}.\nChange the value resolver source type, or redirect the source value supplied to the value resolver using FromMember.", new object[]
{
source.Value.GetType(),
typeof (TSource)
}));
return source.New(
//perform the mapping with the current umbraco context
ResolveCore(source.Context.GetUmbracoContext(), (TSource)source.Value), typeof(List<Tab<ContentPropertyDisplay>>));
}
/// <summary>
/// Adds the container (listview) tab to the document
/// </summary>
@@ -199,7 +164,13 @@ namespace Umbraco.Web.Models.Mapping
display.Tabs = tabs;
}
protected override IEnumerable<Tab<ContentPropertyDisplay>> ResolveCore(IContentBase content)
/// <summary>
/// Create the list of tabs for the <see cref="IContentBase"/>
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content">Source value</param>
/// <returns>Destination</returns>
protected virtual List<Tab<ContentPropertyDisplay>> ResolveCore(UmbracoContext umbracoContext, TSource content)
{
var tabs = new List<Tab<ContentPropertyDisplay>>();
@@ -224,10 +195,8 @@ namespace Umbraco.Web.Models.Mapping
if (properties.Count == 0)
continue;
// Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties.
var mappedProperties = Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyDisplay>>(properties.OrderBy(prop => prop.PropertyType.SortOrder));
TranslateProperties(mappedProperties);
//map the properties
var mappedProperties = MapProperties(umbracoContext, content, properties);
// add the tab
// we need to pick an identifier... there is no "right" way...
@@ -245,12 +214,41 @@ namespace Umbraco.Web.Models.Mapping
});
}
MapGenericProperties(umbracoContext, content, tabs);
// activate the first tab
tabs[0].IsActive = true;
return tabs;
}
/// <summary>
/// Returns a collection of custom generic properties that exist on the generic properties tab
/// </summary>
/// <returns></returns>
protected virtual IEnumerable<ContentPropertyDisplay> GetCustomGenericProperties(IContentBase content)
{
return Enumerable.Empty<ContentPropertyDisplay>();
}
/// <summary>
/// Maps properties on to the generic properties tab
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="tabs"></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)
{
// add the generic properties tab, for properties that don't belong to a tab
// get the properties, map and translate them, then add the tab
var noGroupProperties = content.GetNonGroupedProperties()
.Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored
var genericproperties = Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyDisplay>>(noGroupProperties).ToList();
TranslateProperties(genericproperties);
.Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored
.ToList();
var genericproperties = MapProperties(umbracoContext, content, noGroupProperties);
tabs.Add(new Tab<ContentPropertyDisplay>
{
@@ -260,20 +258,55 @@ namespace Umbraco.Web.Models.Mapping
Properties = genericproperties
});
// activate the first tab
tabs.First().IsActive = true;
var genericProps = tabs.Single(x => x.Id == 0);
return tabs;
}
//store the current props to append to the newly inserted ones
var currProps = genericProps.Properties.ToArray();
private void TranslateProperties(IEnumerable<ContentPropertyDisplay> properties)
{
// Not sure whether it's a good idea to add this to the ContentPropertyDisplay mapper
foreach (var prop in properties)
var contentProps = new List<ContentPropertyDisplay>();
var customProperties = GetCustomGenericProperties(content);
if (customProperties != null)
{
prop.Label = _localizedTextService.UmbracoDictionaryTranslate(prop.Label);
prop.Description = _localizedTextService.UmbracoDictionaryTranslate(prop.Description);
//add the custom ones
contentProps.AddRange(customProperties);
}
//now add the user props
contentProps.AddRange(currProps);
//re-assign
genericProps.Properties = contentProps;
//Show or hide properties tab based on wether it has or not any properties
if (genericProps.Properties.Any() == false)
{
//loop throug the tabs, remove the one with the id of zero and exit the loop
for (var i = 0; i < tabs.Count; i++)
{
if (tabs[i].Id != 0) continue;
tabs.RemoveAt(i);
break;
}
}
}
/// <summary>
/// Maps a list of <see cref="Property"/> to a list of <see cref="ContentPropertyDisplay"/>
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="properties"></param>
/// <returns></returns>
protected virtual List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties)
{
var result = 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))
.ToList();
return result;
}
}
}
@@ -47,7 +47,6 @@ using System.Security;
[assembly: InternalsVisibleTo("Umbraco.Forms.Core.Providers")]
[assembly: InternalsVisibleTo("Umbraco.Forms.Web")]
[assembly: InternalsVisibleTo("Umbraco.Headless")]
//allow custom unit-testing code to access internals through custom adapters
[assembly: InternalsVisibleTo("Umbraco.VisualStudio")] // backwards compat.
@@ -0,0 +1,27 @@
using System;
using System.Web;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Umbraco.Core;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Security;
using Umbraco.Web.Security.Identity;
namespace Umbraco.Web.Security
{
internal static class OwinExtensions
{
/// <summary>
/// Nasty little hack to get httpcontextbase from an owin context
/// </summary>
/// <param name="owinContext"></param>
/// <returns></returns>
internal static Attempt<HttpContextBase> TryGetHttpContext(this IOwinContext owinContext)
{
var ctx = owinContext.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
return ctx == null ? Attempt<HttpContextBase>.Fail() : Attempt.Succeed(ctx);
}
}
}
+3
View File
@@ -330,6 +330,9 @@
<Compile Include="Editors\CodeFileController.cs" />
<Compile Include="Editors\TourController.cs" />
<Compile Include="Models\BackOfficeTourFilter.cs" />
<Compile Include="Models\Mapping\AutoMapperExtensions.cs" />
<Compile Include="Models\Mapping\ContentTreeNodeUrlResolver.cs" />
<Compile Include="Models\Mapping\MemberTreeNodeUrlResolver.cs" />
<Compile Include="TourFilterResolver.cs" />
<Compile Include="Editors\UserEditorAuthorizationHelper.cs" />
<Compile Include="Editors\UserGroupAuthorizationAttribute.cs" />
+44 -3
View File
@@ -14,6 +14,7 @@ using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.WebApi.Filters;
using System.Linq;
using System.Net.Http;
using Umbraco.Core.Models.Membership;
using Umbraco.Web;
@@ -235,6 +236,14 @@ namespace Umbraco.Web.WebApi.Binders
return base.ValidatePropertyData(postedItem, actionContext);
}
/// <summary>
/// This ensures that the internal membership property types are removed from validation before processing the validation
/// since those properties are actually mapped to real properties of the IMember.
/// This also validates any posted data for fields that are sensitive.
/// </summary>
/// <param name="postedItem"></param>
/// <param name="actionContext"></param>
/// <returns></returns>
protected override bool ValidateProperties(ContentItemBasic<ContentPropertyBasic, IMember> postedItem, HttpActionContext actionContext)
{
var propertiesToValidate = postedItem.Properties.ToList();
@@ -245,9 +254,41 @@ namespace Umbraco.Web.WebApi.Binders
propertiesToValidate.RemoveAll(property => property.Alias == remove);
}
return ValidateProperties(propertiesToValidate.ToArray(), postedItem.PersistedContent.Properties.ToArray(), actionContext);
}
var httpCtx = actionContext.Request.TryGetHttpContext();
if (httpCtx.Success == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "No http context");
return false;
}
var umbCtx = httpCtx.Result.GetUmbracoContext();
//if the user doesn't have access to sensitive values, then we need to validate the incoming properties to check
//if a sensitive value is being submitted.
if (umbCtx.Security.CurrentUser.HasAccessToSensitiveData() == false)
{
var sensitiveProperties = postedItem.PersistedContent.ContentType
.PropertyTypes.Where(x => postedItem.PersistedContent.ContentType.IsSensitiveProperty(x.Alias))
.ToList();
foreach (var sensitiveProperty in sensitiveProperties)
{
var prop = propertiesToValidate.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias);
if (prop != null)
{
//this should not happen, this means that there was data posted for a sensitive property that
//the user doesn't have access to, which means that someone is trying to hack the values.
var message = string.Format("property with alias: {0} cannot be posted", prop.Alias);
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new InvalidOperationException(message));
return false;
}
}
}
return ValidateProperties(propertiesToValidate, postedItem.PersistedContent.Properties.ToList(), actionContext);
}
internal bool ValidateUniqueLogin(MemberSave contentItem, MembershipProvider membershipProvider, HttpActionContext actionContext)
{
if (contentItem == null) throw new ArgumentNullException("contentItem");
@@ -333,4 +374,4 @@ namespace Umbraco.Web.WebApi.Binders
}
}
}
}
}
@@ -74,7 +74,7 @@ namespace Umbraco.Web.WebApi.Filters
/// <returns></returns>
protected virtual bool ValidateProperties(ContentItemBasic<ContentPropertyBasic, TPersisted> postedItem, HttpActionContext actionContext)
{
return ValidateProperties(postedItem.Properties.ToArray(), postedItem.PersistedContent.Properties.ToArray(), actionContext);
return ValidateProperties(postedItem.Properties.ToList(), postedItem.PersistedContent.Properties.ToList(), actionContext);
}
/// <summary>
@@ -84,7 +84,7 @@ namespace Umbraco.Web.WebApi.Filters
/// <param name="persistedProperties"></param>
/// <param name="actionContext"></param>
/// <returns></returns>
protected bool ValidateProperties(ContentPropertyBasic[] postedProperties , Property[] persistedProperties, HttpActionContext actionContext)
protected bool ValidateProperties(List<ContentPropertyBasic> postedProperties , List<Property> persistedProperties, HttpActionContext actionContext)
{
foreach (var p in postedProperties)
{
@@ -125,8 +125,12 @@ namespace Umbraco.Web.WebApi.Filters
continue;
}
//get the posted value for this property
var postedValue = postedItem.Properties.Single(x => x.Alias == p.Alias).Value;
//get the posted value for this property, this may be null in cases where the property was marked as readonly which means
//the angular app will not post that value.
var postedProp = postedItem.Properties.FirstOrDefault(x => x.Alias == p.Alias);
if (postedProp == null) continue;
var postedValue = postedProp.Value;
//get the pre-values for this property
var preValues = p.PreValues;
@@ -180,4 +184,4 @@ namespace Umbraco.Web.WebApi.Filters
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2005
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI", "Umbraco.Web.UI\Umbraco.Web.UI.csproj", "{4C4C194C-B5E4-4991-8F87-4373E24CC19F}"
EndProject