diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index c9f8c4fba2..d3ab992a23 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -17,7 +17,7 @@ - + diff --git a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs index 07c9a8ded2..1a4e7ae1a9 100644 --- a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs +++ b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Collections /// /// Initializes a new instance of the struct. /// - 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 /// /// Gets the first type. /// - public Type Type1 { get; } + public Type Type1 { get; private set; } /// /// Gets the second type. /// - public Type Type2 { get; } + public Type Type2 { get; private set; } /// public bool Equals(CompositeTypeTypeKey other) - => Type1 == other.Type1 && Type2 == other.Type2; + { + return Type1 == other.Type1 && Type2 == other.Type2; + } /// 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; + } /// public override int GetHashCode() diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 1e5da5f0b3..6d9435ed68 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -40,4 +40,4 @@ namespace Umbraco.Core.Configuration Current.Revision > 0 ? Current.Revision.ToInvariantString() : null); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 0ada375163..9aa7a907dd 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -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 } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IMemberType.cs b/src/Umbraco.Core/Models/IMemberType.cs index 878cc24334..9596d88cca 100644 --- a/src/Umbraco.Core/Models/IMemberType.cs +++ b/src/Umbraco.Core/Models/IMemberType.cs @@ -19,6 +19,13 @@ /// bool MemberCanViewProperty(string propertyTypeAlias); + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool IsSensitiveProperty(string propertyTypeAlias); + /// /// Sets a boolean indicating whether a Property is editable by the Member. /// @@ -32,5 +39,12 @@ /// PropertyType Alias of the Property to set /// Boolean value, true or false void SetMemberCanViewProperty(string propertyTypeAlias, bool value); + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetIsSensitiveProperty(string propertyTypeAlias, bool value); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 4f79e1d231..4e70b4157f 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -64,7 +64,7 @@ namespace Umbraco.Core.Models } /// - /// 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. /// [DataMember] internal IDictionary MemberTypePropertyTypes { get; private set; } @@ -76,11 +76,11 @@ namespace Umbraco.Core.Models /// 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 /// 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; + } + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + 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 /// Boolean value, true or false 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 /// Boolean value, true or false 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); + } + } + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + 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); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs index fa9e0b7307..db483191a0 100644 --- a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs +++ b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs @@ -5,13 +5,15 @@ namespace Umbraco.Core.Models /// 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; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs index c9432652af..861ff1383e 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs @@ -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; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs index d85baa2884..07c2bc421c 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs @@ -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; } + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs index f6bb09066d..48beab61c2 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs @@ -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; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index a65b307f24..5db36d16ed 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -261,6 +261,16 @@ namespace Umbraco.Core.Models return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); } + /// + /// Determines whether this user has access to view sensitive data + /// + /// + 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(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 1e3c75ee6d..ccd379f7d2 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -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.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.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2)); + decimal value2; + return Attempt.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.Succeed(value); } // Same as int var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2)); + decimal value2; + return Attempt.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.Succeed(value); } @@ -305,42 +313,53 @@ namespace Umbraco.Core switch (Type.GetTypeCode(target)) { case TypeCode.Int16: - return Attempt.SucceedIf(short.TryParse(input, out var value), value); + short value; + return Attempt.SucceedIf(short.TryParse(input, out value), value); case TypeCode.Double: var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(double.TryParse(input2, out var valueD), valueD); + double valueD; + return Attempt.SucceedIf(double.TryParse(input2, out valueD), valueD); case TypeCode.Single: var input3 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(float.TryParse(input3, out var valueF), valueF); + float valueF; + return Attempt.SucceedIf(float.TryParse(input3, out valueF), valueF); case TypeCode.Char: - return Attempt.SucceedIf(char.TryParse(input, out var valueC), valueC); + char valueC; + return Attempt.SucceedIf(char.TryParse(input, out valueC), valueC); case TypeCode.Byte: - return Attempt.SucceedIf(byte.TryParse(input, out var valueB), valueB); + byte valueB; + return Attempt.SucceedIf(byte.TryParse(input, out valueB), valueB); case TypeCode.SByte: - return Attempt.SucceedIf(sbyte.TryParse(input, out var valueSb), valueSb); + sbyte valueSb; + return Attempt.SucceedIf(sbyte.TryParse(input, out valueSb), valueSb); case TypeCode.UInt32: - return Attempt.SucceedIf(uint.TryParse(input, out var valueU), valueU); + uint valueU; + return Attempt.SucceedIf(uint.TryParse(input, out valueU), valueU); case TypeCode.UInt16: - return Attempt.SucceedIf(ushort.TryParse(input, out var valueUs), valueUs); + ushort valueUs; + return Attempt.SucceedIf(ushort.TryParse(input, out valueUs), valueUs); case TypeCode.UInt64: - return Attempt.SucceedIf(ulong.TryParse(input, out var valueUl), valueUl); + ulong valueUl; + return Attempt.SucceedIf(ulong.TryParse(input, out valueUl), valueUl); } } else if (target == typeof(Guid)) { - return Attempt.SucceedIf(Guid.TryParse(input, out var value), value); + Guid value; + return Attempt.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.SucceedIf(DateTimeOffset.TryParse(input, out var value), value); + DateTimeOffset value; + return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out value), value); } else if (target == typeof(TimeSpan)) { - return Attempt.SucceedIf(TimeSpan.TryParse(input, out var value), value); + TimeSpan value; + return Attempt.SucceedIf(TimeSpan.TryParse(input, out value), value); } else if (target == typeof(decimal)) { var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(decimal.TryParse(input2, out var value), value); + decimal value; + return Attempt.SucceedIf(decimal.TryParse(input2, out value), value); } else if (input != null && target == typeof(Version)) { - return Attempt.SucceedIf(Version.TryParse(input, out var value), value); + Version value; + return Attempt.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; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 3672f80873..4186d00774 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -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 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index ba7a47165b..439ac68360 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -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 } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 1f94d30b0f..45dbeb72bc 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -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); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index cd2426357d..c649ec3f58 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -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(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/AddIsSensitiveMemberTypeColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/AddIsSensitiveMemberTypeColumn.cs new file mode 100644 index 0000000000..97d6444467 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/AddIsSensitiveMemberTypeColumn.cs @@ -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() + { + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/CreateSensitiveDataUserGroup.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/CreateSensitiveDataUserGroup.cs new file mode 100644 index 0000000000..857830a46d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenNineZero/CreateSensitiveDataUserGroup.cs @@ -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("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() + { + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 5865878aa9..d473e19485 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -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.Fail(propertyEditor); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs index 2c603a40ed..c4e57c5122 100644 --- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/PublishingStrategy.cs @@ -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( string.Format("Content '{0}' with Id '{1}' has been unpublished.", diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 57bd816576..b46781f76d 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -884,8 +884,8 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Published && x.ExpireDate <= DateTime.Now); - return repository.GetByQuery(query); + var query = Query.Builder.Where(x => x.ExpireDate <= DateTime.Now); + return repository.GetByQuery(query).Where(x => x.HasPublishedVersion); } } diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index 5229ce7dec..57520a3754 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -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('/'); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 423166be56..c12097a22e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -560,6 +560,8 @@ + + diff --git a/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs index 6cb39b7235..4e8476bb6d 100644 --- a/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.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; } diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 1ff3447539..a976c8ad8c 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -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, diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index e29aebc54f..f06b66a93d 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -47,6 +47,10 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var mockedContentService = Mock.Of(); var mockedMediaService = Mock.Of(); var mockedEntityService = Mock.Of(); + var mockedMemberService = Mock.Of(); + var mockedMemberTypeService = Mock.Of(); + var mockedDataTypeService = Mock.Of(); + var mockedContentTypeService = Mock.Of(); var mockedMigrationService = new Mock(); //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(), sectionService:Mock.Of()); @@ -156,4 +164,4 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting protected abstract ApiController CreateController(Type controllerType, HttpRequestMessage msg, UmbracoHelper helper); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg index 7ee2c48330..d7800af062 100644 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg and b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js index edc3a0a560..6dc066b282 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js @@ -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 } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index caa79439be..c0be05addf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -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 diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js index a1cb579433..28ac7f6485 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js @@ -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); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index f09e31924f..dddf99b84f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -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); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js index 57ddb6babd..fe1148d6a8 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js @@ -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; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index c05d33db04..eecbf51ec5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -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%; + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.controller.js index 78ae77b46c..5cb2a218d5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.controller.js @@ -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); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html index 50e5e29d1e..9df9c801b4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html @@ -1,6 +1,6 @@
-
+