From 3e67b3034d97df4db887058252edfb45f4104a8c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Feb 2014 12:56:00 +1100 Subject: [PATCH] Added the ability to specify explicit db type's for a property type using an overloaded ctor argument. This allows us to specify explicit db types for properties without having them get overwritten during the repository saving logic which will always reset it to be the db type of the underlying property editor. In the case of our built-in membership properties we do not want this to happen. Fixes some unit tests. Removes built-in props from being included in the property types on the profile model on the front-end. --- src/Umbraco.Core/Constants-Conventions.cs | 18 ++-- src/Umbraco.Core/DateTimeExtensions.cs | 25 +++++ src/Umbraco.Core/Models/Property.cs | 11 ++- src/Umbraco.Core/Models/PropertyType.cs | 26 +++++- .../Factories/MemberTypeReadOnlyFactory.cs | 92 +++++++++++-------- .../Factories/PropertyGroupFactory.cs | 41 +++++---- .../Repositories/ContentTypeBaseRepository.cs | 43 +++++---- .../Repositories/MemberTypeRepository.cs | 57 ++++++++---- .../Repositories/PermissionRepository.cs | 4 +- .../Repositories/MemberTypeRepositoryTest.cs | 4 +- .../Routing/RenderRouteHandlerTests.cs | 3 +- .../Services/MemberServiceTests.cs | 37 ++++++++ src/Umbraco.Web/Security/MembershipHelper.cs | 5 +- 13 files changed, 250 insertions(+), 116 deletions(-) diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 8ef9f1ca8f..f88b29c149 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -177,7 +177,7 @@ namespace Umbraco.Core { { Comments, - new PropertyType(new Guid(PropertyEditors.TextboxMultiple), DataTypeDatabaseType.Ntext) + new PropertyType(new Guid(PropertyEditors.TextboxMultiple), DataTypeDatabaseType.Ntext, true) { Alias = Comments, Name = CommentsLabel @@ -185,7 +185,7 @@ namespace Umbraco.Core }, { FailedPasswordAttempts, - new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Integer) + new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Integer, true) { Alias = FailedPasswordAttempts, Name = FailedPasswordAttemptsLabel @@ -193,7 +193,7 @@ namespace Umbraco.Core }, { IsApproved, - new PropertyType(new Guid(PropertyEditors.TrueFalse), DataTypeDatabaseType.Integer) + new PropertyType(new Guid(PropertyEditors.TrueFalse), DataTypeDatabaseType.Integer, true) { Alias = IsApproved, Name = IsApprovedLabel @@ -201,7 +201,7 @@ namespace Umbraco.Core }, { IsLockedOut, - new PropertyType(new Guid(PropertyEditors.TrueFalse), DataTypeDatabaseType.Integer) + new PropertyType(new Guid(PropertyEditors.TrueFalse), DataTypeDatabaseType.Integer, true) { Alias = IsLockedOut, Name = IsLockedOutLabel @@ -209,7 +209,7 @@ namespace Umbraco.Core }, { LastLockoutDate, - new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Date) + new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Date, true) { Alias = LastLockoutDate, Name = LastLockoutDateLabel @@ -217,7 +217,7 @@ namespace Umbraco.Core }, { LastLoginDate, - new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Date) + new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Date, true) { Alias = LastLoginDate, Name = LastLoginDateLabel @@ -225,7 +225,7 @@ namespace Umbraco.Core }, { LastPasswordChangeDate, - new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Date) + new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Date, true) { Alias = LastPasswordChangeDate, Name = LastPasswordChangeDateLabel @@ -233,7 +233,7 @@ namespace Umbraco.Core }, { PasswordAnswer, - new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Nvarchar) + new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Nvarchar, true) { Alias = PasswordAnswer, Name = PasswordAnswerLabel @@ -241,7 +241,7 @@ namespace Umbraco.Core }, { PasswordQuestion, - new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Nvarchar) + new PropertyType(new Guid(PropertyEditors.NoEdit), DataTypeDatabaseType.Nvarchar, true) { Alias = PasswordQuestion, Name = PasswordQuestionLabel diff --git a/src/Umbraco.Core/DateTimeExtensions.cs b/src/Umbraco.Core/DateTimeExtensions.cs index 74460814b3..1ff996170f 100644 --- a/src/Umbraco.Core/DateTimeExtensions.cs +++ b/src/Umbraco.Core/DateTimeExtensions.cs @@ -18,5 +18,30 @@ namespace Umbraco.Core return dt.ToString("yyyy-MM-dd HH:mm:ss"); } + public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo) + { + if (truncateTo == DateTruncate.Year) + return new DateTime(dt.Year, 0, 0); + if (truncateTo == DateTruncate.Month) + return new DateTime(dt.Year, dt.Month, 0); + if (truncateTo == DateTruncate.Day) + return new DateTime(dt.Year, dt.Month, dt.Day); + if (truncateTo == DateTruncate.Hour) + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0); + if (truncateTo == DateTruncate.Minute) + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0); + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); + } + + public enum DateTruncate + { + Year, + Month, + Day, + Hour, + Minute, + Second + } + } } diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index d6298d9da2..782d5f4bf4 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -43,7 +43,7 @@ namespace Umbraco.Core.Models private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); private static readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); - + /// /// Returns the Alias of the PropertyType, which this Property is based on /// @@ -59,9 +59,14 @@ namespace Umbraco.Core.Models /// /// Returns the DatabaseType that the underlaying DataType is using to store its values /// - /// Only used internally when saving the property value + /// + /// Only used internally when saving the property value. + /// [IgnoreDataMember] - internal DataTypeDatabaseType DataTypeDatabaseType { get { return _propertyType.DataTypeDatabaseType; } } + internal DataTypeDatabaseType DataTypeDatabaseType + { + get { return _propertyType.DataTypeDatabaseType; } + } /// /// Returns the PropertyType, which this Property is based on diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index cfeb9e832d..b312cc98f5 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -14,6 +14,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class PropertyType : Entity, IEquatable { + private readonly bool _isExplicitDbType; private string _name; private string _alias; private string _description; @@ -29,16 +30,28 @@ namespace Umbraco.Core.Models public PropertyType(IDataTypeDefinition dataTypeDefinition) { if(dataTypeDefinition.HasIdentity) - DataTypeDefinitionId = dataTypeDefinition.Id; + _dataTypeDefinitionId = dataTypeDefinition.Id; - DataTypeId = dataTypeDefinition.ControlId; - DataTypeDatabaseType = dataTypeDefinition.DatabaseType; + _dataTypeId = dataTypeDefinition.ControlId; + _dataTypeDatabaseType = dataTypeDefinition.DatabaseType; } internal PropertyType(Guid dataTypeControlId, DataTypeDatabaseType dataTypeDatabaseType) + : this(dataTypeControlId, dataTypeDatabaseType, false) { - DataTypeId = dataTypeControlId; - DataTypeDatabaseType = dataTypeDatabaseType; + } + + /// + /// Used internally to assign an explicity database type for this property type regardless of what the underlying data type/property editor is. + /// + /// + /// + /// + internal PropertyType(Guid dataTypeControlId, DataTypeDatabaseType dataTypeDatabaseType, bool isExplicitDbType) + { + _isExplicitDbType = isExplicitDbType; + _dataTypeId = dataTypeControlId; + _dataTypeDatabaseType = dataTypeDatabaseType; } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); @@ -149,6 +162,9 @@ namespace Umbraco.Core.Models get { return _dataTypeDatabaseType; } set { + //don't allow setting this if an explicit declaration has been made in the ctor + if (_isExplicitDbType) return; + SetPropertyValueAndDetectChanges(o => { _dataTypeDatabaseType = value; diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index e291916e10..b6b3425425 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -11,6 +11,8 @@ namespace Umbraco.Core.Persistence.Factories { public IMemberType BuildEntity(MemberTypeReadOnlyDto dto) { + var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + var memberType = new MemberType(dto.ParentId) { Alias = dto.Alias, @@ -32,13 +34,12 @@ namespace Umbraco.Core.Persistence.Factories AllowedContentTypes = Enumerable.Empty() }; - var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType); + var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes); memberType.PropertyGroups = propertyTypeGroupCollection; - var propertyTypes = GetPropertyTypes(dto, memberType); + var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes); - //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. - var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. foreach (var standardPropertyType in standardPropertyTypes) { if(dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; @@ -55,10 +56,10 @@ namespace Umbraco.Core.Persistence.Factories return memberType; } - private PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType) + private PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) { - var propertyGroups = new PropertyGroupCollection(); - var standardProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + var propertyGroups = new PropertyGroupCollection(); + foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue)) { var group = new PropertyGroup(); @@ -91,13 +92,19 @@ namespace Umbraco.Core.Persistence.Factories var tempGroupDto = groupDto; - var propertyType = new PropertyType(typeDto.ControlId, - //ensures that any built-in membership properties have their correct dbtype assigned no matter - //what the underlying data type is - MemberTypeRepository.GetDbTypeForBuiltInProperty( - typeDto.Alias, - typeDto.DbType.EnumParse(true), - standardProps).Result) + //ensures that any built-in membership properties have their correct dbtype assigned no matter + //what the underlying data type is + var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty( + typeDto.Alias, + typeDto.DbType.EnumParse(true), + standardProps); + + var propertyType = new PropertyType( + typeDto.ControlId, + propDbType.Result, + //This flag tells the property type that it has an explicit dbtype and that it cannot be changed + // which is what we want for the built-in properties. + propDbType.Success) { Alias = typeDto.Alias, DataTypeDefinitionId = typeDto.DataTypeId, @@ -126,34 +133,47 @@ namespace Umbraco.Core.Persistence.Factories return propertyGroups; } - - private List GetPropertyTypes(MemberTypeReadOnlyDto dto, MemberType memberType) + + private List GetPropertyTypes(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) { //Find PropertyTypes that does not belong to a PropertyTypeGroup var propertyTypes = new List(); - foreach (var propertyType in dto.PropertyTypes.Where(x => (x.PropertyTypeGroupId.HasValue == false || x.PropertyTypeGroupId.Value == 0) && x.Id.HasValue)) + foreach (var typeDto in dto.PropertyTypes.Where(x => (x.PropertyTypeGroupId.HasValue == false || x.PropertyTypeGroupId.Value == 0) && x.Id.HasValue)) { //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType - memberType.MemberTypePropertyTypes.Add(propertyType.Alias, - new MemberTypePropertyProfileAccess(propertyType.ViewOnProfile, propertyType.CanEdit)); - //PropertyType Collection - propertyTypes.Add(new PropertyType(propertyType.ControlId, - propertyType.DbType.EnumParse(true)) - { - Alias = propertyType.Alias, - DataTypeDefinitionId = propertyType.DataTypeId, - Description = propertyType.Description, - HelpText = propertyType.HelpText, - Id = propertyType.Id.Value, - Mandatory = propertyType.Mandatory, - Name = propertyType.Name, - SortOrder = propertyType.SortOrder, - ValidationRegExp = propertyType.ValidationRegExp, - PropertyGroupId = new Lazy(() => default(int)), - CreateDate = dto.CreateDate, - UpdateDate = dto.CreateDate - }); + memberType.MemberTypePropertyTypes.Add(typeDto.Alias, + new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit)); + + //ensures that any built-in membership properties have their correct dbtype assigned no matter + //what the underlying data type is + var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty( + typeDto.Alias, + typeDto.DbType.EnumParse(true), + standardProps); + + var propertyType = new PropertyType( + typeDto.ControlId, + propDbType.Result, + //This flag tells the property type that it has an explicit dbtype and that it cannot be changed + // which is what we want for the built-in properties. + propDbType.Success) + { + Alias = typeDto.Alias, + DataTypeDefinitionId = typeDto.DataTypeId, + Description = typeDto.Description, + HelpText = typeDto.HelpText, + Id = typeDto.Id.Value, + Mandatory = typeDto.Mandatory, + Name = typeDto.Name, + SortOrder = typeDto.SortOrder, + ValidationRegExp = typeDto.ValidationRegExp, + PropertyGroupId = new Lazy(() => default(int)), + CreateDate = dto.CreateDate, + UpdateDate = dto.CreateDate + }; + + propertyTypes.Add(propertyType); } return propertyTypes; } diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index 57cc79bc32..b3382df1e3 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -11,17 +11,21 @@ namespace Umbraco.Core.Persistence.Factories private readonly int _id; private readonly DateTime _createDate; private readonly DateTime _updateDate; + //a callback to create a property type which can be injected via a contructor + private readonly Func _propertyTypeCtor; public PropertyGroupFactory(int id) { _id = id; + _propertyTypeCtor = (guid, dbType, alias) => new PropertyType(guid, dbType); } - - public PropertyGroupFactory(int id, DateTime createDate, DateTime updateDate) + + public PropertyGroupFactory(int id, DateTime createDate, DateTime updateDate, Func propertyTypeCtor) { _id = id; _createDate = createDate; _updateDate = updateDate; + _propertyTypeCtor = propertyTypeCtor; } #region Implementation of IEntityFactory,IEnumerable> @@ -55,22 +59,23 @@ namespace Umbraco.Core.Persistence.Factories foreach (var typeDto in typeDtos) { var tempGroupDto = groupDto; - var propertyType = new PropertyType(typeDto.DataTypeDto.ControlId, - typeDto.DataTypeDto.DbType.EnumParse(true)) - { - Alias = typeDto.Alias, - DataTypeDefinitionId = typeDto.DataTypeId, - Description = typeDto.Description, - Id = typeDto.Id, - Name = typeDto.Name, - HelpText = typeDto.HelpText, - Mandatory = typeDto.Mandatory, - SortOrder = typeDto.SortOrder, - ValidationRegExp = typeDto.ValidationRegExp, - PropertyGroupId = new Lazy(() => tempGroupDto.Id), - CreateDate = _createDate, - UpdateDate = _updateDate - }; + var propertyType = _propertyTypeCtor(typeDto.DataTypeDto.ControlId, + typeDto.DataTypeDto.DbType.EnumParse(true), + typeDto.Alias); + + propertyType.Alias = typeDto.Alias; + propertyType.DataTypeDefinitionId = typeDto.DataTypeId; + propertyType.Description = typeDto.Description; + propertyType.Id = typeDto.Id; + propertyType.Name = typeDto.Name; + propertyType.HelpText = typeDto.HelpText; + propertyType.Mandatory = typeDto.Mandatory; + propertyType.SortOrder = typeDto.SortOrder; + propertyType.ValidationRegExp = typeDto.ValidationRegExp; + propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); + propertyType.CreateDate = _createDate; + propertyType.UpdateDate = _updateDate; + //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 propertyType.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 4d0e925461..5b761ad559 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -56,6 +56,11 @@ namespace Umbraco.Core.Persistence.Repositories } } + protected virtual PropertyType CreatePropertyType(Guid dataTypeId, DataTypeDatabaseType dbType, string propertyTypeAlias) + { + return new PropertyType(dataTypeId, dbType); + } + protected void PersistNewBaseContentType(ContentTypeDto dto, IContentTypeComposition entity) { //Logic for setting Path, Level and SortOrder @@ -344,7 +349,7 @@ namespace Umbraco.Core.Persistence.Repositories var dtos = Database.Fetch(new GroupPropertyTypeRelator().Map, sql); - var propertyGroupFactory = new PropertyGroupFactory(id, createDate, updateDate); + var propertyGroupFactory = new PropertyGroupFactory(id, createDate, updateDate, CreatePropertyType); var propertyGroups = propertyGroupFactory.BuildEntity(dtos); return new PropertyGroupCollection(propertyGroups); } @@ -361,25 +366,23 @@ namespace Umbraco.Core.Persistence.Repositories var dtos = Database.Fetch(sql); //TODO Move this to a PropertyTypeFactory - var list = (from dto in dtos - where (dto.PropertyTypeGroupId > 0) == false - select - new PropertyType(dto.DataTypeDto.ControlId, - dto.DataTypeDto.DbType.EnumParse(true)) - { - Alias = dto.Alias, - DataTypeDefinitionId = dto.DataTypeId, - Description = dto.Description, - Id = dto.Id, - Name = dto.Name, - HelpText = dto.HelpText, - Mandatory = dto.Mandatory, - SortOrder = dto.SortOrder, - ValidationRegExp = dto.ValidationRegExp, - CreateDate = createDate, - UpdateDate = updateDate - }).ToList(); - + var list = new List(); + foreach (var dto in dtos.Where(x => (x.PropertyTypeGroupId > 0) == false)) + { + var propType = CreatePropertyType(dto.DataTypeDto.ControlId, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias); + propType.Alias = dto.Alias; + propType.DataTypeDefinitionId = dto.DataTypeId; + propType.Description = dto.Description; + propType.Id = dto.Id; + propType.Name = dto.Name; + propType.HelpText = dto.HelpText; + propType.Mandatory = dto.Mandatory; + propType.SortOrder = dto.SortOrder; + propType.ValidationRegExp = dto.ValidationRegExp; + propType.CreateDate = createDate; + propType.UpdateDate = updateDate; + list.Add(propType); + } //Reset dirty properties Parallel.ForEach(list, currentFile => currentFile.ResetDirtyProperties(false)); diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 0fa8811d26..9875943f33 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -18,17 +18,17 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class MemberTypeRepository : ContentTypeBaseRepository, IMemberTypeRepository { - public MemberTypeRepository(IDatabaseUnitOfWork work) + public MemberTypeRepository(IDatabaseUnitOfWork work) : base(work) { } - public MemberTypeRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) + public MemberTypeRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) { } - #region Overrides of RepositoryBase + #region Overrides of RepositoryBase protected override IMemberType PerformGet(int id) { @@ -40,13 +40,13 @@ namespace Umbraco.Core.Persistence.Repositories Database.Fetch( new PropertyTypePropertyGroupRelator().Map, sql); - if (dtos == null || dtos.Any() == false) - return null; + if (dtos == null || dtos.Any() == false) + return null; - var factory = new MemberTypeReadOnlyFactory(); - var member = factory.BuildEntity(dtos.First()); + var factory = new MemberTypeReadOnlyFactory(); + var member = factory.BuildEntity(dtos.First()); - return member; + return member; } protected override IEnumerable PerformGetAll(params int[] ids) @@ -99,11 +99,11 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", + sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.helpText", "cmsPropertyType.mandatory", "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", - "cmsDataType.controlId", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", + "cmsDataType.controlId", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", "cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.parentGroupId", "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId") .From() @@ -179,10 +179,10 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new MemberTypeFactory(NodeObjectTypeId); var dto = factory.BuildDto(entity); - EnsureCorrectDbTypeForBuiltInProperties(entity); + EnsureExplicitDataTypeForBuiltInProperties(entity); PersistNewBaseContentType(dto, entity); - + //Handles the MemberTypeDto (cmsMemberType table) var memberTypeDtos = factory.BuildMemberTypeDtos(entity); foreach (var memberTypeDto in memberTypeDtos) @@ -216,12 +216,12 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new MemberTypeFactory(NodeObjectTypeId); var dto = factory.BuildDto(entity); - EnsureCorrectDbTypeForBuiltInProperties(entity); + EnsureExplicitDataTypeForBuiltInProperties(entity); PersistUpdatedBaseContentType(dto, entity); //Remove existing entries before inserting new ones - Database.Delete("WHERE NodeId = @Id", new {Id = entity.Id}); + Database.Delete("WHERE NodeId = @Id", new { Id = entity.Id }); //Handles the MemberTypeDto (cmsMemberType table) var memberTypeDtos = factory.BuildMemberTypeDtos(entity); @@ -236,11 +236,29 @@ namespace Umbraco.Core.Persistence.Repositories #endregion /// - /// Ensure that all the built-in membership provider properties have their correct db types - /// and property editors assigned. + /// Override so we can specify explicit db type's on any property types that are built-in. + /// + /// + /// + /// + /// + protected override PropertyType CreatePropertyType(Guid dataTypeId, DataTypeDatabaseType dbType, string propertyTypeAlias) + { + //custom property type constructor logic to set explicit dbtype's for built in properties + var stdProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + var propDbType = GetDbTypeForBuiltInProperty(propertyTypeAlias, dbType, stdProps); + return new PropertyType(dataTypeId, propDbType.Result, + //This flag tells the property type that it has an explicit dbtype and that it cannot be changed + // which is what we want for the built-in properties. + propDbType.Success); + } + + /// + /// Ensure that all the built-in membership provider properties have their correct data type + /// and property editors assigned. This occurs prior to saving so that the correct values are persisted. /// /// - private static void EnsureCorrectDbTypeForBuiltInProperties(IContentTypeBase memberType) + private static void EnsureExplicitDataTypeForBuiltInProperties(IContentTypeBase memberType) { var stdProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); foreach (var propertyType in memberType.PropertyTypes) @@ -248,7 +266,6 @@ namespace Umbraco.Core.Persistence.Repositories var dbTypeAttempt = GetDbTypeForBuiltInProperty(propertyType.Alias, propertyType.DataTypeDatabaseType, stdProps); if (dbTypeAttempt) { - propertyType.DataTypeDatabaseType = dbTypeAttempt.Result; //this reset's it's current data type reference which will be re-assigned based on the property editor assigned on the next line propertyType.DataTypeDefinitionId = 0; propertyType.DataTypeId = GetPropertyEditorForBuiltInProperty(propertyType.Alias, propertyType.DataTypeId, stdProps).Result; @@ -281,7 +298,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Successful attempt if it was a built in property /// internal static Attempt GetDbTypeForBuiltInProperty( - string propAlias, + string propAlias, DataTypeDatabaseType dbType, Dictionary standardProps) { @@ -307,7 +324,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Successful attempt if it was a built in property /// internal static Attempt GetPropertyEditorForBuiltInProperty( - string propAlias, + string propAlias, Guid propertyEditor, Dictionary standardProps) { diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index fd96dc7a54..f0a8378fc9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -208,7 +208,9 @@ namespace Umbraco.Core.Persistence.Repositories UserId = p.Item1 }).ToArray(); - _unitOfWork.Database.BulkInsertRecords(actions); + _unitOfWork.Database.BulkInsertRecords(actions, trans); + + trans.Complete(); //Raise the event AssignedPermissions.RaiseEvent( diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index 52dbbb600f..63e9956f11 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -61,7 +61,7 @@ namespace Umbraco.Tests.Persistence.Repositories var standardProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); Assert.That(sut, Is.Not.Null); - Assert.That(sut.PropertyGroups.Count(), Is.EqualTo(1)); + Assert.That(sut.PropertyGroups.Count(), Is.EqualTo(2)); Assert.That(sut.PropertyTypes.Count(), Is.EqualTo(3 + standardProps.Count)); Assert.That(sut.PropertyGroups.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); @@ -149,7 +149,7 @@ namespace Umbraco.Tests.Persistence.Repositories memberType = repository.Get(memberType.Id); Assert.That(memberType.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); - Assert.That(memberType.PropertyGroups.Count(), Is.EqualTo(1)); + Assert.That(memberType.PropertyGroups.Count(), Is.EqualTo(2)); } } diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 4b13acf7ee..794c9b0401 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -79,7 +79,8 @@ namespace Umbraco.Tests.Routing var docRequest = new PublishedContentRequest(routingContext.UmbracoContext.CleanedUmbracoUrl, routingContext) { PublishedContent = routingContext.UmbracoContext.ContentCache.GetById(1174), - TemplateModel = template + TemplateModel = template, + RenderingEngine = RenderingEngine.Mvc }; var handler = new RenderRouteHandler(new TestControllerFactory(), routingContext.UmbracoContext); diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index f67dd55abe..380f050224 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -5,6 +5,7 @@ using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Entities; @@ -924,5 +925,41 @@ namespace Umbraco.Tests.Services Assert.IsTrue(found.Comments.IsNullOrWhiteSpace()); } + /// + /// Because we are forcing some of the built-ins to be Labels which have an underlying db type as nvarchar but we need + /// to ensure that the dates/int get saved to the correct column anyways. + /// + [Test] + public void Setting_DateTime_Property_On_Built_In_Member_Property_Saves_To_Correct_Column() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "test", "test"); + var date = DateTime.Now; + member.LastLoginDate = DateTime.Now; + ServiceContext.MemberService.Save(member); + + var result = ServiceContext.MemberService.GetById(member.Id); + Assert.AreEqual( + date.TruncateTo(DateTimeExtensions.DateTruncate.Second), + result.LastLoginDate.TruncateTo(DateTimeExtensions.DateTruncate.Second)); + + //now ensure the col is correct + var sql = new Sql().Select("cmsPropertyData.*") + .From() + .InnerJoin() + .On(dto => dto.PropertyTypeId, dto => dto.Id) + .Where(dto => dto.NodeId == member.Id) + .Where(dto => dto.Alias == Constants.Conventions.Member.LastLoginDate); + + var colResult = DatabaseContext.Database.Fetch(sql); + + Assert.AreEqual(1, colResult.Count); + Assert.IsTrue(colResult.First().Date.HasValue); + Assert.IsFalse(colResult.First().Integer.HasValue); + Assert.IsNull(colResult.First().Text); + Assert.IsNull(colResult.First().VarChar); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 899661ead6..78f7ab09a8 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -227,7 +227,10 @@ namespace Umbraco.Web.Security var memberType = member.ContentType; - foreach (var prop in memberType.PropertyTypes.Where(x => memberType.MemberCanEditProperty(x.Alias))) + var builtIns = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); + + foreach (var prop in memberType.PropertyTypes + .Where(x => builtIns.Contains(x.Alias) == false && memberType.MemberCanEditProperty(x.Alias))) { var value = string.Empty; var propValue = member.Properties[prop.Alias];