From 0aa38728fc793485e3f6419dea3e4b2cad4049f7 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Tue, 27 Aug 2013 15:48:00 +0200 Subject: [PATCH 01/45] Updating the readonly dtos --- .../Models/Rdbms/MemberReadOnlyDto.cs | 4 +++ .../Models/Rdbms/PropertyDataReadOnlyDto.cs | 35 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs index 21c6ad7780..87692344ef 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs @@ -43,6 +43,10 @@ namespace Umbraco.Core.Models.Rdbms [Column("createDate")] public DateTime CreateDate { get; set; } + /* cmsContent */ + [Column("contentType")] + public int ContentTypeId { get; set; } + /* from cmsContentType joined with cmsContent */ [Column("ContentTypeAlias")] public string ContentTypeAlias { get; set; } diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs index c6f08f3605..138a66f3eb 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs @@ -12,9 +12,6 @@ namespace Umbraco.Core.Models.Rdbms [Column("id")] public int Id { get; set; } - [Column("contentNodeId")] - public int NodeId { get; set; } - [Column("VersionId")] public Guid? VersionId { get; set; } @@ -61,5 +58,37 @@ namespace Umbraco.Core.Models.Rdbms /* cmsDataType */ [Column("controlId")] public Guid ControlId { get; set; } + + [Column("dbType")] + public string DbType { get; set; } + + [Ignore] + public object GetValue + { + get + { + if (Integer.HasValue) + { + return Integer.Value; + } + + if (Date.HasValue) + { + return Date.Value; + } + + if (string.IsNullOrEmpty(VarChar) == false) + { + return VarChar; + } + + if (string.IsNullOrEmpty(Text) == false) + { + return Text; + } + + return null; + } + } } } \ No newline at end of file From 89fbad447bd069d4ba8cdfdff6926669d6d6ca13 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Tue, 27 Aug 2013 15:48:18 +0200 Subject: [PATCH 02/45] Adding propertytype alias conventions for Member --- src/Umbraco.Core/Constants-Conventions.cs | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 2d32cda9fc..df2e15284d 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -90,6 +90,57 @@ public const string Image = "Image"; } + /// + /// Constants for Umbraco Member property aliases. + /// + public static class Member + { + /// + /// Property alias for a Members Password Question + /// + public const string PasswordQuestion = "umbracoPasswordRetrievalQuestionPropertyTypeAlias"; + + /// + /// Property alias for Members Password Answer + /// + public const string PasswordAnswer = "umbracoPasswordRetrievalAnswerPropertyTypeAlias"; + + /// + /// Property alias for the Comments on a Member + /// + public const string Comments = "umbracoCommentPropertyTypeAlias"; + + /// + /// Property alias for the Approved boolean of a Member + /// + public const string IsApproved = "umbracoApprovePropertyTypeAlias"; + + /// + /// Property alias for the Locked out boolean of a Member + /// + public const string IsLockedOut = "umbracoLockPropertyTypeAlias"; + + /// + /// Property alias for the last date the Member logged in + /// + public const string LastLoginDate = "umbracoLastLoginPropertyTypeAlias"; + + /// + /// Property alias for the last date a Member changed its password + /// + public const string LastPasswordChangeDate = "umbracoMemberLastPasswordChange"; + + /// + /// Property alias for the last date a Member was locked out + /// + public const string LastLockoutDate = "umbracoMemberLastLockout"; + + /// + /// Property alias for the number of failed login attemps + /// + public const string FailedPasswordAttempts = "umbracoFailedPasswordAttemptsPropertyTypeAlias"; + } + /// /// Constants for Umbraco URLs/Querystrings. /// From c3c9c100601b4526bcaa621038f4a2c2a275daa2 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Tue, 27 Aug 2013 15:48:58 +0200 Subject: [PATCH 03/45] Implementing a factory for readonly Member objects --- .../Factories/MemberReadOnlyFactory.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs diff --git a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs new file mode 100644 index 0000000000..021f67d9f0 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class MemberReadOnlyFactory : IEntityFactory + { + public IMembershipUser BuildEntity(MemberReadOnlyDto dto) + { + var member = new Member + { + Id = dto.NodeId, + CreateDate = dto.CreateDate, + UpdateDate = dto.UpdateDate, + Name = dto.Text, + Email = dto.Email, + Username = dto.LoginName, + Password = dto.Password, + ProviderUserKey = dto.UniqueId + }; + + //Create PropertyType + //Use PropertyType to create Property + var properties = new List(); + foreach (var property in dto.Properties) + { + var propertyType = new PropertyType(property.ControlId, + property.DbType.EnumParse(true)) + { + Id = property.PropertyTypeId, + Alias = property.Alias, + Name = property.Name, + Description = property.Description, + HelpText = property.HelpText, + Mandatory = property.Mandatory, + ValidationRegExp = property.ValidationRegExp + //SortOrder + //PropertyGroupId + }; + + propertyType.ResetDirtyProperties(false); + var prop = propertyType.CreatePropertyFromRawValue(property.GetValue, property.VersionId.Value, property.Id); + properties.Add(prop); + } + + member.Properties = new PropertyCollection(properties); + + member.SetProviderUserKeyType(typeof(Guid)); + member.ResetDirtyProperties(false); + return member; + } + + + public MemberReadOnlyDto BuildDto(IMembershipUser entity) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file From 342232f21e2988245538f30529a8dcb495e673ad Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Tue, 27 Aug 2013 15:49:46 +0200 Subject: [PATCH 04/45] Updating the Member class and adding an UmbracoMember wrapper as a bridge between MembershipUser and Member --- src/Umbraco.Core/Models/Membership/Member.cs | 248 +++++++++++++++++- .../Models/Membership/UmbracoMember.cs | 20 ++ 2 files changed, 259 insertions(+), 9 deletions(-) create mode 100644 src/Umbraco.Core/Models/Membership/UmbracoMember.cs diff --git a/src/Umbraco.Core/Models/Membership/Member.cs b/src/Umbraco.Core/Models/Membership/Member.cs index 3fa26af085..5c641a9be2 100644 --- a/src/Umbraco.Core/Models/Membership/Member.cs +++ b/src/Umbraco.Core/Models/Membership/Member.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; using System.Reflection; using System.Runtime.Serialization; @@ -23,12 +24,19 @@ namespace Umbraco.Core.Models.Membership private Guid _key; private DateTime _createDate; private DateTime _updateDate; + private PropertyCollection _properties; private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); private static readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); private static readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); + private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + + protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(PropertyCollectionSelector); + } /// /// Integer Id @@ -113,6 +121,7 @@ namespace Umbraco.Core.Models.Membership /// /// Indicates whether the current entity has an identity, eg. Id. /// + [IgnoreDataMember] public virtual bool HasIdentity { get @@ -129,23 +138,244 @@ namespace Umbraco.Core.Models.Membership } } + /// + /// Gets or sets the Username + /// + [DataMember] public string Username { get; set; } + + /// + /// Gets or sets the Email + /// + [DataMember] public string Email { get; set; } + /// + /// Gets or sets the Password + /// + [DataMember] public string Password { get; set; } - public string PasswordQuestion { get; set; } - public string PasswordAnswer { get; set; } - public string Comments { get; set; } - public bool IsApproved { get; set; } - public bool IsOnline { get; set; } - public bool IsLockedOut { get; set; } - public DateTime LastLoginDate { get; set; } - public DateTime LastPasswordChangeDate { get; set; } - public DateTime LastLockoutDate { get; set; } + + /// + /// Gets or sets the Password Question + /// + /// + /// Alias: umbracoPasswordRetrievalQuestionPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public string PasswordQuestion + { + get + { + return Properties[Constants.Conventions.Member.PasswordQuestion].Value.ToString(); + } + set + { + Properties[Constants.Conventions.Member.PasswordQuestion].Value = value; + } + } + /// + /// Gets or sets the Password Answer + /// + /// + /// Alias: umbracoPasswordRetrievalAnswerPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public string PasswordAnswer + { + get + { + return Properties[Constants.Conventions.Member.PasswordAnswer].Value.ToString(); + } + set + { + Properties[Constants.Conventions.Member.PasswordAnswer].Value = value; + } + } + + /// + /// Gets or set the comments for the member + /// + /// + /// Alias: umbracoCommentPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public string Comments + { + get + { + return Properties[Constants.Conventions.Member.Comments].Value.ToString(); + } + set + { + Properties[Constants.Conventions.Member.Comments].Value = value; + } + } + + /// + /// Gets or sets a boolean indicating whether the Member is approved + /// + /// + /// Alias: umbracoApprovePropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public bool IsApproved + { + get + { + if (Properties[Constants.Conventions.Member.IsApproved].Value is bool) + return (bool)Properties[Constants.Conventions.Member.IsApproved].Value; + + return (bool)Convert.ChangeType(Properties[Constants.Conventions.Member.IsApproved].Value, typeof(bool)); + } + set + { + Properties[Constants.Conventions.Member.IsApproved].Value = value; + } + } + + /// + /// Gets or sets a boolean indicating whether the Member is locked out + /// + /// + /// Alias: umbracoLockPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public bool IsLockedOut + { + get + { + if (Properties[Constants.Conventions.Member.IsLockedOut].Value is bool) + return (bool)Properties[Constants.Conventions.Member.IsLockedOut].Value; + + return (bool)Convert.ChangeType(Properties[Constants.Conventions.Member.IsLockedOut].Value, typeof(bool)); + } + set + { + Properties[Constants.Conventions.Member.IsLockedOut].Value = value; + } + } + + /// + /// Gets or sets the date for last login + /// + /// + /// Alias: umbracoLastLoginPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public DateTime LastLoginDate + { + get + { + if (Properties[Constants.Conventions.Member.LastLoginDate].Value is DateTime) + return (DateTime)Properties[Constants.Conventions.Member.LastLoginDate].Value; + + return (DateTime)Convert.ChangeType(Properties[Constants.Conventions.Member.LastLoginDate].Value, typeof(DateTime)); + } + set + { + Properties[Constants.Conventions.Member.LastLoginDate].Value = value; + } + } + + /// + /// Gest or sets the date for last password change + /// + /// + /// Alias: umbracoMemberLastPasswordChange + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public DateTime LastPasswordChangeDate + { + get + { + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value is DateTime) + return (DateTime)Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value; + + return (DateTime)Convert.ChangeType(Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value, typeof(DateTime)); + } + set + { + Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value = value; + } + } + + /// + /// Gets or sets the date for when Member was locked out + /// + /// + /// Alias: umbracoMemberLastLockout + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public DateTime LastLockoutDate + { + get + { + if (Properties[Constants.Conventions.Member.LastLockoutDate].Value is DateTime) + return (DateTime)Properties[Constants.Conventions.Member.LastLockoutDate].Value; + + return (DateTime)Convert.ChangeType(Properties[Constants.Conventions.Member.LastLockoutDate].Value, typeof(DateTime)); + } + set + { + Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; + } + } + + /// + /// Gets or sets the number of failed password attempts. + /// This is the number of times the password was entered incorrectly upon login. + /// + /// + /// Alias: umbracoFailedPasswordAttemptsPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public int FailedPasswordAttempts + { + get + { + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value is int) + return (int)Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value; + + return (int)Convert.ChangeType(Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value, typeof(int)); + } + set + { + Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; + } + } + + /// + /// Boolean indicating whether the user is online + /// + /// Context dependent property + [IgnoreDataMember] + public bool IsOnline { get; set; } + public object ProfileId { get; set; } public IEnumerable Groups { get; set; } + [DataMember] + public PropertyCollection Properties + { + get { return _properties; } + set + { + _properties = value; + _properties.CollectionChanged += PropertiesChanged; + } + } + #region Internal methods internal virtual void ResetIdentity() diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMember.cs b/src/Umbraco.Core/Models/Membership/UmbracoMember.cs new file mode 100644 index 0000000000..b890276432 --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/UmbracoMember.cs @@ -0,0 +1,20 @@ +using System.Web.Security; + +namespace Umbraco.Core.Models.Membership +{ + internal class UmbracoMember : MembershipUser + { + private readonly IMembershipUser _member; + + public UmbracoMember(IMembershipUser member) + { + _member = member; + } + + public override string Email + { + get { return _member.Email; } + set { _member.Email = value; } + } + } +} \ No newline at end of file From a3f41af3ba149a861a1de555e19b3207db965b4b Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Tue, 27 Aug 2013 15:50:17 +0200 Subject: [PATCH 05/45] Updating the sql query in the MemberRepository --- .../Persistence/Repositories/MemberRepository.cs | 5 +++-- src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 2b4ca4e92c..fc0bdaac95 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -23,6 +23,7 @@ namespace Umbraco.Core.Persistence.Repositories /* The following methods might be relevant for this specific repository (in an optimized form) * GetById - get a member by its integer Id * GetByKey - get a member by its unique guid Id (which should correspond to a membership provider user's id) + * GetByUsername - get a member by its username * GetByPropertyValue - get members with a certain property value (supply both property alias and value?) * GetByMemberTypeAlias - get all members of a certain type * GetByMemberGroup - get all members in a specific group @@ -65,14 +66,14 @@ namespace Umbraco.Core.Persistence.Repositories { //TODO Count var sql = new Sql(); - sql.Select("umbracoNode.*", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", + sql.Select("umbracoNode.*", "cmsContent.contentType", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", "cmsContentVersion.VersionDate", "cmsContentVersion.LanguageLocale", "cmsMember.Email", "cmsMember.LoginName", "cmsMember.Password", "cmsPropertyData.id", "cmsPropertyData.dataDate", "cmsPropertyData.dataInt", "cmsPropertyData.dataNtext", "cmsPropertyData.dataNvarchar", "cmsPropertyData.propertytypeid", "cmsPropertyType.Alias", "cmsPropertyType.Description", "cmsPropertyType.Name", "cmsPropertyType.mandatory", "cmsPropertyType.validationRegExp", "cmsPropertyType.helpText", "cmsPropertyType.propertyTypeGroupId", "cmsPropertyType.dataTypeId", - "cmsDataType.controlId") + "cmsDataType.controlId", "cmsDataType.dbType") .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7f07eeb1b7..32a07f80f8 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -200,6 +200,7 @@ + @@ -247,6 +248,7 @@ + From 1995b25092841a750a880e0402f8e4a221b5c5ac Mon Sep 17 00:00:00 2001 From: Darren Ferguson Date: Wed, 28 Aug 2013 12:48:04 +0100 Subject: [PATCH 06/45] Adding new Memership provider implementation Obsolete SecUtility class Add Security Helper class --- src/Umbraco.Core/Services/IMemberService.cs | 18 +- src/Umbraco.Core/Services/MemberService.cs | 70 +- src/Umbraco.Web/Helpers/SecurityHelper.cs | 88 ++ .../Providers/MembersMembershipProvider.cs | 768 ++++++++++++++++-- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/umbraco.providers/SecUtility.cs | 1 + 6 files changed, 883 insertions(+), 63 deletions(-) create mode 100644 src/Umbraco.Web/Helpers/SecurityHelper.cs diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 9c0f9a14cf..0216639cb3 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Services +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Services { /// /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. @@ -15,5 +17,17 @@ /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl. /// internal interface IMembershipMemberService : IService - {} + { + IMembershipUser CreateMember(string username, string email, string password, string memberType, int userId = 0); + + IMembershipUser GetByUsername(string login); + + IMembershipUser GetByEmail(string email); + + IMembershipUser GetById(object id); + + void Delete(IMembershipUser membershipUser); + + void Save(IMembershipUser membershipUser); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index a977069045..0d6a6e471b 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -1,5 +1,8 @@ -using Umbraco.Core.Persistence; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; +using System.Linq; namespace Umbraco.Core.Services { @@ -26,5 +29,70 @@ namespace Umbraco.Core.Services _repositoryFactory = repositoryFactory; _uowProvider = provider; } + + public IMembershipUser CreateMember(string username, string email, string password, string memberType, int userId = 0) + { + var uow = _uowProvider.GetUnitOfWork(); + + var member = new Member(); + + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) + { + member.Username = username; + member.Email = email; + member.Password = password; + + repository.AddOrUpdate(member); + uow.Commit(); + } + + return member; + } + + public IMembershipUser GetByUsername(string userName) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Username == userName); + var membershipUser = repository.GetByQuery(query).FirstOrDefault(); + + return membershipUser; + } + } + + public IMembershipUser GetByEmail(string email) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Email == email); + var membershipUser = repository.GetByQuery(query).FirstOrDefault(); + + return membershipUser; + } + } + + public IMembershipUser GetById(object id) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Id == id); + var membershipUser = repository.GetByQuery(query).FirstOrDefault(); + + return membershipUser; + } + } + + public void Delete(IMembershipUser membershipUser) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + repository.Delete(membershipUser); + } + } + + public void Save(IMembershipUser membershipUser) + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Helpers/SecurityHelper.cs b/src/Umbraco.Web/Helpers/SecurityHelper.cs new file mode 100644 index 0000000000..a731d516b2 --- /dev/null +++ b/src/Umbraco.Web/Helpers/SecurityHelper.cs @@ -0,0 +1,88 @@ +using System.Collections.Specialized; +using System.Configuration.Provider; +using System.Web.Hosting; + +namespace Umbraco.Web.Helpers +{ + internal class SecurityHelper + { + /// + /// Gets the boolean value. + /// + /// The config. + /// Name of the value. + /// if set to true [default value]. + /// + internal static bool GetBooleanValue(NameValueCollection config, string valueName, bool defaultValue) + { + bool flag; + var str = config[valueName]; + if (str == null) + return defaultValue; + + if (bool.TryParse(str, out flag) == false) + { + throw new ProviderException("Value must be boolean."); + } + return flag; + } + + /// + /// Gets the int value. + /// + /// The config. + /// Name of the value. + /// The default value. + /// if set to true [zero allowed]. + /// The max value allowed. + /// + internal static int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed) + { + int num; + var s = config[valueName]; + if (s == null) + { + return defaultValue; + } + if (int.TryParse(s, out num) == false) + { + if (zeroAllowed) + { + throw new ProviderException("Value must be non negative integer"); + } + throw new ProviderException("Value must be positive integer"); + } + if (zeroAllowed && (num < 0)) + { + throw new ProviderException("Value must be non negativeinteger"); + } + if (zeroAllowed == false && (num <= 0)) + { + throw new ProviderException("Value must be positive integer"); + } + if ((maxValueAllowed > 0) && (num > maxValueAllowed)) + { + throw new ProviderException("Value too big"); + } + return num; + } + + + /// + /// Gets the name of the default app. + /// + /// + internal static string GetDefaultAppName() + { + try + { + var applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath; + return string.IsNullOrEmpty(applicationVirtualPath) ? "/" : applicationVirtualPath; + } + catch + { + return "/"; + } + } + } +} diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 8518b52362..b4e9ebbb2b 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -1,14 +1,209 @@ -using System.Web.Security; +using System; +using System.Collections.Specialized; +using System.Configuration.Provider; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; +using Umbraco.Web.Helpers; namespace Umbraco.Web.Security.Providers { /// - /// Custom Membership Provider for Umbraco Members (User authentication for Umbraco based Websites) + /// Extension methods + /// + static internal class Extensions + { + public static MembershipUser AsConcreteMembershipUser(this IMembershipUser member) + { + // TODO: Provider name?? first constructor arg... Replace DateTime.Now; + var m = new MembershipUser("", + member.Username, + member.ProviderUserKey, + member.Email, + member.PasswordQuestion, + member.Comments, + member.IsApproved, + member.IsLockedOut, + member.CreateDate, + member.LastLoginDate, + member.UpdateDate, + member.LastPasswordChangeDate, + member.LastLockoutDate); + + return m; + } + + public static IMembershipUser AsIMembershipUser(this MembershipUser membershipUser) + { + var m = new Member + { + Username = membershipUser.UserName, + Password = membershipUser.GetPassword(), + Email = membershipUser.Email, + CreateDate = membershipUser.CreationDate, + Comments = membershipUser.Comment, + IsApproved = membershipUser.IsApproved, + IsLockedOut = membershipUser.IsLockedOut, + LastLockoutDate = membershipUser.LastLockoutDate, + LastLoginDate = membershipUser.LastLoginDate, + LastPasswordChangeDate = membershipUser.LastPasswordChangedDate + }; + + return m; + } + + } + + /// + /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) /// internal class MembersMembershipProvider : MembershipProvider { + + #region Fields + private string _applicationName; + private bool _enablePasswordReset; + private bool _enablePasswordRetrieval; + private int _maxInvalidPasswordAttempts; + private int _minRequiredNonAlphanumericCharacters; + private int _minRequiredPasswordLength; + private int _passwordAttemptWindow; + + private MembershipPasswordFormat _passwordFormat; + + private string _passwordStrengthRegularExpression; + private bool _requiresQuestionAndAnswer; + private bool _requiresUniqueEmail; + + #endregion + + /// + /// The name of the application using the custom membership provider. + /// + /// + /// The name of the application using the custom membership provider. + public override string ApplicationName + { + get { return _applicationName; } + set { _applicationName = value; } + } + + /// + /// Indicates whether the membership provider is configured to allow users to reset their passwords. + /// + /// + /// true if the membership provider supports password reset; otherwise, false. The default is true. + public override bool EnablePasswordReset + { + get { return _enablePasswordReset; } + } + + /// + /// Indicates whether the membership provider is configured to allow users to retrieve their passwords. + /// + /// + /// true if the membership provider is configured to support password retrieval; otherwise, false. The default is false. + public override bool EnablePasswordRetrieval + { + get { return _enablePasswordRetrieval; } + } + + /// + /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out. + /// + /// + /// The number of invalid password or password-answer attempts allowed before the membership user is locked out. + public override int MaxInvalidPasswordAttempts + { + get { return _maxInvalidPasswordAttempts; } + } + + /// + /// Gets the minimum number of special characters that must be present in a valid password. + /// + /// + /// The minimum number of special characters that must be present in a valid password. + public override int MinRequiredNonAlphanumericCharacters + { + get { return _minRequiredNonAlphanumericCharacters; } + } + + /// + /// Gets the minimum length required for a password. + /// + /// + /// The minimum length required for a password. + public override int MinRequiredPasswordLength + { + get { return _minRequiredPasswordLength; } + } + + /// + /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. + /// + /// + /// The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. + public override int PasswordAttemptWindow + { + get { return _passwordAttemptWindow; } + } + + /// + /// Gets a value indicating the format for storing passwords in the membership data store. + /// + /// + /// One of the values indicating the format for storing passwords in the data store. + public override MembershipPasswordFormat PasswordFormat + { + get { return _passwordFormat; } + } + + /// + /// Gets the regular expression used to evaluate a password. + /// + /// + /// A regular expression used to evaluate a password. + public override string PasswordStrengthRegularExpression + { + get { return _passwordStrengthRegularExpression; } + } + + /// + /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval. + /// + /// + /// true if a password answer is required for password reset and retrieval; otherwise, false. The default is true. + public override bool RequiresQuestionAndAnswer + { + get { return _requiresQuestionAndAnswer; } + } + + /// + /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name. + /// + /// + /// true if the membership provider requires a unique e-mail address; otherwise, false. The default is true. + public override bool RequiresUniqueEmail + { + get { return _requiresUniqueEmail; } + } + + /// + /// The default Umbraco member type alias to create when registration is performed using .net Membership + /// + /// + /// Member type alias + public string DefaultMemberTypeAlias { get; private set; } + + public string ProviderName { + get { return "MembersMembershipProvider"; } + } + private IMembershipMemberService _memberService; protected IMembershipMemberService MemberService @@ -16,138 +211,591 @@ namespace Umbraco.Web.Security.Providers get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } } + /// + /// Initializes the provider. + /// + /// The friendly name of the provider. + /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. + /// The name of the provider is null. + /// An attempt is made to call + /// on a provider after the provider + /// has already been initialized. + /// The name of the provider has a length of zero. + public override void Initialize(string name, NameValueCollection config) + { + if (config == null) {throw new ArgumentNullException("config");} + + if (string.IsNullOrEmpty(name)) name = ProviderName; + + // Initialize base provider class + base.Initialize(name, config); + + _applicationName = string.IsNullOrEmpty(config["applicationName"]) ? SecurityHelper.GetDefaultAppName() : config["applicationName"]; + + _enablePasswordRetrieval = SecurityHelper.GetBooleanValue(config, "enablePasswordRetrieval", false); + _enablePasswordReset = SecurityHelper.GetBooleanValue(config, "enablePasswordReset", false); + _requiresQuestionAndAnswer = SecurityHelper.GetBooleanValue(config, "requiresQuestionAndAnswer", false); + _requiresUniqueEmail = SecurityHelper.GetBooleanValue(config, "requiresUniqueEmail", true); + _maxInvalidPasswordAttempts = SecurityHelper.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); + _passwordAttemptWindow = SecurityHelper.GetIntValue(config, "passwordAttemptWindow", 10, false, 0); + _minRequiredPasswordLength = SecurityHelper.GetIntValue(config, "minRequiredPasswordLength", 7, true, 0x80); + _minRequiredNonAlphanumericCharacters = SecurityHelper.GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80); + _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; + + // make sure password format is Hashed by default. + var str = config["passwordFormat"] ?? "Hashed"; + + LogHelper.Debug("Loaded membership provider properties"); + LogHelper.Debug(ToString()); + + switch (str.ToLower()) + { + case "clear": + _passwordFormat = MembershipPasswordFormat.Clear; + break; + case "encrypted": + _passwordFormat = MembershipPasswordFormat.Encrypted; + break; + case "hashed": + _passwordFormat = MembershipPasswordFormat.Hashed; + break; + default: + var e = new ProviderException("Provider bad password format"); + LogHelper.Error(e.Message, e); + throw e; + } + + if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval) + { + var e = new ProviderException("Provider can not retrieve hashed password"); + LogHelper.Error(e.Message, e); + throw e; + } + + // TODO: rationalise what happens when no member alias is specified.... + DefaultMemberTypeAlias = config["defaultMemberTypeAlias"]; + + LogHelper.Debug("Finished initialising member ship provider " + GetType().FullName); + + } + + public override string ToString() + { + var result = base.ToString(); + + result += "_applicationName =" + _applicationName + Environment.NewLine; + result += "_enablePasswordReset=" + _enablePasswordReset + Environment.NewLine; + result += "_enablePasswordRetrieval=" + _enablePasswordRetrieval + Environment.NewLine; + result += "_maxInvalidPasswordAttempts=" + _maxInvalidPasswordAttempts + Environment.NewLine; + result += "_minRequiredNonAlphanumericCharacters=" + _minRequiredNonAlphanumericCharacters + Environment.NewLine; + result += "_minRequiredPasswordLength=" + _minRequiredPasswordLength + Environment.NewLine; + result += "_passwordAttemptWindow=" + _passwordAttemptWindow + Environment.NewLine; + + result += "_passwordFormat=" + _passwordFormat + Environment.NewLine; + + result += "_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression + Environment.NewLine; + result += "_requiresQuestionAndAnswer=" + _requiresQuestionAndAnswer + Environment.NewLine; + result += "_requiresUniqueEmail=" + _requiresUniqueEmail + Environment.NewLine; + result += "DefaultMemberTypeAlias=" + DefaultMemberTypeAlias + Environment.NewLine; + + return result; + } + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { - throw new System.NotImplementedException(); + LogHelper.Debug("Member signup requested: username -> " + username + ". email -> " +email); + + // Validate password + if (IsPasswordValid(password) == false) + { + status = MembershipCreateStatus.InvalidPassword; + return null; + } + + // Validate email + if (IsEmaiValid(email) == false) + { + status = MembershipCreateStatus.InvalidEmail; + return null; + } + + // Make sure username isn't all whitespace + if (string.IsNullOrWhiteSpace(username.Trim())) + { + status = MembershipCreateStatus.InvalidUserName; + return null; + } + + // Check password question + if (string.IsNullOrWhiteSpace(passwordQuestion) && _requiresQuestionAndAnswer) + { + status = MembershipCreateStatus.InvalidQuestion; + return null; + } + + // Check password answer + if (string.IsNullOrWhiteSpace(passwordAnswer) && _requiresQuestionAndAnswer) + { + status = MembershipCreateStatus.InvalidAnswer; + return null; + } + + // See if the user already exists + if (MemberService.GetByUsername(username) != null) + { + status = MembershipCreateStatus.DuplicateUserName; + LogHelper.Warn("Cannot create member as username already exists: " + username); + return null; + } + + // See if the email is unique + if (MemberService.GetByEmail(email) != null && RequiresUniqueEmail) + { + status = MembershipCreateStatus.DuplicateEmail; + LogHelper.Warn( + "Cannot create member as a member with the same email address exists: " + email); + return null; + } + + var member = MemberService.CreateMember(username, email, password, DefaultMemberTypeAlias); + + member.IsApproved = isApproved; + member.PasswordQuestion = passwordQuestion; + member.PasswordAnswer = passwordAnswer; + + MemberService.Save(member); + + status = MembershipCreateStatus.Success; + return member.AsConcreteMembershipUser(); } + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { - throw new System.NotImplementedException(); + if (_requiresQuestionAndAnswer == false) + { + throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); + } + + if (ValidateUser(username, password) == false) + { + throw new MembershipPasswordException("Invalid username and password combinatio"); + } + + var member = MemberService.GetByUsername(username); + var encodedPassword = EncodePassword(password); + + if (member.Password == encodedPassword) + { + member.PasswordQuestion = newPasswordQuestion; + member.PasswordAnswer = newPasswordAnswer; + + MemberService.Save(member); + + return true; + } + else + { + //TODO: Throw here? or just return false; + } + + return false; } + /// + /// Gets the password for the specified user name from the data source. + /// + /// The user to retrieve the password for. + /// The password answer for the user. + /// + /// The password for the specified user name. + /// public override string GetPassword(string username, string answer) { - throw new System.NotImplementedException(); + if (_enablePasswordRetrieval == false) + throw new ProviderException("Password Retrieval Not Enabled."); + + if (_passwordFormat == MembershipPasswordFormat.Hashed) + throw new ProviderException("Cannot retrieve Hashed passwords."); + + var member = MemberService.GetByUsername(username); + + if (_requiresQuestionAndAnswer && member.PasswordAnswer != answer) + { + throw new ProviderException("Password retrieval answer doesn't match"); + } + + return member.Password; } + + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// The current password for the specified user. + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// public override bool ChangePassword(string username, string oldPassword, string newPassword) { - throw new System.NotImplementedException(); + // Validate new password + if (IsPasswordValid(newPassword) == false) + { + var e = new MembershipPasswordException("Change password canceled due to new password validation failure."); + LogHelper.WarnWithException(e.Message, e); + throw e; + } + + var member = MemberService.GetByUsername(username); + if (member == null) return false; + + var encodedPassword = EncodePassword(oldPassword); + + if (member.Password == encodedPassword) + { + + member.Password = EncodePassword(newPassword); + MemberService.Save(member); + + return true; + } + + LogHelper.Warn("Can't change password as old password was incorrect"); + return false; } + /// + /// Resets a user's password to a new, automatically generated password. + /// + /// The user to reset the password for. + /// The password answer for the specified user (not used with Umbraco). + /// The new password for the specified user. public override string ResetPassword(string username, string answer) { - throw new System.NotImplementedException(); + if (_enablePasswordReset == false) + throw new ProviderException("Password reset is Not Enabled."); + + var member = MemberService.GetByUsername(username); + + if (member == null) + throw new ProviderException("The supplied user is not found"); + + if(member.IsLockedOut) + throw new ProviderException("The member is locked out."); + + if (_requiresQuestionAndAnswer == false || (_requiresQuestionAndAnswer && answer == member.PasswordAnswer)) + { + member.Password = + EncodePassword(Membership.GeneratePassword(_minRequiredPasswordLength, + _minRequiredNonAlphanumericCharacters)); + MemberService.Save(member); + } + else + { + throw new MembershipPasswordException("Incorrect password answer"); + } + + return null; } + + /// + /// Updates e-mail and potentially approved status, lock status and comment on a user. + /// Note: To automatically support lock, approve and comments you'll need to add references to the membertype properties in the + /// 'Member' element in web.config by adding their aliases to the 'umbracoApprovePropertyTypeAlias', 'umbracoLockPropertyTypeAlias' and 'umbracoCommentPropertyTypeAlias' attributes + /// + /// A object that represents the user to update and the updated information for the user. public override void UpdateUser(MembershipUser user) { - throw new System.NotImplementedException(); + var member = user.AsIMembershipUser(); + MemberService.Save(member); } + /// + /// Verifies that the specified user name and password exist in the data source. + /// + /// The name of the user to validate. + /// The password for the specified user. + /// + /// true if the specified username and password are valid; otherwise, false. + /// public override bool ValidateUser(string username, string password) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + + if (member == null || member.IsApproved == false) return false; + + if (member.IsLockedOut) + throw new ProviderException("The member is locked out."); + + var encodedPassword = EncodePassword(password); + + var authenticated = (encodedPassword == member.Password); + + if (authenticated == false) + { + // TODO: Increment login attempts - lock if too many. + + //var count = member.GetValue("loginAttempts"); + //count++; + + //if (count >= _maxInvalidPasswordAttempts) + //{ + // member.SetValue("loginAttempts", 0); + // member.IsLockedOut = true; + // throw new ProviderException("The member " + member.Username + " is locked out."); + //} + //else + //{ + // member.SetValue("loginAttempts", count); + //} + } + else + { + // add this later - member.SetValue("loginAttempts", 0); + member.LastLoginDate = DateTime.Now; + } + + MemberService.Save(member); + return authenticated; } - public override bool UnlockUser(string userName) + /// + /// Clears a lock so that the membership user can be validated. + /// + /// The membership user to clear the lock status for. + /// + /// true if the membership user was successfully unlocked; otherwise, false. + /// + public override bool UnlockUser(string username) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + + if(member == null) + throw new ProviderException("Cannot find member " + username); + + // Non need to update + if (member.IsLockedOut == false) return true; + + member.IsLockedOut = false; + // TODO: add this later - member.SetValue("loginAttempts", 0); + + MemberService.Save(member); + + return true; } + + /// + /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. + /// + /// The unique identifier for the membership user to get information for. + /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. + /// + /// A object populated with the specified user's information from the data source. + /// public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { - throw new System.NotImplementedException(); + var member = MemberService.GetById(providerUserKey); + + if (userIsOnline) + { + member.UpdateDate = DateTime.Now; + MemberService.Save(member); + } + + return member.AsConcreteMembershipUser(); } + /// + /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user. + /// + /// The name of the user to get information for. + /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. + /// + /// A object populated with the specified user's information from the data source. + /// public override MembershipUser GetUser(string username, bool userIsOnline) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + + if (userIsOnline) + { + member.UpdateDate = DateTime.Now; + MemberService.Save(member); + } + + return member.AsConcreteMembershipUser(); } + /// + /// Gets the user name associated with the specified e-mail address. + /// + /// The e-mail address to search for. + /// + /// The user name associated with the specified e-mail address. If no match is found, return null. + /// public override string GetUserNameByEmail(string email) { - throw new System.NotImplementedException(); + var member = MemberService.GetByEmail(email); + + return member == null ? null : member.Username; } + /// + /// Removes a user from the membership data source. + /// + /// The name of the user to delete. + /// true to delete data related to the user from the database; false to leave data related to the user in the database. + /// + /// true if the user was successfully deleted; otherwise, false. + /// public override bool DeleteUser(string username, bool deleteAllRelatedData) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + if (member == null) return false; + + MemberService.Delete(member); + return true; } + /// + /// Gets a collection of all the users in the data source in pages of data. + /// + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { throw new System.NotImplementedException(); } + /// + /// Gets the number of users currently accessing the application. + /// + /// + /// The number of users currently accessing the application. + /// public override int GetNumberOfUsersOnline() { throw new System.NotImplementedException(); } + + /// + /// Gets a collection of membership users where the user name contains the specified user name to match. + /// + /// The user name to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new System.NotImplementedException(); } + /// + /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. + /// + /// The e-mail address to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new System.NotImplementedException(); } - public override bool EnablePasswordRetrieval + private bool IsPasswordValid(string password) { - get { throw new System.NotImplementedException(); } + if (_minRequiredNonAlphanumericCharacters > 0) + { + var nonAlphaNumeric = Regex.Replace(password, "[a-zA-Z0-9]", "", RegexOptions.Multiline | RegexOptions.IgnoreCase); + if (nonAlphaNumeric.Length < _minRequiredNonAlphanumericCharacters) + { + return false; + } + } + + var valid = true; + if(string.IsNullOrEmpty(_passwordStrengthRegularExpression) == false) + { + valid = Regex.IsMatch(password, _passwordStrengthRegularExpression, RegexOptions.Compiled); + } + + return valid && password.Length >= _minRequiredPasswordLength; } - public override bool EnablePasswordReset + private bool IsEmaiValid(string email) { - get { throw new System.NotImplementedException(); } - } + const string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" + + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? + /// Encodes the password. + /// + /// The password. + /// The encoded password. + private string EncodePassword(string password) { - get { throw new System.NotImplementedException(); } - } - - public override string ApplicationName { get; set; } - - public override int MaxInvalidPasswordAttempts - { - get { throw new System.NotImplementedException(); } - } - - public override int PasswordAttemptWindow - { - get { throw new System.NotImplementedException(); } - } - - public override bool RequiresUniqueEmail - { - get { throw new System.NotImplementedException(); } - } - - public override MembershipPasswordFormat PasswordFormat - { - get { throw new System.NotImplementedException(); } - } - - public override int MinRequiredPasswordLength - { - get { throw new System.NotImplementedException(); } - } - - public override int MinRequiredNonAlphanumericCharacters - { - get { throw new System.NotImplementedException(); } - } - - public override string PasswordStrengthRegularExpression - { - get { throw new System.NotImplementedException(); } - } + var encodedPassword = password; + switch (PasswordFormat) + { + case MembershipPasswordFormat.Clear: + break; + case MembershipPasswordFormat.Encrypted: + encodedPassword = + Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password))); + break; + case MembershipPasswordFormat.Hashed: + var hash = new HMACSHA1 {Key = Encoding.Unicode.GetBytes(password)}; + encodedPassword = + Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password))); + break; + default: + throw new ProviderException("Unsupported password format."); + } + return encodedPassword; + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ef5cd23446..5b3ad0cfc5 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -291,6 +291,7 @@ + diff --git a/src/umbraco.providers/SecUtility.cs b/src/umbraco.providers/SecUtility.cs index 72af5f45e0..0bf43bfa08 100644 --- a/src/umbraco.providers/SecUtility.cs +++ b/src/umbraco.providers/SecUtility.cs @@ -13,6 +13,7 @@ namespace umbraco.providers /// /// Security Helper Methods /// + [ObsoleteAttribute("This clas is obsolete. Use Umbraco.Web.Security.Helper.SecurityHelper instead.", false)] internal class SecUtility { /// From 6f63369e11b633a58cb4782fc32d557dd5b338b4 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 28 Aug 2013 14:17:16 +0200 Subject: [PATCH 07/45] Updating the Member and MemberProfile class to include the standard UmbracoEntity properties. Refactoring the sql query and the dtos to use the PropertyType as Primary and Property as secondary as a Property might be empty for a defined type. Adding labels to the conventional Member PropertyType aliases. --- src/Umbraco.Core/Constants-Conventions.cs | 18 ++ src/Umbraco.Core/Models/Membership/Member.cs | 40 +++- .../Models/Membership/MemberProfile.cs | 132 ++++++++++++- .../Models/Rdbms/PropertyDataReadOnlyDto.cs | 50 ++--- .../Factories/MemberReadOnlyFactory.cs | 180 +++++++++++++++--- .../Repositories/MemberRepository.cs | 21 +- 6 files changed, 376 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index df2e15284d..e91019dca2 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -100,45 +100,63 @@ /// public const string PasswordQuestion = "umbracoPasswordRetrievalQuestionPropertyTypeAlias"; + public const string PasswordQuestionLabel = "Password Question"; + /// /// Property alias for Members Password Answer /// public const string PasswordAnswer = "umbracoPasswordRetrievalAnswerPropertyTypeAlias"; + public const string PasswordAnswerLabel = "Password Answer"; + /// /// Property alias for the Comments on a Member /// public const string Comments = "umbracoCommentPropertyTypeAlias"; + public const string CommentsLabel = "Comments"; + /// /// Property alias for the Approved boolean of a Member /// public const string IsApproved = "umbracoApprovePropertyTypeAlias"; + public const string IsApprovedLabel = "Is Approved"; + /// /// Property alias for the Locked out boolean of a Member /// public const string IsLockedOut = "umbracoLockPropertyTypeAlias"; + public const string IsLockedOutLabel = "Is Locked Out"; + /// /// Property alias for the last date the Member logged in /// public const string LastLoginDate = "umbracoLastLoginPropertyTypeAlias"; + public const string LastLoginDateLabel = "Last Login Date"; + /// /// Property alias for the last date a Member changed its password /// public const string LastPasswordChangeDate = "umbracoMemberLastPasswordChange"; + public const string LastPasswordChangeDateLabel = "Last Password Change Date"; + /// /// Property alias for the last date a Member was locked out /// public const string LastLockoutDate = "umbracoMemberLastLockout"; + public const string LastLockoutDateLabel = "Last Lockout Date"; + /// /// Property alias for the number of failed login attemps /// public const string FailedPasswordAttempts = "umbracoFailedPasswordAttemptsPropertyTypeAlias"; + + public const string FailedPasswordAttemptsLabel = "Failed Password Attempts"; } /// diff --git a/src/Umbraco.Core/Models/Membership/Member.cs b/src/Umbraco.Core/Models/Membership/Member.cs index 5c641a9be2..8fb688462a 100644 --- a/src/Umbraco.Core/Models/Membership/Member.cs +++ b/src/Umbraco.Core/Models/Membership/Member.cs @@ -42,7 +42,7 @@ namespace Umbraco.Core.Models.Membership /// Integer Id /// [DataMember] - public new int Id + public override int Id { get { @@ -65,7 +65,7 @@ namespace Umbraco.Core.Models.Membership /// The key is currectly used to store the Unique Id from the /// umbracoNode table, which many of the entities are based on. [DataMember] - public Guid Key + public override Guid Key { get { @@ -88,7 +88,7 @@ namespace Umbraco.Core.Models.Membership /// Gets or sets the Created Date /// [DataMember] - public DateTime CreateDate + public override DateTime CreateDate { get { return _createDate; } set @@ -105,7 +105,7 @@ namespace Umbraco.Core.Models.Membership /// Gets or sets the Modified Date /// [DataMember] - public DateTime UpdateDate + public override DateTime UpdateDate { get { return _updateDate; } set @@ -122,7 +122,7 @@ namespace Umbraco.Core.Models.Membership /// Indicates whether the current entity has an identity, eg. Id. /// [IgnoreDataMember] - public virtual bool HasIdentity + public override bool HasIdentity { get { @@ -168,7 +168,9 @@ namespace Umbraco.Core.Models.Membership { get { - return Properties[Constants.Conventions.Member.PasswordQuestion].Value.ToString(); + return Properties[Constants.Conventions.Member.PasswordQuestion].Value == null + ? string.Empty + : Properties[Constants.Conventions.Member.PasswordQuestion].Value.ToString(); } set { @@ -188,7 +190,9 @@ namespace Umbraco.Core.Models.Membership { get { - return Properties[Constants.Conventions.Member.PasswordAnswer].Value.ToString(); + return Properties[Constants.Conventions.Member.PasswordAnswer].Value == null + ? string.Empty + : Properties[Constants.Conventions.Member.PasswordAnswer].Value.ToString(); } set { @@ -208,7 +212,9 @@ namespace Umbraco.Core.Models.Membership { get { - return Properties[Constants.Conventions.Member.Comments].Value.ToString(); + return Properties[Constants.Conventions.Member.Comments].Value == null + ? string.Empty + : Properties[Constants.Conventions.Member.Comments].Value.ToString(); } set { @@ -228,6 +234,9 @@ namespace Umbraco.Core.Models.Membership { get { + if (Properties[Constants.Conventions.Member.IsApproved].Value == null) + return default(bool); + if (Properties[Constants.Conventions.Member.IsApproved].Value is bool) return (bool)Properties[Constants.Conventions.Member.IsApproved].Value; @@ -251,6 +260,9 @@ namespace Umbraco.Core.Models.Membership { get { + if (Properties[Constants.Conventions.Member.IsLockedOut].Value == null) + return default(bool); + if (Properties[Constants.Conventions.Member.IsLockedOut].Value is bool) return (bool)Properties[Constants.Conventions.Member.IsLockedOut].Value; @@ -274,6 +286,9 @@ namespace Umbraco.Core.Models.Membership { get { + if (Properties[Constants.Conventions.Member.LastLoginDate].Value == null) + return default(DateTime); + if (Properties[Constants.Conventions.Member.LastLoginDate].Value is DateTime) return (DateTime)Properties[Constants.Conventions.Member.LastLoginDate].Value; @@ -297,6 +312,9 @@ namespace Umbraco.Core.Models.Membership { get { + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value == null) + return default(DateTime); + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value is DateTime) return (DateTime)Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value; @@ -320,6 +338,9 @@ namespace Umbraco.Core.Models.Membership { get { + if (Properties[Constants.Conventions.Member.LastLockoutDate].Value == null) + return default(DateTime); + if (Properties[Constants.Conventions.Member.LastLockoutDate].Value is DateTime) return (DateTime)Properties[Constants.Conventions.Member.LastLockoutDate].Value; @@ -344,6 +365,9 @@ namespace Umbraco.Core.Models.Membership { get { + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value == null) + return default(int); + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value is int) return (int)Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value; diff --git a/src/Umbraco.Core/Models/Membership/MemberProfile.cs b/src/Umbraco.Core/Models/Membership/MemberProfile.cs index aef6af3fd5..6bf7eb33d3 100644 --- a/src/Umbraco.Core/Models/Membership/MemberProfile.cs +++ b/src/Umbraco.Core/Models/Membership/MemberProfile.cs @@ -1,7 +1,133 @@ -namespace Umbraco.Core.Models.Membership +using System; +using System.Reflection; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models.Membership { - internal class MemberProfile : Profile + internal abstract class MemberProfile : Profile, IUmbracoEntity { - + private Lazy _parentId; + private int _sortOrder; + private int _level; + private string _path; + private int _creatorId; + private bool _trashed; + + private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).ParentId); + private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).SortOrder); + private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).Level); + private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).Path); + private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).CreatorId); + private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + + public abstract new int Id { get; set; } + public abstract Guid Key { get; set; } + public abstract DateTime CreateDate { get; set; } + public abstract DateTime UpdateDate { get; set; } + public abstract bool HasIdentity { get; protected set; } + + /// + /// Profile of the user who created this Content + /// + int IUmbracoEntity.CreatorId + { + get + { + return _creatorId; + } + set + { + SetPropertyValueAndDetectChanges(o => + { + _creatorId = value; + return _creatorId; + }, _creatorId, CreatorIdSelector); + } + } + + /// + /// Gets or sets the level of the content entity + /// + int IUmbracoEntity.Level + { + get { return _level; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _level = value; + return _level; + }, _level, LevelSelector); + } } + + /// + /// Gets or sets the Id of the Parent entity + /// + int IUmbracoEntity.ParentId + { + get + { + var val = _parentId.Value; + if (val == 0) + { + throw new InvalidOperationException("The ParentId cannot have a value of 0. Perhaps the parent object used to instantiate this object has not been persisted to the data store."); + } + return val; + } + set + { + _parentId = new Lazy(() => value); + OnPropertyChanged(ParentIdSelector); + } + } + + /// + /// Gets or sets the path + /// + string IUmbracoEntity.Path + { + get { return _path; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _path = value; + return _path; + }, _path, PathSelector); + } + } + + /// + /// Gets or sets the sort order of the content entity + /// + int IUmbracoEntity.SortOrder + { + get { return _sortOrder; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _sortOrder = value; + return _sortOrder; + }, _sortOrder, SortOrderSelector); + } + } + + /// + /// Boolean indicating whether this Content is Trashed or not. + /// If Content is Trashed it will be located in the Recyclebin. + /// + public virtual bool Trashed + { + get { return _trashed; } + internal set + { + SetPropertyValueAndDetectChanges(o => + { + _trashed = value; + return _trashed; + }, _trashed, TrashedSelector); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs index 138a66f3eb..f684d104ac 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs @@ -3,34 +3,15 @@ using Umbraco.Core.Persistence; namespace Umbraco.Core.Models.Rdbms { - [TableName("cmsPropertyData")] + [TableName("cmsPropertyType")] [PrimaryKey("id")] [ExplicitColumns] public class PropertyDataReadOnlyDto { - /* cmsPropertyData */ + /* cmsPropertyType */ [Column("id")] public int Id { get; set; } - [Column("VersionId")] - public Guid? VersionId { get; set; } - - [Column("propertytypeid")] - public int PropertyTypeId { get; set; } - - [Column("dataInt")] - public int? Integer { get; set; } - - [Column("dataDate")] - public DateTime? Date { get; set; } - - [Column("dataNvarchar")] - public string VarChar { get; set; } - - [Column("dataNtext")] - public string Text { get; set; } - - /* cmsPropertyType */ [Column("dataTypeId")] public int DataTypeId { get; set; } @@ -55,6 +36,9 @@ namespace Umbraco.Core.Models.Rdbms [Column("Description")] public string Description { get; set; } + [Column("PropertyTypeSortOrder")] + public int SortOrder { get; set; } + /* cmsDataType */ [Column("controlId")] public Guid ControlId { get; set; } @@ -62,6 +46,28 @@ namespace Umbraco.Core.Models.Rdbms [Column("dbType")] public string DbType { get; set; } + /* cmsPropertyData */ + [Column("PropertyDataId")] + public int? PropertyDataId { get; set; } + + [Column("propertytypeid")] + public int? PropertyTypeId { get; set; } + + [Column("VersionId")] + public Guid VersionId { get; set; } + + [Column("dataInt")] + public int? Integer { get; set; } + + [Column("dataDate")] + public DateTime? Date { get; set; } + + [Column("dataNvarchar")] + public string VarChar { get; set; } + + [Column("dataNtext")] + public string Text { get; set; } + [Ignore] public object GetValue { @@ -87,7 +93,7 @@ namespace Umbraco.Core.Models.Rdbms return Text; } - return null; + return string.Empty; } } } diff --git a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs index 021f67d9f0..67c6a29e37 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs @@ -1,6 +1,8 @@ using System; +using System.Linq; using System.Collections.Generic; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; @@ -19,44 +21,174 @@ namespace Umbraco.Core.Persistence.Factories Email = dto.Email, Username = dto.LoginName, Password = dto.Password, - ProviderUserKey = dto.UniqueId + ProviderUserKey = dto.UniqueId, + Trashed = dto.Trashed, + Key = dto.UniqueId.Value, + ProfileId = dto.UniqueId.Value }; - //Create PropertyType - //Use PropertyType to create Property - var properties = new List(); - foreach (var property in dto.Properties) + ((IUmbracoEntity)member).CreatorId = dto.UserId.Value; + ((IUmbracoEntity)member).Level = dto.Level; + ((IUmbracoEntity)member).ParentId = dto.ParentId; + ((IUmbracoEntity)member).Path = dto.Path; + ((IUmbracoEntity)member).SortOrder = dto.SortOrder; + + var propertyTypes = GetStandardPropertyTypeStubs(); + var propertiesDictionary = dto.Properties.ToDictionary(x => x.Alias); + foreach (var property in propertiesDictionary) { - var propertyType = new PropertyType(property.ControlId, - property.DbType.EnumParse(true)) - { - Id = property.PropertyTypeId, - Alias = property.Alias, - Name = property.Name, - Description = property.Description, - HelpText = property.HelpText, - Mandatory = property.Mandatory, - ValidationRegExp = property.ValidationRegExp - //SortOrder - //PropertyGroupId - }; - - propertyType.ResetDirtyProperties(false); - var prop = propertyType.CreatePropertyFromRawValue(property.GetValue, property.VersionId.Value, property.Id); - properties.Add(prop); + if (propertyTypes.ContainsKey(property.Key)) + { + UpdatePropertyType(propertyTypes[property.Key], property.Value); + } + else + { + propertyTypes.Add(property.Key, CreateProperty(property.Value)); + } } - + + var properties = CreateProperties(propertyTypes, propertiesDictionary); member.Properties = new PropertyCollection(properties); member.SetProviderUserKeyType(typeof(Guid)); member.ResetDirtyProperties(false); return member; } - public MemberReadOnlyDto BuildDto(IMembershipUser entity) { throw new System.NotImplementedException(); } + + private IEnumerable CreateProperties(Dictionary propertyTypes, Dictionary propertiesDictionary) + { + var properties = new List(); + foreach (var propertyType in propertyTypes) + { + if (propertiesDictionary.ContainsKey(propertyType.Key)) + { + var prop = propertiesDictionary[propertyType.Key]; + if (prop.PropertyDataId.HasValue && prop.PropertyDataId.Value != default(int)) + { + properties.Add(propertyType.Value.CreatePropertyFromRawValue(prop.GetValue, prop.VersionId, prop.PropertyDataId.Value)); + } + else + { + properties.Add(propertyType.Value.CreatePropertyFromValue(prop.GetValue)); + } + } + else + { + properties.Add(propertyType.Value.CreatePropertyFromValue(null)); + } + } + + return properties; + } + + private PropertyType CreateProperty(PropertyDataReadOnlyDto property) + { + var propertyType = new PropertyType(property.ControlId, property.DbType.EnumParse(true)) + { + Id = property.Id, + Alias = property.Alias, + Name = property.Name, + Description = property.Description, + HelpText = property.HelpText, + Mandatory = property.Mandatory, + ValidationRegExp = property.ValidationRegExp, + SortOrder = property.SortOrder + }; + + if(property.PropertyTypeGroupId.HasValue) + propertyType.PropertyGroupId = new Lazy(() => property.PropertyTypeGroupId.Value); + + return propertyType; + } + + private void UpdatePropertyType(PropertyType propertyType, PropertyDataReadOnlyDto property) + { + propertyType.Id = property.Id; + propertyType.Alias = property.Alias; + propertyType.Name = property.Name; + propertyType.Description = property.Description; + propertyType.HelpText = property.HelpText; + propertyType.Mandatory = property.Mandatory; + propertyType.ValidationRegExp = property.ValidationRegExp; + propertyType.SortOrder = property.SortOrder; + + if (property.PropertyTypeGroupId.HasValue) + propertyType.PropertyGroupId = new Lazy(() => property.PropertyTypeGroupId.Value); + } + + private Dictionary GetStandardPropertyTypeStubs() + { + var propertyTypes = new Dictionary(); + + propertyTypes.Add(Constants.Conventions.Member.Comments, + new PropertyType(new Guid(Constants.PropertyEditors.TextboxMultiple), DataTypeDatabaseType.Ntext) + { + Alias = Constants.Conventions.Member.Comments, + Name = Constants.Conventions.Member.CommentsLabel + }); + + propertyTypes.Add(Constants.Conventions.Member.FailedPasswordAttempts, + new PropertyType(new Guid(Constants.PropertyEditors.Integer), DataTypeDatabaseType.Integer) + { + Alias = Constants.Conventions.Member.FailedPasswordAttempts, + Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel + }); + + propertyTypes.Add(Constants.Conventions.Member.IsApproved, + new PropertyType(new Guid(Constants.PropertyEditors.TrueFalse), DataTypeDatabaseType.Integer) + { + Alias = Constants.Conventions.Member.IsApproved, + Name = Constants.Conventions.Member.IsApprovedLabel + }); + + propertyTypes.Add(Constants.Conventions.Member.IsLockedOut, + new PropertyType(new Guid(Constants.PropertyEditors.TrueFalse), DataTypeDatabaseType.Integer) + { + Alias = Constants.Conventions.Member.IsLockedOut, + Name = Constants.Conventions.Member.IsLockedOutLabel + }); + + propertyTypes.Add(Constants.Conventions.Member.LastLockoutDate, + new PropertyType(new Guid(Constants.PropertyEditors.Date), DataTypeDatabaseType.Date) + { + Alias = Constants.Conventions.Member.LastLockoutDate, + Name = Constants.Conventions.Member.LastLockoutDateLabel + }); + + propertyTypes.Add(Constants.Conventions.Member.LastLoginDate, + new PropertyType(new Guid(Constants.PropertyEditors.Date), DataTypeDatabaseType.Date) + { + Alias = Constants.Conventions.Member.LastLoginDate, + Name = Constants.Conventions.Member.LastLoginDateLabel + }); + + propertyTypes.Add(Constants.Conventions.Member.LastPasswordChangeDate, + new PropertyType(new Guid(Constants.PropertyEditors.Date), DataTypeDatabaseType.Date) + { + Alias = Constants.Conventions.Member.LastPasswordChangeDate, + Name = Constants.Conventions.Member.LastPasswordChangeDateLabel + }); + + propertyTypes.Add(Constants.Conventions.Member.PasswordAnswer, + new PropertyType(new Guid(Constants.PropertyEditors.Textbox), DataTypeDatabaseType.Nvarchar) + { + Alias = Constants.Conventions.Member.PasswordAnswer, + Name = Constants.Conventions.Member.PasswordAnswerLabel + }); + + propertyTypes.Add(Constants.Conventions.Member.PasswordQuestion, + new PropertyType(new Guid(Constants.PropertyEditors.Textbox), DataTypeDatabaseType.Nvarchar) + { + Alias = Constants.Conventions.Member.PasswordQuestion, + Name = Constants.Conventions.Member.PasswordQuestionLabel + }); + + return propertyTypes; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index fc0bdaac95..cb93a00185 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.UnitOfWork; @@ -45,7 +46,10 @@ namespace Umbraco.Core.Persistence.Repositories if (dto == null || dto.Any() == false) return null; - return new Member(); + var factory = new MemberReadOnlyFactory(); + var member = factory.BuildEntity(dto.First()); + + return member; } protected override IEnumerable PerformGetAll(params int[] ids) @@ -68,20 +72,21 @@ namespace Umbraco.Core.Persistence.Repositories var sql = new Sql(); sql.Select("umbracoNode.*", "cmsContent.contentType", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", "cmsContentVersion.VersionDate", "cmsContentVersion.LanguageLocale", "cmsMember.Email", - "cmsMember.LoginName", "cmsMember.Password", "cmsPropertyData.id", "cmsPropertyData.dataDate", - "cmsPropertyData.dataInt", "cmsPropertyData.dataNtext", "cmsPropertyData.dataNvarchar", - "cmsPropertyData.propertytypeid", "cmsPropertyType.Alias", "cmsPropertyType.Description", + "cmsMember.LoginName", "cmsMember.Password", "cmsPropertyData.id AS PropertyDataId", "cmsPropertyData.propertytypeid", + "cmsPropertyData.dataDate", "cmsPropertyData.dataInt", "cmsPropertyData.dataNtext", "cmsPropertyData.dataNvarchar", + "cmsPropertyType.id", "cmsPropertyType.Alias", "cmsPropertyType.Description", "cmsPropertyType.Name", "cmsPropertyType.mandatory", "cmsPropertyType.validationRegExp", - "cmsPropertyType.helpText", "cmsPropertyType.propertyTypeGroupId", "cmsPropertyType.dataTypeId", - "cmsDataType.controlId", "cmsDataType.dbType") + "cmsPropertyType.helpText", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId", + "cmsPropertyType.dataTypeId", "cmsDataType.controlId", "cmsDataType.dbType") .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) .InnerJoin().On(left => left.NodeId, right => right.NodeId) .InnerJoin().On(left => left.NodeId, right => right.NodeId) - .LeftJoin().On(left => left.VersionId, right => right.VersionId) - .LeftJoin().On(left => left.Id, right => right.PropertyTypeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } From 575abc6abf0ce7a378f82a6a3f35e95dbf8fb62c Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 28 Aug 2013 15:24:22 +0200 Subject: [PATCH 08/45] Implementing the mapper for Member. Refactoring the MemberProfile, IMember and IMembershipUser interfaces --- src/Umbraco.Core/Models/Membership/IMember.cs | 12 ++++ .../Models/Membership/IMembershipUser.cs | 6 +- src/Umbraco.Core/Models/Membership/Member.cs | 61 ++++++++++++----- .../Models/Membership/MemberProfile.cs | 28 +++++++- .../Factories/MemberReadOnlyFactory.cs | 10 +-- .../Persistence/Mappers/MemberMapper.cs | 67 +++++++++++++++++++ .../Interfaces/IMemberRepository.cs | 2 +- .../Repositories/MemberRepository.cs | 14 ++-- src/Umbraco.Core/Umbraco.Core.csproj | 2 + 9 files changed, 166 insertions(+), 36 deletions(-) create mode 100644 src/Umbraco.Core/Models/Membership/IMember.cs create mode 100644 src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs diff --git a/src/Umbraco.Core/Models/Membership/IMember.cs b/src/Umbraco.Core/Models/Membership/IMember.cs new file mode 100644 index 0000000000..2cd41c8a5d --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/IMember.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Models.Membership +{ + internal interface IMember : IMembershipUser, IMemberProfile + { + new int Id { get; set; } + } + + internal interface IMemberProfile : IProfile + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs index b80faeee10..422b47da22 100644 --- a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs +++ b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Models.Membership { internal interface IMembershipUser : IMembershipUserId, IAggregateRoot { - new object Id { get; set; } + /*new object Id { get; set; }*/ string Username { get; set; } string Email { get; set; } string Password { get; set; } @@ -16,10 +16,6 @@ namespace Umbraco.Core.Models.Membership bool IsApproved { get; set; } bool IsOnline { get; set; } bool IsLockedOut { get; set; } - //Was CreationDate - //DateTime CreateDate { get; set; } - //LastActivityDate - //DateTime UpdateDate { get; set; } DateTime LastLoginDate { get; set; } DateTime LastPasswordChangeDate { get; set; } DateTime LastLockoutDate { get; set; } diff --git a/src/Umbraco.Core/Models/Membership/Member.cs b/src/Umbraco.Core/Models/Membership/Member.cs index 8fb688462a..640c7aa8a9 100644 --- a/src/Umbraco.Core/Models/Membership/Member.cs +++ b/src/Umbraco.Core/Models/Membership/Member.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.Reflection; using System.Runtime.Serialization; @@ -17,27 +16,24 @@ namespace Umbraco.Core.Models.Membership [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}")] - internal class Member : MemberProfile, IMembershipUser + internal class Member : MemberProfile, IMember { private bool _hasIdentity; private int _id; private Guid _key; private DateTime _createDate; private DateTime _updateDate; - private PropertyCollection _properties; + private int _contentTypeId; + private string _contentTypeAlias; private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); private static readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); private static readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); - private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); - - protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(PropertyCollectionSelector); - } - + private static readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); + private static readonly PropertyInfo DefaultContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); + /// /// Integer Id /// @@ -386,20 +382,49 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] public bool IsOnline { get; set; } - public object ProfileId { get; set; } - public IEnumerable Groups { get; set; } - + /// + /// Guid Id of the curent Version + /// [DataMember] - public PropertyCollection Properties + public Guid Version { get; internal set; } + + /// + /// Integer Id of the default ContentType + /// + [DataMember] + public virtual int ContentTypeId { - get { return _properties; } - set + get { return _contentTypeId; } + internal set { - _properties = value; - _properties.CollectionChanged += PropertiesChanged; + SetPropertyValueAndDetectChanges(o => + { + _contentTypeId = value; + return _contentTypeId; + }, _contentTypeId, DefaultContentTypeIdSelector); } } + /// + /// String alias of the default ContentType + /// + [DataMember] + public virtual string ContentTypeAlias + { + get { return _contentTypeAlias; } + internal set + { + SetPropertyValueAndDetectChanges(o => + { + _contentTypeAlias = value; + return _contentTypeAlias; + }, _contentTypeAlias, DefaultContentTypeAliasSelector); + } + } + + public object ProfileId { get; set; } + public IEnumerable Groups { get; set; } + #region Internal methods internal virtual void ResetIdentity() diff --git a/src/Umbraco.Core/Models/Membership/MemberProfile.cs b/src/Umbraco.Core/Models/Membership/MemberProfile.cs index 6bf7eb33d3..2148cd1ca2 100644 --- a/src/Umbraco.Core/Models/Membership/MemberProfile.cs +++ b/src/Umbraco.Core/Models/Membership/MemberProfile.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Specialized; using System.Reflection; +using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models.Membership @@ -12,6 +14,7 @@ namespace Umbraco.Core.Models.Membership private string _path; private int _creatorId; private bool _trashed; + private PropertyCollection _properties; private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).ParentId); private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).SortOrder); @@ -19,6 +22,12 @@ namespace Umbraco.Core.Models.Membership private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).Path); private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).CreatorId); private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + + protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(PropertyCollectionSelector); + } public abstract new int Id { get; set; } public abstract Guid Key { get; set; } @@ -29,6 +38,7 @@ namespace Umbraco.Core.Models.Membership /// /// Profile of the user who created this Content /// + [DataMember] int IUmbracoEntity.CreatorId { get @@ -48,7 +58,8 @@ namespace Umbraco.Core.Models.Membership /// /// Gets or sets the level of the content entity /// - int IUmbracoEntity.Level + [DataMember] + int IUmbracoEntity.Level { get { return _level; } set @@ -63,6 +74,7 @@ namespace Umbraco.Core.Models.Membership /// /// Gets or sets the Id of the Parent entity /// + [DataMember] int IUmbracoEntity.ParentId { get @@ -84,6 +96,7 @@ namespace Umbraco.Core.Models.Membership /// /// Gets or sets the path /// + [DataMember] string IUmbracoEntity.Path { get { return _path; } @@ -100,6 +113,7 @@ namespace Umbraco.Core.Models.Membership /// /// Gets or sets the sort order of the content entity /// + [DataMember] int IUmbracoEntity.SortOrder { get { return _sortOrder; } @@ -117,6 +131,7 @@ namespace Umbraco.Core.Models.Membership /// Boolean indicating whether this Content is Trashed or not. /// If Content is Trashed it will be located in the Recyclebin. /// + [DataMember] public virtual bool Trashed { get { return _trashed; } @@ -129,5 +144,16 @@ namespace Umbraco.Core.Models.Membership }, _trashed, TrashedSelector); } } + + [DataMember] + public PropertyCollection Properties + { + get { return _properties; } + set + { + _properties = value; + _properties.CollectionChanged += PropertiesChanged; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs index 67c6a29e37..fb8010a28d 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs @@ -8,9 +8,9 @@ using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Factories { - internal class MemberReadOnlyFactory : IEntityFactory + internal class MemberReadOnlyFactory : IEntityFactory { - public IMembershipUser BuildEntity(MemberReadOnlyDto dto) + public IMember BuildEntity(MemberReadOnlyDto dto) { var member = new Member { @@ -24,7 +24,9 @@ namespace Umbraco.Core.Persistence.Factories ProviderUserKey = dto.UniqueId, Trashed = dto.Trashed, Key = dto.UniqueId.Value, - ProfileId = dto.UniqueId.Value + ProfileId = dto.UniqueId.Value, + ContentTypeId = dto.ContentTypeId, + ContentTypeAlias = dto.ContentTypeAlias }; ((IUmbracoEntity)member).CreatorId = dto.UserId.Value; @@ -55,7 +57,7 @@ namespace Umbraco.Core.Persistence.Factories return member; } - public MemberReadOnlyDto BuildDto(IMembershipUser entity) + public MemberReadOnlyDto BuildDto(IMember entity) { throw new System.NotImplementedException(); } diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs new file mode 100644 index 0000000000..cf32d1edd3 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs @@ -0,0 +1,67 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(IMember))] + [MapperFor(typeof(Member))] + public sealed class MemberMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it + // otherwise that would fail because there is no public constructor. + public MemberMapper() + { + BuildMap(); + } + + #region Overrides of BaseMapper + + internal override ConcurrentDictionary PropertyInfoCache + { + get { return PropertyInfoCacheInstance; } + } + + internal override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => ((IUmbracoEntity)src).Level, dto => dto.Level); + CacheMap(src => ((IUmbracoEntity)src).ParentId, dto => dto.ParentId); + CacheMap(src => ((IUmbracoEntity)src).Path, dto => dto.Path); + CacheMap(src => ((IUmbracoEntity)src).SortOrder, dto => dto.SortOrder); + CacheMap(src => ((IUmbracoEntity)src).CreatorId, dto => dto.UserId); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.ProfileId, dto => dto.UniqueId); + CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); + CacheMap(src => src.ContentTypeAlias, dto => dto.Alias); + CacheMap(src => src.UpdateDate, dto => dto.VersionDate); + CacheMap(src => src.Version, dto => dto.VersionId); + + CacheMap(src => src.Email, dto => dto.Email); + CacheMap(src => src.Username, dto => dto.LoginName); + CacheMap(src => src.Password, dto => dto.Password); + + CacheMap(src => src.IsApproved, dto => dto.Integer); + CacheMap(src => src.IsLockedOut, dto => dto.Integer); + CacheMap(src => src.Comments, dto => dto.Text); + CacheMap(src => src.PasswordAnswer, dto => dto.VarChar); + CacheMap(src => src.PasswordQuestion, dto => dto.VarChar); + CacheMap(src => src.FailedPasswordAttempts, dto => dto.Integer); + CacheMap(src => src.LastLockoutDate, dto => dto.Date); + CacheMap(src => src.LastLoginDate, dto => dto.Date); + CacheMap(src => src.LastPasswordChangeDate, dto => dto.Date); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index 9ce6642e3b..616e10286b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Persistence.Repositories { - internal interface IMemberRepository : IRepositoryVersionable + internal interface IMemberRepository : IRepositoryVersionable { } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index cb93a00185..d18eb4fb87 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -11,7 +11,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class MemberRepository : VersionableRepositoryBase, IMemberRepository + internal class MemberRepository : VersionableRepositoryBase, IMemberRepository { public MemberRepository(IDatabaseUnitOfWork work) : base(work) { @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of RepositoryBase - protected override IMembershipUser PerformGet(int id) + protected override IMember PerformGet(int id) { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); @@ -52,12 +52,12 @@ namespace Umbraco.Core.Persistence.Repositories return member; } - protected override IEnumerable PerformGetAll(params int[] ids) + protected override IEnumerable PerformGetAll(params int[] ids) { throw new NotImplementedException(); } - protected override IEnumerable PerformGetByQuery(IQuery query) + protected override IEnumerable PerformGetByQuery(IQuery query) { throw new NotImplementedException(); } @@ -110,12 +110,12 @@ namespace Umbraco.Core.Persistence.Repositories #region Unit of Work Implementation - protected override void PersistNewItem(IMembershipUser entity) + protected override void PersistNewItem(IMember entity) { throw new NotImplementedException(); } - protected override void PersistUpdatedItem(IMembershipUser entity) + protected override void PersistUpdatedItem(IMember entity) { throw new NotImplementedException(); } @@ -124,7 +124,7 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of VersionableRepositoryBase - public override IMembershipUser GetByVersion(Guid versionId) + public override IMember GetByVersion(Guid versionId) { throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 32a07f80f8..c20863a8e0 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -200,6 +200,7 @@ + @@ -264,6 +265,7 @@ + From c7351dfad7462bdbcbe90631e7115eea0aa17fbe Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 28 Aug 2013 17:27:29 +0200 Subject: [PATCH 09/45] Adding additional methods to the MemberRepository to support various types of lookups/queries, and experimenting with adding dummy properties to the model so a strongly typed query can be translated using mappers. GetByQuery needs to be refactored so the selection is done in a subquery, so we don't loose properties in the result set --- src/Umbraco.Core/Models/Membership/Member.cs | 13 ++ .../Models/Membership/MemberProfile.cs | 27 ++-- .../Factories/MemberReadOnlyFactory.cs | 2 +- .../Persistence/Mappers/MemberMapper.cs | 8 + .../Querying/ModelToSqlExpressionHelper.cs | 2 +- .../Repositories/MemberRepository.cs | 152 ++++++++++++++++-- 6 files changed, 178 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Core/Models/Membership/Member.cs b/src/Umbraco.Core/Models/Membership/Member.cs index 640c7aa8a9..eb21ea7bad 100644 --- a/src/Umbraco.Core/Models/Membership/Member.cs +++ b/src/Umbraco.Core/Models/Membership/Member.cs @@ -34,6 +34,9 @@ namespace Umbraco.Core.Models.Membership private static readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); private static readonly PropertyInfo DefaultContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); + public Member() + {} + /// /// Integer Id /// @@ -424,6 +427,16 @@ namespace Umbraco.Core.Models.Membership public object ProfileId { get; set; } public IEnumerable Groups { get; set; } + + /* Internal experiment - only used for mapping queries. + * Adding these to have first level properties instead of the Properties collection. + */ + internal string LongStringPropertyValue { get; set; } + internal string ShortStringPropertyValue { get; set; } + internal int IntegerropertyValue { get; set; } + internal bool BoolPropertyValue { get; set; } + internal DateTime DateTimePropertyValue { get; set; } + internal string PropertyTypeAlias { get; set; } #region Internal methods diff --git a/src/Umbraco.Core/Models/Membership/MemberProfile.cs b/src/Umbraco.Core/Models/Membership/MemberProfile.cs index 2148cd1ca2..185c292f06 100644 --- a/src/Umbraco.Core/Models/Membership/MemberProfile.cs +++ b/src/Umbraco.Core/Models/Membership/MemberProfile.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models.Membership { - internal abstract class MemberProfile : Profile, IUmbracoEntity + internal class MemberProfile : Profile, IUmbracoEntity { private Lazy _parentId; private int _sortOrder; @@ -16,12 +16,12 @@ namespace Umbraco.Core.Models.Membership private bool _trashed; private PropertyCollection _properties; - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).ParentId); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).SortOrder); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).Level); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).Path); - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => ((IUmbracoEntity)x).CreatorId); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -29,11 +29,14 @@ namespace Umbraco.Core.Models.Membership OnPropertyChanged(PropertyCollectionSelector); } - public abstract new int Id { get; set; } - public abstract Guid Key { get; set; } - public abstract DateTime CreateDate { get; set; } - public abstract DateTime UpdateDate { get; set; } - public abstract bool HasIdentity { get; protected set; } + protected MemberProfile() + {} + + public virtual new int Id { get; set; } + public virtual Guid Key { get; set; } + public virtual DateTime CreateDate { get; set; } + public virtual DateTime UpdateDate { get; set; } + public virtual bool HasIdentity { get; protected set; } /// /// Profile of the user who created this Content diff --git a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs index fb8010a28d..37bfc7fa49 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories { public IMember BuildEntity(MemberReadOnlyDto dto) { - var member = new Member + var member = new Member() { Id = dto.NodeId, CreateDate = dto.CreateDate, diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs index cf32d1edd3..6e93d7c4aa 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs @@ -60,6 +60,14 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.LastLockoutDate, dto => dto.Date); CacheMap(src => src.LastLoginDate, dto => dto.Date); CacheMap(src => src.LastPasswordChangeDate, dto => dto.Date); + + /* Internal experiment */ + CacheMap(src => src.DateTimePropertyValue, dto => dto.Date); + CacheMap(src => src.IntegerropertyValue, dto => dto.Integer); + CacheMap(src => src.BoolPropertyValue, dto => dto.Integer); + CacheMap(src => src.LongStringPropertyValue, dto => dto.Text); + CacheMap(src => src.ShortStringPropertyValue, dto => dto.VarChar); + CacheMap(src => src.PropertyTypeAlias, dto => dto.Alias); } #endregion diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index 3042510b05..76ca62139f 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -250,7 +250,7 @@ namespace Umbraco.Core.Persistence.Querying case "EndsWith": return string.Format("upper({0}) like '%{1}'", r, RemoveQuote(args[0].ToString()).ToUpper()); case "Contains": - return string.Format("upper({0}) like '%{1}%'", r, RemoveQuote(args[0].ToString()).ToUpper()); + return string.Format("{0} like '%{1}%'", r, RemoveQuote(args[0].ToString()).ToUpper()); case "Substring": var startIndex = Int32.Parse(args[0].ToString()) + 1; if (args.Count == 2) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index d18eb4fb87..34c69fe358 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -25,10 +25,10 @@ namespace Umbraco.Core.Persistence.Repositories * GetById - get a member by its integer Id * GetByKey - get a member by its unique guid Id (which should correspond to a membership provider user's id) * GetByUsername - get a member by its username + * * GetByPropertyValue - get members with a certain property value (supply both property alias and value?) * GetByMemberTypeAlias - get all members of a certain type * GetByMemberGroup - get all members in a specific group - * GetAllMembers */ #region Overrides of RepositoryBase @@ -43,23 +43,39 @@ namespace Umbraco.Core.Persistence.Repositories Database.Fetch( new PropertyDataRelator().Map, sql); - if (dto == null || dto.Any() == false) - return null; - - var factory = new MemberReadOnlyFactory(); - var member = factory.BuildEntity(dto.First()); - - return member; + return BuildFromDto(dto); } protected override IEnumerable PerformGetAll(params int[] ids) { - throw new NotImplementedException(); + var sql = GetBaseQuery(false); + if (ids.Any()) + { + var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.id='{0}'", x))); + sql.Where(statement); + } + sql.OrderByDescending(x => x.VersionDate); + + var dtos = + Database.Fetch( + new PropertyDataRelator().Map, sql); + + return BuildFromDtos(dtos); } protected override IEnumerable PerformGetByQuery(IQuery query) { - throw new NotImplementedException(); + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + var dtos = + Database.Fetch( + new PropertyDataRelator().Map, sql); + + return BuildFromDtos(dtos); } #endregion @@ -68,7 +84,19 @@ namespace Umbraco.Core.Persistence.Repositories protected override Sql GetBaseQuery(bool isCount) { - //TODO Count + if (isCount) + { + var sqlCount = new Sql() + .Select("COUNT(*)") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sqlCount; + } + var sql = new Sql(); sql.Select("umbracoNode.*", "cmsContent.contentType", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", "cmsContentVersion.VersionDate", "cmsContentVersion.LanguageLocale", "cmsMember.Email", @@ -98,7 +126,21 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable GetDeleteClauses() { - throw new NotImplementedException(); + var list = new List + { + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoRelation WHERE parentId = @Id", + "DELETE FROM umbracoRelation WHERE childId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", + "DELETE FROM cmsMember WHERE nodeId = @Id", + "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM cmsContentXml WHERE nodeID = @Id", + "DELETE FROM cmsContent WHERE NodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" + }; + return list; } protected override Guid NodeObjectTypeId @@ -135,5 +177,91 @@ namespace Umbraco.Core.Persistence.Repositories } #endregion + + //NOTE Might be sufficient to use the GetByQuery method for this, as the mapping should cover it + public IMember GetByKey(Guid key) + { + var sql = GetBaseQuery(false); + sql.Where(x => x.UniqueId == key); + sql.OrderByDescending(x => x.VersionDate); + + var dto = + Database.Fetch( + new PropertyDataRelator().Map, sql); + + return BuildFromDto(dto); + } + + //NOTE Might be sufficient to use the GetByQuery method for this, as the mapping should cover it + public IMember GetByUsername(string username) + { + var sql = GetBaseQuery(false); + sql.Where(x => x.LoginName == username); + sql.OrderByDescending(x => x.VersionDate); + + var dto = + Database.Fetch( + new PropertyDataRelator().Map, sql); + + return BuildFromDto(dto); + } + + //NOTE Might be sufficient to use the GetByQuery method for this, as the mapping should cover it + public IEnumerable GetByMemberTypeAlias(string memberTypeAlias) + { + var sql = GetBaseQuery(false); + sql.Where(x => x.Alias == memberTypeAlias); + sql.OrderByDescending(x => x.VersionDate); + + var dtos = + Database.Fetch( + new PropertyDataRelator().Map, sql); + + return BuildFromDtos(dtos); + } + + /*public IEnumerable GetByPropertyValue(string value) + {} + + public IEnumerable GetByPropertyValue(bool value) + { } + + public IEnumerable GetByPropertyValue(int value) + { } + + public IEnumerable GetByPropertyValue(DateTime value) + { } + + public IEnumerable GetByPropertyValue(string propertyTypeAlias, string value) + { } + + public IEnumerable GetByPropertyValue(string propertyTypeAlias, bool value) + { } + + public IEnumerable GetByPropertyValue(string propertyTypeAlias, int value) + { } + + public IEnumerable GetByPropertyValue(string propertyTypeAlias, DateTime value) + { }*/ + + private IMember BuildFromDto(List dtos) + { + if (dtos == null || dtos.Any() == false) + return null; + + var factory = new MemberReadOnlyFactory(); + var member = factory.BuildEntity(dtos.First()); + + return member; + } + + private IEnumerable BuildFromDtos(List dtos) + { + if (dtos == null || dtos.Any() == false) + return Enumerable.Empty(); + + var factory = new MemberReadOnlyFactory(); + return dtos.Select(factory.BuildEntity); + } } } \ No newline at end of file From 37ce5c7a22a5f5667a9dc995dad7dbe83b7f800f Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 29 Aug 2013 15:42:13 +0200 Subject: [PATCH 10/45] Implementing subquery addon for PerformGetByQuery, which allows for better querying first level properties as well as the property collection on a member --- .../Repositories/MemberRepository.cs | 91 +++++-------------- 1 file changed, 22 insertions(+), 69 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 34c69fe358..b684db9724 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -65,9 +65,11 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformGetByQuery(IQuery query) { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() + var sqlSubquery = GetSubquery(); + var translator = new SqlTranslator(sqlSubquery, query); + var subquery = translator.Translate(); + var sql = GetBaseQuery(false) + .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)) .OrderByDescending(x => x.VersionDate) .OrderBy(x => x.SortOrder); @@ -124,6 +126,23 @@ namespace Umbraco.Core.Persistence.Repositories return "umbracoNode.id = @Id"; } + protected Sql GetSubquery() + { + var sql = new Sql(); + sql.Select("umbracoNode.id") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + protected override IEnumerable GetDeleteClauses() { var list = new List @@ -178,72 +197,6 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - //NOTE Might be sufficient to use the GetByQuery method for this, as the mapping should cover it - public IMember GetByKey(Guid key) - { - var sql = GetBaseQuery(false); - sql.Where(x => x.UniqueId == key); - sql.OrderByDescending(x => x.VersionDate); - - var dto = - Database.Fetch( - new PropertyDataRelator().Map, sql); - - return BuildFromDto(dto); - } - - //NOTE Might be sufficient to use the GetByQuery method for this, as the mapping should cover it - public IMember GetByUsername(string username) - { - var sql = GetBaseQuery(false); - sql.Where(x => x.LoginName == username); - sql.OrderByDescending(x => x.VersionDate); - - var dto = - Database.Fetch( - new PropertyDataRelator().Map, sql); - - return BuildFromDto(dto); - } - - //NOTE Might be sufficient to use the GetByQuery method for this, as the mapping should cover it - public IEnumerable GetByMemberTypeAlias(string memberTypeAlias) - { - var sql = GetBaseQuery(false); - sql.Where(x => x.Alias == memberTypeAlias); - sql.OrderByDescending(x => x.VersionDate); - - var dtos = - Database.Fetch( - new PropertyDataRelator().Map, sql); - - return BuildFromDtos(dtos); - } - - /*public IEnumerable GetByPropertyValue(string value) - {} - - public IEnumerable GetByPropertyValue(bool value) - { } - - public IEnumerable GetByPropertyValue(int value) - { } - - public IEnumerable GetByPropertyValue(DateTime value) - { } - - public IEnumerable GetByPropertyValue(string propertyTypeAlias, string value) - { } - - public IEnumerable GetByPropertyValue(string propertyTypeAlias, bool value) - { } - - public IEnumerable GetByPropertyValue(string propertyTypeAlias, int value) - { } - - public IEnumerable GetByPropertyValue(string propertyTypeAlias, DateTime value) - { }*/ - private IMember BuildFromDto(List dtos) { if (dtos == null || dtos.Any() == false) From d44318f589a11c8d51cfa6842e50f2f604d779bb Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 30 Aug 2013 10:50:43 +0200 Subject: [PATCH 11/45] Fixes U4-2752 ContentService.DeleteVersion and DeleteVersions fail --- .../Repositories/ContentRepository.cs | 24 +++++++++++++++++-- .../Repositories/MediaRepository.cs | 4 ++-- .../Repositories/VersionableRepositoryBase.cs | 8 +++---- .../Services/ContentServiceTests.cs | 16 +++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 870ed2cc00..156eeab919 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -170,11 +170,31 @@ namespace Umbraco.Core.Persistence.Repositories return content; } + public override void DeleteVersion(Guid versionId) + { + var sql = new Sql() + .Select("*") + .From() + .InnerJoin().On(left => left.VersionId, right => right.VersionId) + .Where(x => x.VersionId == versionId) + .Where(x => x.Newest == true); + var dto = Database.Fetch(sql).FirstOrDefault(); + + if(dto == null) return; + + using (var transaction = Database.GetTransaction()) + { + PerformDeleteVersion(dto.NodeId, versionId); + + transaction.Complete(); + } + } + protected override void PerformDeleteVersion(int id, Guid versionId) { Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index e27fac1ba3..80c719596d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -176,8 +176,8 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PerformDeleteVersion(int id, Guid versionId) { Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); } #endregion diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 0ed84582fa..3cbcfc6a14 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -43,8 +43,8 @@ namespace Umbraco.Core.Persistence.Repositories public virtual void DeleteVersion(Guid versionId) { - var dto = Database.FirstOrDefault("WHERE versionId = @VersionId AND newest = @Newest", new { VersionId = versionId, Newest = false }); - Mandate.That(dto != null); + var dto = Database.FirstOrDefault("WHERE versionId = @VersionId", new { VersionId = versionId }); + if(dto == null) return; using (var transaction = Database.GetTransaction()) { @@ -56,8 +56,8 @@ namespace Umbraco.Core.Persistence.Repositories public virtual void DeleteVersions(int id, DateTime versionDate) { - var list = Database.Fetch("WHERE nodeId = @Id AND VersionDate < @VersionDate", new { Id = id, VersionDate = versionDate }); - Mandate.That(list.Any()); + var list = Database.Fetch("WHERE ContentId = @Id AND VersionDate < @VersionDate", new { Id = id, VersionDate = versionDate }); + if (list.Any() == false) return; using (var transaction = Database.GetTransaction()) { diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 522f608272..089fc25ae2 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -861,6 +861,22 @@ namespace Umbraco.Tests.Services Assert.That(sut.GetValue("imgCropper"), Is.Empty); } + [Test] + public void Can_Delete_Previous_Versions_Not_Latest() + { + // Arrange + var contentService = ServiceContext.ContentService; + var content = contentService.GetById(1049); + var version = content.Version; + + // Act + contentService.DeleteVersion(1049, version, true, 0); + var sut = contentService.GetById(1049); + + // Assert + Assert.That(sut.Version, Is.EqualTo(version)); + } + private IEnumerable CreateContentHierarchy() { var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); From 09c9fb4e8fc9a6d8679df8a1261b975d53d33832 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 30 Aug 2013 10:52:58 +0200 Subject: [PATCH 12/45] Fixes U4-2731 Document.BeforeMove + Document.AfterMove Doesn't get fired. Using the legacy Document and Media classes for the .Move and .Copy calls as they already use the new services under the hood, so shouldn't make any noticeable difference except that the legacy events will also work. --- .../umbraco/dialogs/moveOrCopy.aspx.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs index ccd6c6481d..ad8410da85 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs @@ -272,11 +272,15 @@ namespace umbraco.dialogs { if (CurrentApp == Constants.Applications.Content) { - Services.ContentService.Move((IContent)currContent, Request.GetItemAs("copyTo"), getUser().Id); + //Backwards comp. change, so old events are fired #U4-2731 + var doc = new Document(currContent as IContent); + doc.Move(Request.GetItemAs("copyTo")); } else { - Services.MediaService.Move((IMedia)currContent, Request.GetItemAs("copyTo"), getUser().Id); + //Backwards comp. change, so old events are fired #U4-2731 + var media = new umbraco.cms.businesslogic.media.Media(currContent as IMedia); + media.Move(Request.GetItemAs("copyTo")); library.ClearLibraryCacheForMedia(currContent.Id); } @@ -290,7 +294,9 @@ namespace umbraco.dialogs { //NOTE: We ONLY support Copy on content not media for some reason. - var newContent = Services.ContentService.Copy((IContent)currContent, Request.GetItemAs("copyTo"), RelateDocuments.Checked, getUser().Id); + //Backwards comp. change, so old events are fired #U4-2731 + var newContent = new Document(currContent as IContent); + newContent.Copy(Request.GetItemAs("copyTo"), getUser(), RelateDocuments.Checked); feedback.Text = ui.Text("moveOrCopy", "copyDone", nodes, getUser()) + "

" + ui.Text("closeThisWindow") + ""; feedback.type = uicontrols.Feedback.feedbacktype.success; From 5d5c25f0e46937cb9b93a5710cc821bd21ffd5f2 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 30 Aug 2013 17:24:06 +0200 Subject: [PATCH 13/45] Implemented new IMemberType, MemberTypeFactory, MemberFactory and MemberTypeMapper. Started implementation of MemberTypeRepository. --- src/Umbraco.Core/Models/IContentType.cs | 1 - src/Umbraco.Core/Models/Membership/IMember.cs | 17 +- .../Models/Membership/IMemberType.cs | 36 +++ src/Umbraco.Core/Models/Membership/Member.cs | 21 ++ .../Models/Membership/MemberType.cs | 125 ++++++++- src/Umbraco.Core/Models/Rdbms/MemberDto.cs | 3 + .../Persistence/Factories/MemberFactory.cs | 93 +++++++ .../Factories/MemberTypeFactory.cs | 74 ++++++ .../Persistence/Mappers/ContentTypeMapper.cs | 4 +- .../Persistence/Mappers/MediaTypeMapper.cs | 4 +- .../Persistence/Mappers/MemberTypeMapper.cs | 57 ++++ .../Interfaces/IMemberRepository.cs | 10 +- .../Interfaces/IMemberTypeRepository.cs | 9 + .../Repositories/MemberRepository.cs | 245 ++++++++++++++++-- .../Repositories/MemberTypeRepository.cs | 182 ++++++++++++- src/Umbraco.Core/Umbraco.Core.csproj | 5 + 16 files changed, 843 insertions(+), 43 deletions(-) create mode 100644 src/Umbraco.Core/Models/Membership/IMemberType.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/MemberFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs index c8cba83a37..3e61b98510 100644 --- a/src/Umbraco.Core/Models/IContentType.cs +++ b/src/Umbraco.Core/Models/IContentType.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { diff --git a/src/Umbraco.Core/Models/Membership/IMember.cs b/src/Umbraco.Core/Models/Membership/IMember.cs index 2cd41c8a5d..b98c6e40a0 100644 --- a/src/Umbraco.Core/Models/Membership/IMember.cs +++ b/src/Umbraco.Core/Models/Membership/IMember.cs @@ -1,8 +1,23 @@ -namespace Umbraco.Core.Models.Membership +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.Membership { internal interface IMember : IMembershipUser, IMemberProfile { new int Id { get; set; } + + ///

+ /// Guid Id of the curent Version + /// + [DataMember] + Guid Version { get; } + + /// + /// String alias of the default ContentType + /// + [DataMember] + string ContentTypeAlias { get; } } internal interface IMemberProfile : IProfile diff --git a/src/Umbraco.Core/Models/Membership/IMemberType.cs b/src/Umbraco.Core/Models/Membership/IMemberType.cs new file mode 100644 index 0000000000..79e911d5d7 --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/IMemberType.cs @@ -0,0 +1,36 @@ +namespace Umbraco.Core.Models.Membership +{ + /// + /// Defines a ContentType, which Member is based on + /// + public interface IMemberType : IContentTypeComposition + { + /// + /// Gets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to check + /// + bool MemberCanEditProperty(string propertyTypeAlias); + + /// + /// Gets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool MemberCanViewProperty(string propertyTypeAlias); + + /// + /// Sets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetMemberCanEditProperty(string propertyTypeAlias, bool value); + + /// + /// Sets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetMemberCanViewProperty(string propertyTypeAlias, bool value); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/Member.cs b/src/Umbraco.Core/Models/Membership/Member.cs index eb21ea7bad..80bb5fd7ee 100644 --- a/src/Umbraco.Core/Models/Membership/Member.cs +++ b/src/Umbraco.Core/Models/Membership/Member.cs @@ -440,6 +440,27 @@ namespace Umbraco.Core.Models.Membership #region Internal methods + /// + /// Method to call when Entity is being saved + /// + /// Created date is set and a Unique key is assigned + internal void AddingEntity() + { + CreateDate = DateTime.Now; + UpdateDate = DateTime.Now; + + if (Key == Guid.Empty) + Key = Guid.NewGuid(); + } + + /// + /// Method to call on entity saved/updated + /// + internal virtual void UpdatingEntity() + { + UpdateDate = DateTime.Now; + } + internal virtual void ResetIdentity() { _hasIdentity = false; diff --git a/src/Umbraco.Core/Models/Membership/MemberType.cs b/src/Umbraco.Core/Models/Membership/MemberType.cs index d6baa03e68..4ffa5713ee 100644 --- a/src/Umbraco.Core/Models/Membership/MemberType.cs +++ b/src/Umbraco.Core/Models/Membership/MemberType.cs @@ -1,20 +1,129 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models.Membership { /// - /// Represents the Type for an Umbraco Member + /// Represents the content type that a object is based on /// - /// - /// Should be internal until a proper user/membership implementation - /// is part of the roadmap. - /// [Serializable] [DataContract(IsReference = true)] - internal class MemberType : Entity + public class MemberType : ContentTypeCompositionBase, IMemberType { - + //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId + private IDictionary> _memberTypePropertyTypes; + + /// + /// Constuctor for creating a ContentType with the parent's id. + /// + /// You usually only want to use this for creating ContentTypes at the root. + /// + public MemberType(int parentId) : base(parentId) + { + _memberTypePropertyTypes = new Dictionary>(); + } + + /// + /// Constuctor for creating a ContentType with the parent as an inherited type. + /// + /// Use this to ensure inheritance from parent. + /// + public MemberType(IContentType parent) + : base(parent) + { + _memberTypePropertyTypes = new Dictionary>(); + } + + private static readonly PropertyInfo MemberTypePropertyTypesSelector = ExpressionHelper.GetPropertyInfo>>(x => x.MemberTypePropertyTypes); + + /// + /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias. + /// + [DataMember] + internal IDictionary> MemberTypePropertyTypes + { + get { return _memberTypePropertyTypes; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _memberTypePropertyTypes = value; + return _memberTypePropertyTypes; + }, _memberTypePropertyTypes, MemberTypePropertyTypesSelector); + } + } + + /// + /// Gets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to check + /// + public bool MemberCanEditProperty(string propertyTypeAlias) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + return MemberTypePropertyTypes[propertyTypeAlias].Item1; + } + + return false; + } + + /// + /// Gets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + public bool MemberCanViewProperty(string propertyTypeAlias) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + return MemberTypePropertyTypes[propertyTypeAlias].Item2; + } + + return false; + } + + /// + /// Sets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + var tuple = MemberTypePropertyTypes[propertyTypeAlias]; + MemberTypePropertyTypes[propertyTypeAlias] = new Tuple(value, tuple.Item2, tuple.Item3); + } + else + { + var propertyType = PropertyTypes.First(x => x.Alias.Equals(propertyTypeAlias)); + var tuple = new Tuple(value, false, propertyType.Id); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } + + /// + /// Sets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + var tuple = MemberTypePropertyTypes[propertyTypeAlias]; + MemberTypePropertyTypes[propertyTypeAlias] = new Tuple(tuple.Item1, value, tuple.Item3); + } + else + { + var propertyType = PropertyTypes.First(x => x.Alias.Equals(propertyTypeAlias)); + var tuple = new Tuple(false, value, propertyType.Id); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs index 32e03f75d0..e5f7b3f17c 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs @@ -28,5 +28,8 @@ namespace Umbraco.Core.Models.Rdbms [Length(1000)] [Constraint(Default = "''")] public string Password { get; set; } + + [ResultColumn] + public ContentVersionDto ContentVersionDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs new file mode 100644 index 0000000000..b22a074bca --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs @@ -0,0 +1,93 @@ +using System; +using System.Globalization; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class MemberFactory : IEntityFactory + { + private readonly Guid _nodeObjectTypeId; + private readonly int _id; + private int _primaryKey; + + public MemberFactory(Guid nodeObjectTypeId, int id) + { + _nodeObjectTypeId = nodeObjectTypeId; + _id = id; + } + + public IMember BuildEntity(MemberDto dto) + { + throw new System.NotImplementedException(); + } + + public MemberDto BuildDto(IMember entity) + { + var member = new MemberDto + { + NodeId = entity.Id, + Email = entity.Email, + LoginName = entity.Username, + Password = entity.Password, + ContentVersionDto = BuildDto(entity as Member) + }; + return member; + } + + public void SetPrimaryKey(int primaryKey) + { + _primaryKey = primaryKey; + } + + private ContentVersionDto BuildDto(Member entity) + { + var dto = new ContentVersionDto + { + NodeId = entity.Id, + VersionDate = entity.UpdateDate, + VersionId = entity.Version, + ContentDto = BuildContentDto(entity) + }; + return dto; + } + + private ContentDto BuildContentDto(Member entity) + { + var contentDto = new ContentDto + { + NodeId = entity.Id, + ContentTypeId = entity.ContentTypeId, + NodeDto = BuildNodeDto(entity) + }; + + if (_primaryKey > 0) + { + contentDto.PrimaryKey = _primaryKey; + } + + return contentDto; + } + + private NodeDto BuildNodeDto(IUmbracoEntity entity) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = _nodeObjectTypeId, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = entity.Trashed, + UniqueId = entity.Key, + UserId = entity.CreatorId + }; + + return nodeDto; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs new file mode 100644 index 0000000000..604579ec0f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class MemberTypeFactory : IEntityFactory + { + private readonly Guid _nodeObjectType; + + public MemberTypeFactory(Guid nodeObjectType) + { + _nodeObjectType = nodeObjectType; + } + + public IMemberType BuildEntity(ContentTypeDto dto) + { + throw new System.NotImplementedException(); + } + + public ContentTypeDto BuildDto(IMemberType entity) + { + var contentTypeDto = new ContentTypeDto + { + Alias = entity.Alias, + Description = entity.Description, + Icon = entity.Icon, + Thumbnail = entity.Thumbnail, + NodeId = entity.Id, + AllowAtRoot = entity.AllowedAsRoot, + IsContainer = entity.IsContainer, + NodeDto = BuildNodeDto(entity) + }; + return contentTypeDto; + } + + public IEnumerable BuildMemberTypeDtos(IMemberType entity) + { + var memberType = entity as MemberType; + if (memberType == null || memberType.MemberTypePropertyTypes.Any() == false) + return Enumerable.Empty(); + + return memberType.MemberTypePropertyTypes.Select(propertyType => new MemberTypeDto + { + NodeId = entity.Id, + CanEdit = propertyType.Value.Item1, + ViewOnProfile = propertyType.Value.Item2, + PropertyTypeId = propertyType.Value.Item3 + }).ToList(); + } + + private NodeDto BuildNodeDto(IMemberType entity) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = _nodeObjectType, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = false, + UniqueId = entity.Key, + UserId = entity.CreatorId + }; + return nodeDto; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs index bb6e7e1423..943b42b2ba 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs index 4a7befff00..70e4b5c861 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs new file mode 100644 index 0000000000..27fb42a659 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs @@ -0,0 +1,57 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof (MemberType))] + [MapperFor(typeof (IMemberType))] + public sealed class MemberTypeMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = + new ConcurrentDictionary(); + + //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it + // otherwise that would fail because there is no public constructor. + public MemberTypeMapper() + { + BuildMap(); + } + + #region Overrides of BaseMapper + + internal override ConcurrentDictionary PropertyInfoCache + { + get { return PropertyInfoCacheInstance; } + } + + internal override void BuildMap() + { + if (PropertyInfoCache.IsEmpty) + { + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.Level, dto => dto.Level); + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.Path, dto => dto.Path); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.CreatorId, dto => dto.UserId); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); + CacheMap(src => src.Description, dto => dto.Description); + CacheMap(src => src.Icon, dto => dto.Icon); + CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index 616e10286b..eb5d8fb049 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -1,9 +1,15 @@ -using Umbraco.Core.Models.Membership; +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Persistence.Repositories { internal interface IMemberRepository : IRepositoryVersionable { - + /// + /// Get all members in a specific group + /// + /// + /// + IEnumerable GetByMemberGroup(string groupName); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs new file mode 100644 index 0000000000..c4d9ae9d48 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IMemberTypeRepository : IRepositoryQueryable + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index b684db9724..a999cc8baa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -1,6 +1,10 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; @@ -21,16 +25,6 @@ namespace Umbraco.Core.Persistence.Repositories { } - /* The following methods might be relevant for this specific repository (in an optimized form) - * GetById - get a member by its integer Id - * GetByKey - get a member by its unique guid Id (which should correspond to a membership provider user's id) - * GetByUsername - get a member by its username - * - * GetByPropertyValue - get members with a certain property value (supply both property alias and value?) - * GetByMemberTypeAlias - get all members of a certain type - * GetByMemberGroup - get all members in a specific group - */ - #region Overrides of RepositoryBase protected override IMember PerformGet(int id) @@ -39,11 +33,11 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where(GetBaseWhereClause(), new { Id = id }); sql.OrderByDescending(x => x.VersionDate); - var dto = + var dtos = Database.Fetch( new PropertyDataRelator().Map, sql); - return BuildFromDto(dto); + return BuildFromDto(dtos); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -86,20 +80,20 @@ namespace Umbraco.Core.Persistence.Repositories protected override Sql GetBaseQuery(bool isCount) { + var sql = new Sql(); + if (isCount) { - var sqlCount = new Sql() - .Select("COUNT(*)") + sql.Select("COUNT(*)") .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) .InnerJoin().On(left => left.NodeId, right => right.NodeId) .InnerJoin().On(left => left.NodeId, right => right.NodeId) .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sqlCount; + return sql; } - var sql = new Sql(); sql.Select("umbracoNode.*", "cmsContent.contentType", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", "cmsContentVersion.VersionDate", "cmsContentVersion.LanguageLocale", "cmsMember.Email", "cmsMember.LoginName", "cmsMember.Password", "cmsPropertyData.id AS PropertyDataId", "cmsPropertyData.propertytypeid", @@ -129,7 +123,7 @@ namespace Umbraco.Core.Persistence.Repositories protected Sql GetSubquery() { var sql = new Sql(); - sql.Select("umbracoNode.id") + sql.Select("DISTINCT(umbracoNode.id)") .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) @@ -143,6 +137,16 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } + protected Sql GetMemberGroupSubquery() + { + var sql = new Sql(); + sql.Select("DISTINCT(cmsMember2MemberGroup.Member)") + .From() + .InnerJoin().On(left => left.NodeId, right => right.MemberGroup) + .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.MemberGroup)); + return sql; + } + protected override IEnumerable GetDeleteClauses() { var list = new List @@ -173,30 +177,227 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistNewItem(IMember entity) { - throw new NotImplementedException(); + ((Member)entity).AddingEntity(); + + var factory = new MemberFactory(NodeObjectTypeId, entity.Id); + var dto = factory.BuildDto(entity); + + //NOTE Should the logic below have some kind of fallback for empty parent ids ? + //Logic for setting Path, Level and SortOrder + var parent = Database.First("WHERE id = @ParentId", new { ParentId = ((IUmbracoEntity)entity).ParentId }); + int level = parent.Level + 1; + int sortOrder = + Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = ((IUmbracoEntity)entity).ParentId, NodeObjectType = NodeObjectTypeId }); + + //Create the (base) node data - umbracoNode + var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + nodeDto.Path = parent.Path; + nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); + nodeDto.SortOrder = sortOrder; + var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + + //Update with new correct path + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + Database.Update(nodeDto); + + //Update entity with correct values + entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set + ((IUmbracoEntity)entity).Path = nodeDto.Path; + ((IUmbracoEntity)entity).SortOrder = sortOrder; + ((IUmbracoEntity)entity).Level = level; + + //Create the Content specific data - cmsContent + var contentDto = dto.ContentVersionDto.ContentDto; + contentDto.NodeId = nodeDto.NodeId; + Database.Insert(contentDto); + + //Create the first version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + dto.ContentVersionDto.NodeId = nodeDto.NodeId; + Database.Insert(dto.ContentVersionDto); + + //Create the first entry in cmsMember + dto.NodeId = nodeDto.NodeId; + Database.Insert(dto); + + //TODO ContentType for the Member entity + + //Create the PropertyData for this version - cmsPropertyData + /*var propertyFactory = new PropertyFactory(entity.ContentType, entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(((Member)entity).Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + + //Update Properties with its newly set Id + foreach (var property in ((Member)entity).Properties) + { + property.Id = keyDictionary[property.PropertyTypeId]; + }*/ + + ((Member)entity).ResetDirtyProperties(); } protected override void PersistUpdatedItem(IMember entity) { - throw new NotImplementedException(); + //Updates Modified date + ((Member)entity).UpdatingEntity(); + + //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed + if (((ICanBeDirty)entity).IsPropertyDirty("ParentId")) + { + var parent = Database.First("WHERE id = @ParentId", new { ParentId = ((IUmbracoEntity)entity).ParentId }); + ((IUmbracoEntity)entity).Path = string.Concat(parent.Path, ",", entity.Id); + ((IUmbracoEntity)entity).Level = parent.Level + 1; + var maxSortOrder = + Database.ExecuteScalar( + "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = ((IUmbracoEntity)entity).ParentId, NodeObjectType = NodeObjectTypeId }); + ((IUmbracoEntity)entity).SortOrder = maxSortOrder + 1; + } + + var factory = new MemberFactory(NodeObjectTypeId, entity.Id); + //Look up Content entry to get Primary for updating the DTO + var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { Id = entity.Id }); + factory.SetPrimaryKey(contentDto.PrimaryKey); + var dto = factory.BuildDto(entity); + + //Updates the (base) node data - umbracoNode + var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + var o = Database.Update(nodeDto); + + //Only update this DTO if the contentType has actually changed + if (contentDto.ContentTypeId != ((Member)entity).ContentTypeId) + { + //Create the Content specific data - cmsContent + var newContentDto = dto.ContentVersionDto.ContentDto; + Database.Update(newContentDto); + } + + //In order to update the ContentVersion we need to retreive its primary key id + var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { Version = entity.Version }); + dto.ContentVersionDto.Id = contentVerDto.Id; + //Updates the current version - cmsContentVersion + //Assumes a Version guid exists and Version date (modified date) has been set/updated + Database.Update(dto.ContentVersionDto); + //Updates the cmsMember entry + Database.Update(dto); + + //TODO ContentType for the Member entity + + //Create the PropertyData for this version - cmsPropertyData + /*var propertyFactory = new PropertyFactory(entity.ContentType, entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(((Member)entity).Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + if (propertyDataDto.Id > 0) + { + Database.Update(propertyDataDto); + } + else + { + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + } + + //Update Properties with its newly set Id + if (keyDictionary.Any()) + { + foreach (var property in ((Member)entity).Properties) + { + property.Id = keyDictionary[property.PropertyTypeId]; + } + }*/ + + ((ICanBeDirty)entity).ResetDirtyProperties(); } - + + protected override void PersistDeletedItem(IMember entity) + { + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + var uploadFieldId = new Guid(Constants.PropertyEditors.UploadField); + //Loop through properties to check if the media item contains images/file that should be deleted + foreach (var property in ((Member)entity).Properties) + { + if (property.PropertyType.DataTypeId == uploadFieldId && + string.IsNullOrEmpty(property.Value.ToString()) == false + && fs.FileExists(IOHelper.MapPath(property.Value.ToString()))) + { + var relativeFilePath = fs.GetRelativePath(property.Value.ToString()); + var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); + + // don't want to delete the media folder if not using directories. + if (UmbracoSettings.UploadAllowDirectories && parentDirectory != fs.GetRelativePath("/")) + { + //issue U4-771: if there is a parent directory the recursive parameter should be true + fs.DeleteDirectory(parentDirectory, String.IsNullOrEmpty(parentDirectory) == false); + } + else + { + fs.DeleteFile(relativeFilePath, true); + } + } + } + + base.PersistDeletedItem(entity); + } + #endregion #region Overrides of VersionableRepositoryBase public override IMember GetByVersion(Guid versionId) { - throw new NotImplementedException(); + var sql = GetBaseQuery(false); + sql.Where(x => x.VersionId == versionId); + sql.OrderByDescending(x => x.VersionDate); + + var dtos = + Database.Fetch( + new PropertyDataRelator().Map, sql); + + return BuildFromDto(dtos); } protected override void PerformDeleteVersion(int id, Guid versionId) { - throw new NotImplementedException(); + Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); } #endregion + /// + /// Get all members in a specific group + /// + /// + /// + public IEnumerable GetByMemberGroup(string groupName) + { + var subquery = GetSubquery().Where(x => x.Text == groupName); + var sql = GetBaseQuery(false) + .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + var dtos = + Database.Fetch( + new PropertyDataRelator().Map, sql); + + return BuildFromDtos(dtos); + } + private IMember BuildFromDto(List dtos) { if (dtos == null || dtos.Any() == false) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index eb971eec56..536d029ce0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -1,7 +1,183 @@ -namespace Umbraco.Core.Persistence.Repositories +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories { - public class MemberTypeRepository + /// + /// Represents a repository for doing CRUD operations for + /// + internal class MemberTypeRepository : ContentTypeBaseRepository, IMemberTypeRepository { - + public MemberTypeRepository(IDatabaseUnitOfWork work) + : base(work) + { + } + + public MemberTypeRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) + : base(work, cache) + { + } + + #region Overrides of RepositoryBase + + protected override IMemberType PerformGet(int id) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + + if (isCount) + { + sql.Select("COUNT(*)") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.Alias", "cmsPropertyType.Name", + "cmsPropertyType.Description", "cmsPropertyType.helpText", "cmsPropertyType.mandatory", + "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder", + "cmsPropertyType.propertyTypeGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", + "cmsDataType.controlId", "cmsDataType.dbType", "cmsPropertyTypeGroup.text AS PropertyGroupName", + "cmsPropertyTypeGroup.parentGroupId", "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.NodeId) + .InnerJoin().On(left => left.PropertyTypeId, right => right.Id) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + protected Sql GetSubquery() + { + var sql = new Sql() + .Select("DISTINCT(umbracoNode.id)") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.NodeId) + .InnerJoin().On(left => left.PropertyTypeId, right => right.Id) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id", + "DELETE FROM cmsContentTypeAllowedContentType WHERE AllowedId = @Id", + "DELETE FROM cmsContentType2ContentType WHERE parentContentTypeId = @Id", + "DELETE FROM cmsContentType2ContentType WHERE childContentTypeId = @Id", + "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id", + "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id", + "DELETE FROM cmsMemberType WHERE NodeId = @Id", + "DELETE FROM cmsContentType WHERE NodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { return new Guid(Constants.ObjectTypes.MemberType); } + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistNewItem(IMemberType entity) + { + ((MemberType)entity).AddingEntity(); + + var factory = new MemberTypeFactory(NodeObjectTypeId); + var dto = factory.BuildDto(entity); + + PersistNewBaseContentType(dto, entity); + + //Handles the MemberTypeDto (cmsMemberType table) + var memberTypeDtos = factory.BuildMemberTypeDtos(entity); + foreach (var memberTypeDto in memberTypeDtos) + { + Database.Insert(memberTypeDto); + } + + ((ICanBeDirty)entity).ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IMemberType entity) + { + //Updates Modified date + ((MemberType)entity).UpdatingEntity(); + + //Look up parent to get and set the correct Path if ParentId has changed + if (((ICanBeDirty)entity).IsPropertyDirty("ParentId")) + { + var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + entity.Path = string.Concat(parent.Path, ",", entity.Id); + entity.Level = parent.Level + 1; + var maxSortOrder = + Database.ExecuteScalar( + "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); + entity.SortOrder = maxSortOrder + 1; + } + + var factory = new MemberTypeFactory(NodeObjectTypeId); + var dto = factory.BuildDto(entity); + + PersistUpdatedBaseContentType(dto, entity); + + //Remove existing entries before inserting new ones + Database.Delete("WHERE NodeId = @Id", new {Id = entity.Id}); + + //Handles the MemberTypeDto (cmsMemberType table) + var memberTypeDtos = factory.BuildMemberTypeDtos(entity); + foreach (var memberTypeDto in memberTypeDtos) + { + Database.Insert(memberTypeDto); + } + + ((ICanBeDirty)entity).ResetDirtyProperties(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c20863a8e0..fc68311fda 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -201,6 +201,7 @@ + @@ -249,7 +250,9 @@ + + @@ -266,6 +269,7 @@ + @@ -492,6 +496,7 @@ + From 0196afa12acaf892e64b50bfae1faf778a3e55f7 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 30 Aug 2013 17:24:42 +0200 Subject: [PATCH 14/45] Correcting the update of the cmsContentVersion table so the UpdateDate is updated properly --- src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 80c719596d..65dbdb79a2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -289,6 +289,9 @@ namespace Umbraco.Core.Persistence.Repositories Database.Update(newContentDto); } + //In order to update the ContentVersion we need to retreive its primary key id + var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { Version = entity.Version }); + dto.Id = contentVerDto.Id; //Updates the current version - cmsContentVersion //Assumes a Version guid exists and Version date (modified date) has been set/updated Database.Update(dto); From ea76c42c7bbd15220c0c9ac408cd0d4af9737bfe Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Sat, 31 Aug 2013 18:02:10 +0200 Subject: [PATCH 15/45] Updating Resolves_Assigned_Mappers test to reflect current number of mappers --- src/Umbraco.Tests/PluginManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/PluginManagerTests.cs b/src/Umbraco.Tests/PluginManagerTests.cs index 0e3abc4710..9544845041 100644 --- a/src/Umbraco.Tests/PluginManagerTests.cs +++ b/src/Umbraco.Tests/PluginManagerTests.cs @@ -268,7 +268,7 @@ namespace Umbraco.Tests public void Resolves_Assigned_Mappers() { var foundTypes1 = PluginManager.Current.ResolveAssignedMapperTypes(); - Assert.AreEqual(17, foundTypes1.Count()); + Assert.AreEqual(19, foundTypes1.Count()); } [Test] From 757283a3ff1976ddf66bdce8ae6fc498726fd924 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Sat, 31 Aug 2013 19:09:29 +0200 Subject: [PATCH 16/45] Making explicit references to the legacy member class to avoid ambiguous references to the new member classes --- src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs | 1 + src/Umbraco.Web/Search/ExamineEvents.cs | 1 + src/Umbraco.Web/UmbracoHelper.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index adec7b6f85..cb1dd65029 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -12,6 +12,7 @@ using System.Linq; using umbraco.cms.businesslogic.web; using Content = Umbraco.Core.Models.Content; using Macro = umbraco.cms.businesslogic.macro.Macro; +using Member = umbraco.cms.businesslogic.member.Member; using Template = umbraco.cms.businesslogic.template.Template; namespace Umbraco.Web.Cache diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index 0a4e64e332..de120db8ac 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -18,6 +18,7 @@ using umbraco.cms.businesslogic.member; using umbraco.interfaces; using Content = umbraco.cms.businesslogic.Content; using Document = umbraco.cms.businesslogic.web.Document; +using Member = umbraco.cms.businesslogic.member.Member; namespace Umbraco.Web.Search { diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 9d84e497dd..9666506cb4 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using umbraco.cms.businesslogic.member; using umbraco.cms.businesslogic.web; using umbraco.presentation.templateControls; +using Member = umbraco.cms.businesslogic.member.Member; namespace Umbraco.Web { From 20b4975cfb29134a34001294d674062a8cbd851d Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Sat, 31 Aug 2013 19:13:36 +0200 Subject: [PATCH 17/45] Creating a new Member/IMember and MemberType/IMemberType class under the Umbraco.Core.Model namespace in order to move away from the weird structure that we initially ported over. Refactoring the MemberTypeRepository to use the new MemberType class. Starting the implementation of ReadOnly DTOs for MemberType. Starting the implementation of the factory for the MemberTypeReadOnlyDto. --- src/Umbraco.Core/Models/Content.cs | 2 - src/Umbraco.Core/Models/IMember.cs | 7 + src/Umbraco.Core/Models/IMemberType.cs | 36 ++ src/Umbraco.Core/Models/Member.cs | 424 ++++++++++++++++++ src/Umbraco.Core/Models/MemberType.cs | 118 +++++ .../Models/Rdbms/MemberReadOnlyDto.cs | 2 +- .../Models/Rdbms/MemberTypeReadOnlyDto.cs | 73 +++ .../Models/Rdbms/PropertyDataReadOnlyDto.cs | 2 +- .../Factories/MemberReadOnlyFactory.cs | 3 +- .../Factories/MemberTypeFactory.cs | 2 +- .../Factories/MemberTypeReadOnlyFactory.cs | 18 + .../Interfaces/IMemberTypeRepository.cs | 2 +- .../Repositories/MemberTypeRepository.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 6 + 14 files changed, 689 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Core/Models/IMember.cs create mode 100644 src/Umbraco.Core/Models/IMemberType.cs create mode 100644 src/Umbraco.Core/Models/Member.cs create mode 100644 src/Umbraco.Core/Models/MemberType.cs create mode 100644 src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index be6fd9543a..2223d45cd8 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -1,9 +1,7 @@ using System; -using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { diff --git a/src/Umbraco.Core/Models/IMember.cs b/src/Umbraco.Core/Models/IMember.cs new file mode 100644 index 0000000000..226e3dc70d --- /dev/null +++ b/src/Umbraco.Core/Models/IMember.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Models +{ + public interface IMember : IContentBase + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IMemberType.cs b/src/Umbraco.Core/Models/IMemberType.cs new file mode 100644 index 0000000000..8f39e96801 --- /dev/null +++ b/src/Umbraco.Core/Models/IMemberType.cs @@ -0,0 +1,36 @@ +namespace Umbraco.Core.Models +{ + /// + /// Defines a MemberType, which Member is based on + /// + public interface IMemberType : IContentTypeComposition + { + /// + /// Gets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to check + /// + bool MemberCanEditProperty(string propertyTypeAlias); + + /// + /// Gets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool MemberCanViewProperty(string propertyTypeAlias); + + /// + /// Sets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetMemberCanEditProperty(string propertyTypeAlias, bool value); + + /// + /// Sets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetMemberCanViewProperty(string propertyTypeAlias, bool value); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs new file mode 100644 index 0000000000..fa7f38505c --- /dev/null +++ b/src/Umbraco.Core/Models/Member.cs @@ -0,0 +1,424 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Member object + /// + [Serializable] + [DataContract(IsReference = true)] + public class Member : ContentBase, IMember + { + private readonly IMemberType _contentType; + private string _contentTypeAlias; + private string _username; + private string _email; + private string _password; + private object _providerUserKey; + private Type _userTypeKey; + + public Member(string name, int parentId, IMemberType contentType, PropertyCollection properties) : base(name, parentId, contentType, properties) + { + Mandate.ParameterNotNull(contentType, "contentType"); + + _contentType = contentType; + } + + public Member(string name, IContentBase parent, IMemberType contentType, PropertyCollection properties) + : base(name, parent, contentType, properties) + { + Mandate.ParameterNotNull(contentType, "contentType"); + + _contentType = contentType; + } + + private static readonly PropertyInfo DefaultContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); + private static readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); + private static readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); + private static readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.Password); + private static readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); + private static readonly PropertyInfo UserTypeKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKeyType); + + /// + /// Gets or sets the Username + /// + [DataMember] + public string Username + { + get { return _username; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _username = value; + return _username; + }, _username, UsernameSelector); + } + } + + /// + /// Gets or sets the Email + /// + [DataMember] + public string Email + { + get { return _email; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _email = value; + return _email; + }, _email, EmailSelector); + } + } + + /// + /// Gets or sets the Password + /// + [DataMember] + public string Password + { + get { return _password; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _password = value; + return _password; + }, _password, PasswordSelector); + } + } + + /// + /// Gets or sets the Groups that Member is part of + /// + [DataMember] + public IEnumerable Groups { get; set; } + + /// + /// Gets or sets the Password Question + /// + /// + /// Alias: umbracoPasswordRetrievalQuestionPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public string PasswordQuestion + { + get + { + return Properties[Constants.Conventions.Member.PasswordQuestion].Value == null + ? string.Empty + : Properties[Constants.Conventions.Member.PasswordQuestion].Value.ToString(); + } + set + { + Properties[Constants.Conventions.Member.PasswordQuestion].Value = value; + } + } + + /// + /// Gets or sets the Password Answer + /// + /// + /// Alias: umbracoPasswordRetrievalAnswerPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public string PasswordAnswer + { + get + { + return Properties[Constants.Conventions.Member.PasswordAnswer].Value == null + ? string.Empty + : Properties[Constants.Conventions.Member.PasswordAnswer].Value.ToString(); + } + set + { + Properties[Constants.Conventions.Member.PasswordAnswer].Value = value; + } + } + + /// + /// Gets or set the comments for the member + /// + /// + /// Alias: umbracoCommentPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public string Comments + { + get + { + return Properties[Constants.Conventions.Member.Comments].Value == null + ? string.Empty + : Properties[Constants.Conventions.Member.Comments].Value.ToString(); + } + set + { + Properties[Constants.Conventions.Member.Comments].Value = value; + } + } + + /// + /// Gets or sets a boolean indicating whether the Member is approved + /// + /// + /// Alias: umbracoApprovePropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public bool IsApproved + { + get + { + if (Properties[Constants.Conventions.Member.IsApproved].Value == null) + return default(bool); + + if (Properties[Constants.Conventions.Member.IsApproved].Value is bool) + return (bool)Properties[Constants.Conventions.Member.IsApproved].Value; + + return (bool)Convert.ChangeType(Properties[Constants.Conventions.Member.IsApproved].Value, typeof(bool)); + } + set + { + Properties[Constants.Conventions.Member.IsApproved].Value = value; + } + } + + /// + /// Gets or sets a boolean indicating whether the Member is locked out + /// + /// + /// Alias: umbracoLockPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public bool IsLockedOut + { + get + { + if (Properties[Constants.Conventions.Member.IsLockedOut].Value == null) + return default(bool); + + if (Properties[Constants.Conventions.Member.IsLockedOut].Value is bool) + return (bool)Properties[Constants.Conventions.Member.IsLockedOut].Value; + + return (bool)Convert.ChangeType(Properties[Constants.Conventions.Member.IsLockedOut].Value, typeof(bool)); + } + set + { + Properties[Constants.Conventions.Member.IsLockedOut].Value = value; + } + } + + /// + /// Gets or sets the date for last login + /// + /// + /// Alias: umbracoLastLoginPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public DateTime LastLoginDate + { + get + { + if (Properties[Constants.Conventions.Member.LastLoginDate].Value == null) + return default(DateTime); + + if (Properties[Constants.Conventions.Member.LastLoginDate].Value is DateTime) + return (DateTime)Properties[Constants.Conventions.Member.LastLoginDate].Value; + + return (DateTime)Convert.ChangeType(Properties[Constants.Conventions.Member.LastLoginDate].Value, typeof(DateTime)); + } + set + { + Properties[Constants.Conventions.Member.LastLoginDate].Value = value; + } + } + + /// + /// Gest or sets the date for last password change + /// + /// + /// Alias: umbracoMemberLastPasswordChange + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public DateTime LastPasswordChangeDate + { + get + { + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value == null) + return default(DateTime); + + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value is DateTime) + return (DateTime)Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value; + + return (DateTime)Convert.ChangeType(Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value, typeof(DateTime)); + } + set + { + Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value = value; + } + } + + /// + /// Gets or sets the date for when Member was locked out + /// + /// + /// Alias: umbracoMemberLastLockout + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public DateTime LastLockoutDate + { + get + { + if (Properties[Constants.Conventions.Member.LastLockoutDate].Value == null) + return default(DateTime); + + if (Properties[Constants.Conventions.Member.LastLockoutDate].Value is DateTime) + return (DateTime)Properties[Constants.Conventions.Member.LastLockoutDate].Value; + + return (DateTime)Convert.ChangeType(Properties[Constants.Conventions.Member.LastLockoutDate].Value, typeof(DateTime)); + } + set + { + Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; + } + } + + /// + /// Gets or sets the number of failed password attempts. + /// This is the number of times the password was entered incorrectly upon login. + /// + /// + /// Alias: umbracoFailedPasswordAttemptsPropertyTypeAlias + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public int FailedPasswordAttempts + { + get + { + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value == null) + return default(int); + + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value is int) + return (int)Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value; + + return (int)Convert.ChangeType(Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value, typeof(int)); + } + set + { + Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; + } + } + + /// + /// String alias of the default ContentType + /// + [DataMember] + public virtual string ContentTypeAlias + { + get { return _contentTypeAlias; } + internal set + { + SetPropertyValueAndDetectChanges(o => + { + _contentTypeAlias = value; + return _contentTypeAlias; + }, _contentTypeAlias, DefaultContentTypeAliasSelector); + } + } + + /// + /// User key from the Provider. + /// + /// + /// When using standard umbraco provider this key will + /// correspond to the guid UniqueId/Key. + /// Otherwise it will the one available from the asp.net + /// membership provider. + /// + [DataMember] + internal virtual object ProviderUserKey + { + get + { + return _providerUserKey; + } + set + { + SetPropertyValueAndDetectChanges(o => + { + _providerUserKey = value; + return _providerUserKey; + }, _providerUserKey, ProviderUserKeySelector); + } + } + + /// + /// Gets or sets the type of the provider user key. + /// + /// + /// The type of the provider user key. + /// + [IgnoreDataMember] + internal Type ProviderUserKeyType + { + get + { + return _userTypeKey; + } + private set + { + SetPropertyValueAndDetectChanges(o => + { + _userTypeKey = value; + return _userTypeKey; + }, _userTypeKey, UserTypeKeySelector); + } + } + + /// + /// Sets the type of the provider user key. + /// + /// The type. + internal void SetProviderUserKeyType(Type type) + { + ProviderUserKeyType = type; + } + + /// + /// Gets the ContentType used by this content object + /// + [IgnoreDataMember] + public IMemberType ContentType + { + get { return _contentType; } + } + + public override void ChangeTrashedState(bool isTrashed, int parentId = -20) + { + throw new NotImplementedException(); + } + + /* Internal experiment - only used for mapping queries. + * Adding these to have first level properties instead of the Properties collection. + */ + internal string LongStringPropertyValue { get; set; } + internal string ShortStringPropertyValue { get; set; } + internal int IntegerropertyValue { get; set; } + internal bool BoolPropertyValue { get; set; } + internal DateTime DateTimePropertyValue { get; set; } + internal string PropertyTypeAlias { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs new file mode 100644 index 0000000000..f7c7f318b5 --- /dev/null +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the content type that a object is based on + /// + [Serializable] + [DataContract(IsReference = true)] + public class MemberType : ContentTypeCompositionBase, IMemberType + { + //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId + private IDictionary> _memberTypePropertyTypes; + + public MemberType(int parentId) : base(parentId) + { + _memberTypePropertyTypes = new Dictionary>(); + } + + public MemberType(IContentTypeComposition parent) : base(parent) + { + _memberTypePropertyTypes = new Dictionary>(); + } + + private static readonly PropertyInfo MemberTypePropertyTypesSelector = ExpressionHelper.GetPropertyInfo>>(x => x.MemberTypePropertyTypes); + + /// + /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias. + /// + [DataMember] + internal IDictionary> MemberTypePropertyTypes + { + get { return _memberTypePropertyTypes; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _memberTypePropertyTypes = value; + return _memberTypePropertyTypes; + }, _memberTypePropertyTypes, MemberTypePropertyTypesSelector); + } + } + + /// + /// Gets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to check + /// + public bool MemberCanEditProperty(string propertyTypeAlias) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + return MemberTypePropertyTypes[propertyTypeAlias].Item1; + } + + return false; + } + + /// + /// Gets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + public bool MemberCanViewProperty(string propertyTypeAlias) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + return MemberTypePropertyTypes[propertyTypeAlias].Item2; + } + + return false; + } + + /// + /// Sets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + var tuple = MemberTypePropertyTypes[propertyTypeAlias]; + MemberTypePropertyTypes[propertyTypeAlias] = new Tuple(value, tuple.Item2, tuple.Item3); + } + else + { + var propertyType = PropertyTypes.First(x => x.Alias.Equals(propertyTypeAlias)); + var tuple = new Tuple(value, false, propertyType.Id); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } + + /// + /// Sets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + { + var tuple = MemberTypePropertyTypes[propertyTypeAlias]; + MemberTypePropertyTypes[propertyTypeAlias] = new Tuple(tuple.Item1, value, tuple.Item3); + } + else + { + var propertyType = PropertyTypes.First(x => x.Alias.Equals(propertyTypeAlias)); + var tuple = new Tuple(false, value, propertyType.Id); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs index 87692344ef..30e4380cfb 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Models.Rdbms [TableName("umbracoNode")] [PrimaryKey("id")] [ExplicitColumns] - public class MemberReadOnlyDto + internal class MemberReadOnlyDto { /* from umbracoNode */ [Column("id")] diff --git a/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs new file mode 100644 index 0000000000..e3b69f4fb2 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs @@ -0,0 +1,73 @@ +using System; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoNode")] + [PrimaryKey("id")] + [ExplicitColumns] + internal class MemberTypeReadOnlyDto + { + /* from umbracoNode */ + [Column("id")] + public int NodeId { get; set; } + + [Column("trashed")] + public bool Trashed { get; set; } + + [Column("parentID")] + public int ParentId { get; set; } + + [Column("nodeUser")] + public int? UserId { get; set; } + + [Column("level")] + public short Level { get; set; } + + [Column("path")] + public string Path { get; set; } + + [Column("sortOrder")] + public int SortOrder { get; set; } + + [Column("uniqueID")] + public Guid? UniqueId { get; set; } + + [Column("text")] + public string Text { get; set; } + + [Column("nodeObjectType")] + public Guid? NodeObjectType { get; set; } + + [Column("createDate")] + public DateTime CreateDate { get; set; } + + /* cmsContentType */ + [Column("pk")] + public int PrimaryKey { get; set; } + + [Column("alias")] + public string Alias { get; set; } + + [Column("icon")] + public string Icon { get; set; } + + [Column("thumbnail")] + public string Thumbnail { get; set; } + + [Column("description")] + public string Description { get; set; } + + [Column("isContainer")] + public bool IsContainer { get; set; } + + [Column("allowAtRoot")] + public bool AllowAtRoot { get; set; } + + /* PropertyTypes */ + //TODO Add PropertyTypeDto (+MemberTypeDto and DataTypeDto as one) ReadOnly list + + /* PropertyTypeGroups */ + //TODO Add PropertyTypeGroupDto ReadOnly list + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs index f684d104ac..67254514fd 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Models.Rdbms [TableName("cmsPropertyType")] [PrimaryKey("id")] [ExplicitColumns] - public class PropertyDataReadOnlyDto + internal class PropertyDataReadOnlyDto { /* cmsPropertyType */ [Column("id")] diff --git a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs index 37bfc7fa49..5e1a8a8147 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; +using IMember = Umbraco.Core.Models.Membership.IMember; namespace Umbraco.Core.Persistence.Factories { @@ -12,7 +13,7 @@ namespace Umbraco.Core.Persistence.Factories { public IMember BuildEntity(MemberReadOnlyDto dto) { - var member = new Member() + var member = new Umbraco.Core.Models.Membership.Member() { Id = dto.NodeId, CreateDate = dto.CreateDate, diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs index 604579ec0f..d586c93b10 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Factories diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs new file mode 100644 index 0000000000..f29835b69f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -0,0 +1,18 @@ +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class MemberTypeReadOnlyFactory : IEntityFactory + { + public IMemberType BuildEntity(MemberTypeReadOnlyDto dto) + { + throw new System.NotImplementedException(); + } + + public MemberTypeReadOnlyDto BuildDto(IMemberType entity) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs index c4d9ae9d48..41194c29e3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 536d029ce0..c5e3ffac7e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Factories; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index fc68311fda..16fc03061a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -200,11 +200,16 @@ + + + + + @@ -253,6 +258,7 @@ + From 186d5157ec16fa8be51618384005bbccb6d2446f Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Mon, 2 Sep 2013 15:53:09 +0200 Subject: [PATCH 18/45] Implementing the read only side of the MemberType/MemberTypeRepository. PropertyTypes and PropertyGroups are loaded as part of the MemberType in one go. MemberTypeRepository uses the same approach as the ContentTypeRepository and MediaTypeRepository for saving/updating. Refactoring how the 9-PropertyType convention is handled. --- src/Umbraco.Core/Constants-Conventions.cs | 141 +++++++++++++++++- src/Umbraco.Core/Models/Member.cs | 14 +- src/Umbraco.Core/Models/MemberType.cs | 2 +- src/Umbraco.Core/Models/PropertyGroup.cs | 1 - .../Models/Rdbms/MemberTypeReadOnlyDto.cs | 5 + .../Rdbms/PropertyTypeGroupReadOnlyDto.cs | 22 +++ .../Models/Rdbms/PropertyTypeReadOnlyDto.cs | 58 +++++++ .../Factories/MemberTypeFactory.cs | 36 ++++- .../Factories/MemberTypeReadOnlyFactory.cs | 120 ++++++++++++++- .../PropertyTypePropertyGroupRelator.cs | 50 +++++++ .../Repositories/ContentTypeBaseRepository.cs | 12 ++ .../Repositories/MemberTypeRepository.cs | 74 +++++++-- .../Persistence/RepositoryFactory.cs | 5 + src/Umbraco.Core/Umbraco.Core.csproj | 3 + 14 files changed, 520 insertions(+), 23 deletions(-) create mode 100644 src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs create mode 100644 src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs create mode 100644 src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 6974087e28..b8161b9480 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -1,4 +1,8 @@ -namespace Umbraco.Core +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core { public static partial class Constants { @@ -157,6 +161,141 @@ public const string FailedPasswordAttempts = "umbracoFailedPasswordAttemptsPropertyTypeAlias"; public const string FailedPasswordAttemptsLabel = "Failed Password Attempts"; + + internal static Dictionary + StandardPropertyTypeStubs = new Dictionary + { + { + Comments, + new PropertyType( + new Guid( + PropertyEditors + .TextboxMultiple), + DataTypeDatabaseType + .Ntext) + { + Alias = Comments, + Name = + CommentsLabel + } + }, + { + FailedPasswordAttempts, + new PropertyType( + new Guid( + PropertyEditors + .Integer), + DataTypeDatabaseType + .Integer) + { + Alias = + FailedPasswordAttempts, + Name = + FailedPasswordAttemptsLabel + } + }, + { + IsApproved, + new PropertyType( + new Guid( + PropertyEditors + .TrueFalse), + DataTypeDatabaseType + .Integer) + { + Alias = IsApproved, + Name = + IsApprovedLabel + } + }, + { + IsLockedOut, + new PropertyType( + new Guid( + PropertyEditors + .TrueFalse), + DataTypeDatabaseType + .Integer) + { + Alias = + IsLockedOut, + Name = + IsLockedOutLabel + } + }, + { + LastLockoutDate, + new PropertyType( + new Guid( + PropertyEditors.Date), + DataTypeDatabaseType + .Date) + { + Alias = + LastLockoutDate, + Name = + LastLockoutDateLabel + } + }, + { + LastLoginDate, + new PropertyType( + new Guid( + PropertyEditors.Date), + DataTypeDatabaseType + .Date) + { + Alias = + LastLoginDate, + Name = + LastLoginDateLabel + } + }, + { + LastPasswordChangeDate, + new PropertyType( + new Guid( + PropertyEditors.Date), + DataTypeDatabaseType + .Date) + { + Alias = + LastPasswordChangeDate, + Name = + LastPasswordChangeDateLabel + } + }, + { + PasswordAnswer, + new PropertyType( + new Guid( + PropertyEditors + .Textbox), + DataTypeDatabaseType + .Nvarchar) + { + Alias = + PasswordAnswer, + Name = + PasswordAnswerLabel + } + }, + { + PasswordQuestion, + new PropertyType( + new Guid( + PropertyEditors + .Textbox), + DataTypeDatabaseType + .Nvarchar) + { + Alias = + PasswordQuestion, + Name = + PasswordQuestionLabel + } + } + }; } /// diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index fa7f38505c..b3baac8351 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -397,6 +397,18 @@ namespace Umbraco.Core.Models ProviderUserKeyType = type; } + /// + /// Method to call when Entity is being saved + /// + /// Created date is set and a Unique key is assigned + internal override void AddingEntity() + { + base.AddingEntity(); + + if (Key == Guid.Empty) + Key = Guid.NewGuid(); + } + /// /// Gets the ContentType used by this content object /// @@ -408,7 +420,7 @@ namespace Umbraco.Core.Models public override void ChangeTrashedState(bool isTrashed, int parentId = -20) { - throw new NotImplementedException(); + throw new NotImplementedException("Members can't be trashed as no Recycle Bin exists, so use of this method is invalid"); } /* Internal experiment - only used for mapping queries. diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index f7c7f318b5..834f783e09 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Models private static readonly PropertyInfo MemberTypePropertyTypesSelector = ExpressionHelper.GetPropertyInfo>>(x => x.MemberTypePropertyTypes); /// - /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias. + /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, PropertyTypeId) by the PropertyTypes' alias. /// [DataMember] internal IDictionary> MemberTypePropertyTypes diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 24792b6ab3..a7834c8b1e 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -3,7 +3,6 @@ using System.Collections.Specialized; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { diff --git a/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs index e3b69f4fb2..3694588f56 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Persistence; namespace Umbraco.Core.Models.Rdbms @@ -66,8 +67,12 @@ namespace Umbraco.Core.Models.Rdbms /* PropertyTypes */ //TODO Add PropertyTypeDto (+MemberTypeDto and DataTypeDto as one) ReadOnly list + [ResultColumn] + public List PropertyTypes { get; set; } /* PropertyTypeGroups */ //TODO Add PropertyTypeGroupDto ReadOnly list + [ResultColumn] + public List PropertyTypeGroups { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs new file mode 100644 index 0000000000..9f66813560 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs @@ -0,0 +1,22 @@ +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("cmsPropertyTypeGroup")] + [PrimaryKey("id", autoIncrement = true)] + [ExplicitColumns] + internal class PropertyTypeGroupReadOnlyDto + { + [Column("PropertyTypeGroupId")] + public int? Id { get; set; } + + [Column("parentGroupId")] + public int? ParentGroupId { get; set; } + + [Column("PropertyGroupName")] + public string Text { get; set; } + + [Column("PropertyGroupSortOrder")] + public int SortOrder { 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 new file mode 100644 index 0000000000..78e250a8b5 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs @@ -0,0 +1,58 @@ +using System; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("cmsPropertyType")] + [PrimaryKey("id")] + [ExplicitColumns] + internal class PropertyTypeReadOnlyDto + { + [Column("PropertyTypeId")] + public int? Id { get; set; } + + [Column("dataTypeId")] + public int DataTypeId { get; set; } + + [Column("contentTypeId")] + public int ContentTypeId { get; set; } + + [Column("propertyTypeGroupId")] + public int? PropertyTypeGroupId { get; set; } + + [Column("Alias")] + public string Alias { get; set; } + + [Column("Name")] + public string Name { get; set; } + + [Column("helpText")] + public string HelpText { get; set; } + + [Column("PropertyTypeSortOrder")] + public int SortOrder { get; set; } + + [Column("mandatory")] + public bool Mandatory { get; set; } + + [Column("validationRegExp")] + public string ValidationRegExp { get; set; } + + [Column("Description")] + public string Description { get; set; } + + /* cmsMemberType */ + [Column("memberCanEdit")] + public bool CanEdit { get; set; } + + [Column("viewOnProfile")] + public bool ViewOnProfile { get; set; } + + /* cmsDataType */ + [Column("controlId")] + public Guid ControlId { get; set; } + + [Column("dbType")] + public string DbType { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs index d586c93b10..f83cada0f9 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs @@ -40,16 +40,22 @@ namespace Umbraco.Core.Persistence.Factories public IEnumerable BuildMemberTypeDtos(IMemberType entity) { var memberType = entity as MemberType; - if (memberType == null || memberType.MemberTypePropertyTypes.Any() == false) + if (memberType == null || memberType.PropertyTypes.Any() == false) return Enumerable.Empty(); - return memberType.MemberTypePropertyTypes.Select(propertyType => new MemberTypeDto - { - NodeId = entity.Id, - CanEdit = propertyType.Value.Item1, - ViewOnProfile = propertyType.Value.Item2, - PropertyTypeId = propertyType.Value.Item3 - }).ToList(); + var memberTypes = new List(); + foreach (var propertyType in memberType.PropertyTypes) + { + memberTypes.Add(new MemberTypeDto + { + NodeId = entity.Id, + PropertyTypeId = propertyType.Id, + CanEdit = memberType.MemberCanEditProperty(propertyType.Alias), + ViewOnProfile = memberType.MemberCanViewProperty(propertyType.Alias) + }); + } + + return memberTypes; } private NodeDto BuildNodeDto(IMemberType entity) @@ -70,5 +76,19 @@ namespace Umbraco.Core.Persistence.Factories }; return nodeDto; } + + private int DeterminePropertyTypeId(int initialId, string alias, IEnumerable propertyTypes) + { + if (initialId == 0 || initialId == default(int)) + { + var propertyType = propertyTypes.SingleOrDefault(x => x.Alias.Equals(alias)); + if (propertyType == null) + return default(int); + + return propertyType.Id; + } + + return initialId; + } } } \ 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 f29835b69f..e9e7fe19c4 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Factories @@ -7,12 +10,125 @@ namespace Umbraco.Core.Persistence.Factories { public IMemberType BuildEntity(MemberTypeReadOnlyDto dto) { - throw new System.NotImplementedException(); + var memberType = new MemberType(dto.ParentId) + { + Alias = dto.Alias, + AllowedAsRoot = dto.AllowAtRoot, + CreateDate = dto.CreateDate, + CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0, + Description = dto.Description, + Icon = dto.Icon, + Id = dto.NodeId, + IsContainer = dto.IsContainer, + Key = dto.UniqueId.Value, + Level = dto.Level, + Name = dto.Text, + Path = dto.Path, + SortOrder = dto.SortOrder, + Thumbnail = dto.Thumbnail, + Trashed = dto.Trashed, + UpdateDate = dto.CreateDate, + AllowedContentTypes = Enumerable.Empty() + }; + + var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType); + memberType.PropertyGroups = propertyTypeGroupCollection; + + var propertyTypes = GetPropertyTypes(dto, memberType); + //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.StandardPropertyTypeStubs; + foreach (var standardPropertyType in standardPropertyTypes) + { + if(dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; + + //Add the standard PropertyType to the current list + propertyTypes.Add(standardPropertyType.Value); + + //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, + new Tuple(false, false, default(int))); + } + memberType.PropertyTypes = propertyTypes; + + return memberType; + } + + private PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType) + { + var propertyTypeGroupCollection = new PropertyGroupCollection(); + foreach (var propertyTypeGroup in dto.PropertyTypeGroups.Where(x => x.Id.HasValue)) + { + //Find PropertyTypes that belong to the current PropertyTypeGroup + var groupId = propertyTypeGroup.Id.Value; + var propertyTypesByGroup = + dto.PropertyTypes.Where( + x => x.Id.HasValue && x.PropertyTypeGroupId.HasValue && x.PropertyTypeGroupId.Value.Equals(groupId)); + //Create PropertyTypeCollection for passing into the PropertyTypeGroup, and loop through the above result to create PropertyTypes + var propertyTypeCollection = new PropertyTypeCollection(); + foreach (var propTypeDto in propertyTypesByGroup) + { + //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + memberType.MemberTypePropertyTypes.Add(propTypeDto.Alias, + new Tuple(propTypeDto.CanEdit, propTypeDto.ViewOnProfile, propTypeDto.Id.Value)); + //PropertyType Collection + propertyTypeCollection.Add(new PropertyType(propTypeDto.ControlId, + propTypeDto.DbType.EnumParse(true)) + { + Alias = propTypeDto.Alias, + DataTypeDefinitionId = propTypeDto.DataTypeId, + Description = propTypeDto.Description, + HelpText = propTypeDto.HelpText, + Id = propTypeDto.Id.Value, + Mandatory = propTypeDto.Mandatory, + Name = propTypeDto.Name, + SortOrder = propTypeDto.SortOrder, + ValidationRegExp = propTypeDto.ValidationRegExp, + PropertyGroupId = new Lazy(() => propTypeDto.PropertyTypeGroupId.Value), + CreateDate = dto.CreateDate, + UpdateDate = dto.CreateDate + }); + } + + var group = new PropertyGroup(propertyTypeCollection) {Id = groupId}; + propertyTypeGroupCollection.Add(@group); + } + return propertyTypeGroupCollection; + } + + private List GetPropertyTypes(MemberTypeReadOnlyDto dto, MemberType memberType) + { + //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.Id.HasValue)) + { + //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + memberType.MemberTypePropertyTypes.Add(propertyType.Alias, + new Tuple(propertyType.CanEdit, propertyType.ViewOnProfile, propertyType.Id.Value)); + //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 + }); + } + return propertyTypes; } public MemberTypeReadOnlyDto BuildDto(IMemberType entity) { throw new System.NotImplementedException(); } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs b/src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs new file mode 100644 index 0000000000..e16a61db6f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Relators +{ + internal class PropertyTypePropertyGroupRelator + { + internal MemberTypeReadOnlyDto Current; + + internal MemberTypeReadOnlyDto Map(MemberTypeReadOnlyDto a, PropertyTypeReadOnlyDto p, PropertyTypeGroupReadOnlyDto g) + { + // Terminating call. Since we can return null from this function + // we need to be ready for PetaPoco to callback later with null + // parameters + if (a == null) + return Current; + + // Is this the same MemberTypeReadOnlyDto as the current one we're processing + if (Current != null && Current.UniqueId == a.UniqueId) + { + // Yes, just add this PropertyTypeReadOnlyDto to the current MemberTypeReadOnlyDto's collection + Current.PropertyTypes.Add(p); + + if(g.Id.HasValue) + Current.PropertyTypeGroups.Add(g); + + // Return null to indicate we're not done with this MemberTypeReadOnlyDto yet + return null; + } + + // This is a different MemberTypeReadOnlyDto to the current one, or this is the + // first time through and we don't have a Tab yet + + // Save the current MemberTypeReadOnlyDto + var prev = Current; + + // Setup the new current MemberTypeReadOnlyDto + Current = a; + Current.PropertyTypes = new List(); + Current.PropertyTypes.Add(p); + + Current.PropertyTypeGroups = new List(); + if (g.Id.HasValue) + Current.PropertyTypeGroups.Add(g); + + // Return the now populated previous MemberTypeReadOnlyDto (or null if first time through) + return prev; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 995be6e7b2..e3dd5d1301 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -155,6 +155,12 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var propertyType in entity.PropertyTypes) { var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); + //If the Id of the DataType is not set, we resolve it from the db by its ControlId + if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) + { + var datatype = Database.FirstOrDefault("WHERE controlId = @Id", new { Id = propertyType.DataTypeId }); + propertyType.DataTypeDefinitionId = datatype.DataTypeId; + } var propertyTypeDto = propertyFactory.BuildPropertyTypeDto(tabId, propertyType); int typePrimaryKey = Convert.ToInt32(Database.Insert(propertyTypeDto)); propertyType.Id = typePrimaryKey; //Set Id on new PropertyType @@ -306,6 +312,12 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var propertyType in entity.PropertyTypes) { var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); + //If the Id of the DataType is not set, we resolve it from the db by its ControlId + if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) + { + var datatype = Database.FirstOrDefault("WHERE controlId = @Id", new { Id = propertyType.DataTypeId }); + propertyType.DataTypeDefinitionId = datatype.DataTypeId; + } var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(tabId, propertyType); int typePrimaryKey = propertyType.HasIdentity ? Database.Update(propertyTypeDto) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index c5e3ffac7e..305f828c2d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories @@ -27,19 +29,56 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of RepositoryBase - protected override IMemberType PerformGet(int id) + protected override IMemberType PerformGet(int id) { - throw new NotImplementedException(); + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.OrderByDescending(x => x.NodeId); + + var dtos = + Database.Fetch( + new PropertyTypePropertyGroupRelator().Map, sql); + + if (dtos == null || dtos.Any() == false) + return null; + + var factory = new MemberTypeReadOnlyFactory(); + var member = factory.BuildEntity(dtos.First()); + + return member; } protected override IEnumerable PerformGetAll(params int[] ids) { - throw new NotImplementedException(); + var sql = GetBaseQuery(false); + if (ids.Any()) + { + var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.id='{0}'", x))); + sql.Where(statement); + } + sql.OrderByDescending(x => x.NodeId); + + var dtos = + Database.Fetch( + new PropertyTypePropertyGroupRelator().Map, sql); + + return BuildFromDtos(dtos); } protected override IEnumerable PerformGetByQuery(IQuery query) { - throw new NotImplementedException(); + var sqlSubquery = GetSubquery(); + var translator = new SqlTranslator(sqlSubquery, query); + var subquery = translator.Translate(); + var sql = GetBaseQuery(false) + .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)) + .OrderBy(x => x.SortOrder); + + var dtos = + Database.Fetch( + new PropertyTypePropertyGroupRelator().Map, sql); + + return BuildFromDtos(dtos); } #endregion @@ -59,12 +98,13 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.Alias", "cmsPropertyType.Name", - "cmsPropertyType.Description", "cmsPropertyType.helpText", "cmsPropertyType.mandatory", - "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder", + 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", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", - "cmsDataType.controlId", "cmsDataType.dbType", "cmsPropertyTypeGroup.text AS PropertyGroupName", - "cmsPropertyTypeGroup.parentGroupId", "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder") + "cmsDataType.controlId", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", + "cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.parentGroupId", + "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder") .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) .LeftJoin().On(left => left.ContentTypeId, right => right.NodeId) @@ -127,6 +167,13 @@ namespace Umbraco.Core.Persistence.Repositories { ((MemberType)entity).AddingEntity(); + //By Convention we add 9 stnd PropertyTypes to an Umbraco MemberType + var standardPropertyTypes = Constants.Conventions.Member.StandardPropertyTypeStubs; + foreach (var standardPropertyType in standardPropertyTypes) + { + entity.AddPropertyType(standardPropertyType.Value); + } + var factory = new MemberTypeFactory(NodeObjectTypeId); var dto = factory.BuildDto(entity); @@ -179,5 +226,14 @@ namespace Umbraco.Core.Persistence.Repositories } #endregion + + private IEnumerable BuildFromDtos(List dtos) + { + if (dtos == null || dtos.Any() == false) + return Enumerable.Empty(); + + var factory = new MemberTypeReadOnlyFactory(); + return dtos.Select(factory.BuildEntity); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 36d035244e..a94fd0a59e 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -120,6 +120,11 @@ namespace Umbraco.Core.Persistence return new MemberRepository(uow, RuntimeCacheProvider.Current); } + internal virtual IMemberTypeRepository CreateMemberTypeRepository(IDatabaseUnitOfWork uow) + { + return new MemberTypeRepository(uow, InMemoryCacheProvider.Current); + } + internal virtual IEntityRepository CreateEntityRepository(IDatabaseUnitOfWork uow) { return new EntityRepository(uow); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 16fc03061a..6d90a3cbc9 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -211,6 +211,8 @@ + + @@ -485,6 +487,7 @@ + From 25d2b56ed7c75599d6bd370353460ae9cec674f2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 3 Sep 2013 10:31:00 +0200 Subject: [PATCH 19/45] U4-2776 Default Content For A Razor Layout Section --- src/Umbraco.Web/Mvc/UmbracoViewPage.cs | 56 ++++++++++++++++-------- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/WebViewPageExtensions.cs | 23 ++++++++++ 3 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 src/Umbraco.Web/WebViewPageExtensions.cs diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPage.cs b/src/Umbraco.Web/Mvc/UmbracoViewPage.cs index fa22cf419b..39b9396e01 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPage.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPage.cs @@ -1,6 +1,7 @@ using System; using System.Text; using System.Web.Mvc; +using System.Web.WebPages; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -97,24 +98,24 @@ namespace Umbraco.Web.Mvc get { return _helper ?? (_helper = new UmbracoHelper(UmbracoContext)); } } - /// - /// Ensure that the current view context is added to the route data tokens so we can extract it if we like - /// - /// - /// Currently this is required by mvc macro engines - /// - protected override void InitializePage() - { - base.InitializePage(); - if (!ViewContext.IsChildAction) - { - if (!ViewContext.RouteData.DataTokens.ContainsKey(Constants.DataTokenCurrentViewContext)) - { - ViewContext.RouteData.DataTokens.Add(Constants.DataTokenCurrentViewContext, this.ViewContext); - } - } - - } + /// + /// Ensure that the current view context is added to the route data tokens so we can extract it if we like + /// + /// + /// Currently this is required by mvc macro engines + /// + protected override void InitializePage() + { + base.InitializePage(); + if (!ViewContext.IsChildAction) + { + if (!ViewContext.RouteData.DataTokens.ContainsKey(Constants.DataTokenCurrentViewContext)) + { + ViewContext.RouteData.DataTokens.Add(Constants.DataTokenCurrentViewContext, this.ViewContext); + } + } + + } /// /// This will detect the end /body tag and insert the preview badge if in preview mode @@ -129,7 +130,7 @@ namespace Umbraco.Web.Mvc { var text = value.ToString().ToLowerInvariant(); var pos = text.IndexOf("", StringComparison.InvariantCultureIgnoreCase); - + if (pos > -1) { string markupToInject; @@ -159,6 +160,23 @@ namespace Umbraco.Web.Mvc } base.WriteLiteral(value); + + + } + + public HelperResult RenderSection(string name, Func defaultContents) + { + return WebViewPageExtensions.RenderSection(this, name, defaultContents); + } + + public HelperResult RenderSection(string name, HelperResult defaultContents) + { + return WebViewPageExtensions.RenderSection(this, name, defaultContents); + } + + public HelperResult RenderSection(string name, string defaultContents) + { + return WebViewPageExtensions.RenderSection(this, name, defaultContents); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index cddb3dc51f..ed3c6a1822 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1793,6 +1793,7 @@ Component + diff --git a/src/Umbraco.Web/WebViewPageExtensions.cs b/src/Umbraco.Web/WebViewPageExtensions.cs new file mode 100644 index 0000000000..7d54a01b39 --- /dev/null +++ b/src/Umbraco.Web/WebViewPageExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Web.WebPages; + +namespace Umbraco.Web +{ + public static class WebViewPageExtensions + { + public static HelperResult RenderSection(this WebPageBase webPage, string name, Func defaultContents) + { + return webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : defaultContents(null); + } + + public static HelperResult RenderSection(this WebPageBase webPage, string name, HelperResult defaultContents) + { + return webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : defaultContents; + } + + public static HelperResult RenderSection(this WebPageBase webPage, string name, string defaultContents) + { + return webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : new HelperResult(text => text.Write(defaultContents)); + } + } +} From 05039fdd7cd5ca7d07590fe061886c70485d483d Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Tue, 3 Sep 2013 13:32:10 +0100 Subject: [PATCH 20/45] U4-2783 url attribute in installedPackages.config is not populated when a package is installed --- src/umbraco.cms/businesslogic/Packager/Installer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index b981823267..7a8e81cbba 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -189,6 +189,7 @@ namespace umbraco.cms.businesslogic.packager string _packVersion = xmlHelper.GetNodeValue(_packageConfig.DocumentElement.SelectSingleNode("/umbPackage/info/package/version")); string _packReadme = xmlHelper.GetNodeValue(_packageConfig.DocumentElement.SelectSingleNode("/umbPackage/info/readme")); string _packLicense = xmlHelper.GetNodeValue(_packageConfig.DocumentElement.SelectSingleNode("/umbPackage/info/package/license ")); + string _packUrl = xmlHelper.GetNodeValue(_packageConfig.DocumentElement.SelectSingleNode("/umbPackage/info/package/url ")); bool _enableSkins = false; string _skinRepoGuid = ""; @@ -209,6 +210,7 @@ namespace umbraco.cms.businesslogic.packager insPack.Data.Version = _packVersion; insPack.Data.Readme = _packReadme; insPack.Data.License = _packLicense; + insPack.Data.Url = _packUrl; //skinning insPack.Data.EnableSkins = _enableSkins; From 6742bf4708e9d53daed9bc0cc3e8038e5b8ab7ec Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Tue, 3 Sep 2013 17:01:34 +0200 Subject: [PATCH 21/45] Removing the old Member, MemberType and MemberGroup classes from the Model.Membership namespace --- src/Umbraco.Core/Models/Membership/IMember.cs | 27 - .../Models/Membership/IMemberType.cs | 36 -- src/Umbraco.Core/Models/Membership/Member.cs | 472 ------------------ .../Models/Membership/MemberGroup.cs | 20 - .../Models/Membership/MemberProfile.cs | 162 ------ .../Models/Membership/MemberType.cs | 129 ----- ...coMember.cs => UmbracoMembershipMember.cs} | 6 +- src/Umbraco.Core/Umbraco.Core.csproj | 8 +- 8 files changed, 4 insertions(+), 856 deletions(-) delete mode 100644 src/Umbraco.Core/Models/Membership/IMember.cs delete mode 100644 src/Umbraco.Core/Models/Membership/IMemberType.cs delete mode 100644 src/Umbraco.Core/Models/Membership/Member.cs delete mode 100644 src/Umbraco.Core/Models/Membership/MemberGroup.cs delete mode 100644 src/Umbraco.Core/Models/Membership/MemberProfile.cs delete mode 100644 src/Umbraco.Core/Models/Membership/MemberType.cs rename src/Umbraco.Core/Models/Membership/{UmbracoMember.cs => UmbracoMembershipMember.cs} (64%) diff --git a/src/Umbraco.Core/Models/Membership/IMember.cs b/src/Umbraco.Core/Models/Membership/IMember.cs deleted file mode 100644 index b98c6e40a0..0000000000 --- a/src/Umbraco.Core/Models/Membership/IMember.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models.Membership -{ - internal interface IMember : IMembershipUser, IMemberProfile - { - new int Id { get; set; } - - /// - /// Guid Id of the curent Version - /// - [DataMember] - Guid Version { get; } - - /// - /// String alias of the default ContentType - /// - [DataMember] - string ContentTypeAlias { get; } - } - - internal interface IMemberProfile : IProfile - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/IMemberType.cs b/src/Umbraco.Core/Models/Membership/IMemberType.cs deleted file mode 100644 index 79e911d5d7..0000000000 --- a/src/Umbraco.Core/Models/Membership/IMemberType.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Umbraco.Core.Models.Membership -{ - /// - /// Defines a ContentType, which Member is based on - /// - public interface IMemberType : IContentTypeComposition - { - /// - /// Gets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to check - /// - bool MemberCanEditProperty(string propertyTypeAlias); - - /// - /// Gets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - bool MemberCanViewProperty(string propertyTypeAlias); - - /// - /// Sets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - void SetMemberCanEditProperty(string propertyTypeAlias, bool value); - - /// - /// Sets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - void SetMemberCanViewProperty(string propertyTypeAlias, bool value); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/Member.cs b/src/Umbraco.Core/Models/Membership/Member.cs deleted file mode 100644 index 80bb5fd7ee..0000000000 --- a/src/Umbraco.Core/Models/Membership/Member.cs +++ /dev/null @@ -1,472 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models.Membership -{ - /// - /// Represents an Umbraco Member - /// - /// - /// Should be internal until a proper user/membership implementation - /// is part of the roadmap. - /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}")] - internal class Member : MemberProfile, IMember - { - private bool _hasIdentity; - private int _id; - private Guid _key; - private DateTime _createDate; - private DateTime _updateDate; - private int _contentTypeId; - private string _contentTypeAlias; - - private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); - private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); - private static readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); - private static readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); - private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); - private static readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); - private static readonly PropertyInfo DefaultContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); - - public Member() - {} - - /// - /// Integer Id - /// - [DataMember] - public override int Id - { - get - { - return _id; - } - set - { - SetPropertyValueAndDetectChanges(o => - { - _id = value; - HasIdentity = true; //set the has Identity - return _id; - }, _id, IdSelector); - } - } - - /// - /// Guid based Id - /// - /// The key is currectly used to store the Unique Id from the - /// umbracoNode table, which many of the entities are based on. - [DataMember] - public override Guid Key - { - get - { - if (_key == Guid.Empty) - return _id.ToGuid(); - - return _key; - } - set - { - SetPropertyValueAndDetectChanges(o => - { - _key = value; - return _key; - }, _key, KeySelector); - } - } - - /// - /// Gets or sets the Created Date - /// - [DataMember] - public override DateTime CreateDate - { - get { return _createDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _createDate = value; - return _createDate; - }, _createDate, CreateDateSelector); - } - } - - /// - /// Gets or sets the Modified Date - /// - [DataMember] - public override DateTime UpdateDate - { - get { return _updateDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _updateDate = value; - return _updateDate; - }, _updateDate, UpdateDateSelector); - } - } - - /// - /// Indicates whether the current entity has an identity, eg. Id. - /// - [IgnoreDataMember] - public override bool HasIdentity - { - get - { - return _hasIdentity; - } - protected set - { - SetPropertyValueAndDetectChanges(o => - { - _hasIdentity = value; - return _hasIdentity; - }, _hasIdentity, HasIdentitySelector); - } - } - - /// - /// Gets or sets the Username - /// - [DataMember] - public string Username { get; set; } - - /// - /// Gets or sets the Email - /// - [DataMember] - public string Email { get; set; } - - /// - /// Gets or sets the Password - /// - [DataMember] - public string Password { get; set; } - - /// - /// Gets or sets the Password Question - /// - /// - /// Alias: umbracoPasswordRetrievalQuestionPropertyTypeAlias - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public string PasswordQuestion - { - get - { - return Properties[Constants.Conventions.Member.PasswordQuestion].Value == null - ? string.Empty - : Properties[Constants.Conventions.Member.PasswordQuestion].Value.ToString(); - } - set - { - Properties[Constants.Conventions.Member.PasswordQuestion].Value = value; - } - } - - /// - /// Gets or sets the Password Answer - /// - /// - /// Alias: umbracoPasswordRetrievalAnswerPropertyTypeAlias - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public string PasswordAnswer - { - get - { - return Properties[Constants.Conventions.Member.PasswordAnswer].Value == null - ? string.Empty - : Properties[Constants.Conventions.Member.PasswordAnswer].Value.ToString(); - } - set - { - Properties[Constants.Conventions.Member.PasswordAnswer].Value = value; - } - } - - /// - /// Gets or set the comments for the member - /// - /// - /// Alias: umbracoCommentPropertyTypeAlias - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public string Comments - { - get - { - return Properties[Constants.Conventions.Member.Comments].Value == null - ? string.Empty - : Properties[Constants.Conventions.Member.Comments].Value.ToString(); - } - set - { - Properties[Constants.Conventions.Member.Comments].Value = value; - } - } - - /// - /// Gets or sets a boolean indicating whether the Member is approved - /// - /// - /// Alias: umbracoApprovePropertyTypeAlias - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public bool IsApproved - { - get - { - if (Properties[Constants.Conventions.Member.IsApproved].Value == null) - return default(bool); - - if (Properties[Constants.Conventions.Member.IsApproved].Value is bool) - return (bool)Properties[Constants.Conventions.Member.IsApproved].Value; - - return (bool)Convert.ChangeType(Properties[Constants.Conventions.Member.IsApproved].Value, typeof(bool)); - } - set - { - Properties[Constants.Conventions.Member.IsApproved].Value = value; - } - } - - /// - /// Gets or sets a boolean indicating whether the Member is locked out - /// - /// - /// Alias: umbracoLockPropertyTypeAlias - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public bool IsLockedOut - { - get - { - if (Properties[Constants.Conventions.Member.IsLockedOut].Value == null) - return default(bool); - - if (Properties[Constants.Conventions.Member.IsLockedOut].Value is bool) - return (bool)Properties[Constants.Conventions.Member.IsLockedOut].Value; - - return (bool)Convert.ChangeType(Properties[Constants.Conventions.Member.IsLockedOut].Value, typeof(bool)); - } - set - { - Properties[Constants.Conventions.Member.IsLockedOut].Value = value; - } - } - - /// - /// Gets or sets the date for last login - /// - /// - /// Alias: umbracoLastLoginPropertyTypeAlias - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public DateTime LastLoginDate - { - get - { - if (Properties[Constants.Conventions.Member.LastLoginDate].Value == null) - return default(DateTime); - - if (Properties[Constants.Conventions.Member.LastLoginDate].Value is DateTime) - return (DateTime)Properties[Constants.Conventions.Member.LastLoginDate].Value; - - return (DateTime)Convert.ChangeType(Properties[Constants.Conventions.Member.LastLoginDate].Value, typeof(DateTime)); - } - set - { - Properties[Constants.Conventions.Member.LastLoginDate].Value = value; - } - } - - /// - /// Gest or sets the date for last password change - /// - /// - /// Alias: umbracoMemberLastPasswordChange - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public DateTime LastPasswordChangeDate - { - get - { - if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value == null) - return default(DateTime); - - if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value is DateTime) - return (DateTime)Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value; - - return (DateTime)Convert.ChangeType(Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value, typeof(DateTime)); - } - set - { - Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value = value; - } - } - - /// - /// Gets or sets the date for when Member was locked out - /// - /// - /// Alias: umbracoMemberLastLockout - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public DateTime LastLockoutDate - { - get - { - if (Properties[Constants.Conventions.Member.LastLockoutDate].Value == null) - return default(DateTime); - - if (Properties[Constants.Conventions.Member.LastLockoutDate].Value is DateTime) - return (DateTime)Properties[Constants.Conventions.Member.LastLockoutDate].Value; - - return (DateTime)Convert.ChangeType(Properties[Constants.Conventions.Member.LastLockoutDate].Value, typeof(DateTime)); - } - set - { - Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; - } - } - - /// - /// Gets or sets the number of failed password attempts. - /// This is the number of times the password was entered incorrectly upon login. - /// - /// - /// Alias: umbracoFailedPasswordAttemptsPropertyTypeAlias - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public int FailedPasswordAttempts - { - get - { - if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value == null) - return default(int); - - if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value is int) - return (int)Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value; - - return (int)Convert.ChangeType(Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value, typeof(int)); - } - set - { - Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; - } - } - - /// - /// Boolean indicating whether the user is online - /// - /// Context dependent property - [IgnoreDataMember] - public bool IsOnline { get; set; } - - /// - /// Guid Id of the curent Version - /// - [DataMember] - public Guid Version { get; internal set; } - - /// - /// Integer Id of the default ContentType - /// - [DataMember] - public virtual int ContentTypeId - { - get { return _contentTypeId; } - internal set - { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeId = value; - return _contentTypeId; - }, _contentTypeId, DefaultContentTypeIdSelector); - } - } - - /// - /// String alias of the default ContentType - /// - [DataMember] - public virtual string ContentTypeAlias - { - get { return _contentTypeAlias; } - internal set - { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeAlias = value; - return _contentTypeAlias; - }, _contentTypeAlias, DefaultContentTypeAliasSelector); - } - } - - public object ProfileId { get; set; } - public IEnumerable Groups { get; set; } - - /* Internal experiment - only used for mapping queries. - * Adding these to have first level properties instead of the Properties collection. - */ - internal string LongStringPropertyValue { get; set; } - internal string ShortStringPropertyValue { get; set; } - internal int IntegerropertyValue { get; set; } - internal bool BoolPropertyValue { get; set; } - internal DateTime DateTimePropertyValue { get; set; } - internal string PropertyTypeAlias { get; set; } - - #region Internal methods - - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal void AddingEntity() - { - CreateDate = DateTime.Now; - UpdateDate = DateTime.Now; - - if (Key == Guid.Empty) - Key = Guid.NewGuid(); - } - - /// - /// Method to call on entity saved/updated - /// - internal virtual void UpdatingEntity() - { - UpdateDate = DateTime.Now; - } - - internal virtual void ResetIdentity() - { - _hasIdentity = false; - _id = default(int); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/MemberGroup.cs b/src/Umbraco.Core/Models/Membership/MemberGroup.cs deleted file mode 100644 index 2af92a0f12..0000000000 --- a/src/Umbraco.Core/Models/Membership/MemberGroup.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models.Membership -{ - /// - /// Represents a Group for a Backoffice User - /// - /// - /// Should be internal until a proper user/membership implementation - /// is part of the roadmap. - /// - [Serializable] - [DataContract(IsReference = true)] - internal class MemberGroup : Entity - { - //Add MemberCollection ? - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/MemberProfile.cs b/src/Umbraco.Core/Models/Membership/MemberProfile.cs deleted file mode 100644 index 185c292f06..0000000000 --- a/src/Umbraco.Core/Models/Membership/MemberProfile.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models.Membership -{ - internal class MemberProfile : Profile, IUmbracoEntity - { - private Lazy _parentId; - private int _sortOrder; - private int _level; - private string _path; - private int _creatorId; - private bool _trashed; - private PropertyCollection _properties; - - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); - - protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(PropertyCollectionSelector); - } - - protected MemberProfile() - {} - - public virtual new int Id { get; set; } - public virtual Guid Key { get; set; } - public virtual DateTime CreateDate { get; set; } - public virtual DateTime UpdateDate { get; set; } - public virtual bool HasIdentity { get; protected set; } - - /// - /// Profile of the user who created this Content - /// - [DataMember] - int IUmbracoEntity.CreatorId - { - get - { - return _creatorId; - } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, CreatorIdSelector); - } - } - - /// - /// Gets or sets the level of the content entity - /// - [DataMember] - int IUmbracoEntity.Level - { - get { return _level; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _level = value; - return _level; - }, _level, LevelSelector); - } } - - /// - /// Gets or sets the Id of the Parent entity - /// - [DataMember] - int IUmbracoEntity.ParentId - { - get - { - var val = _parentId.Value; - if (val == 0) - { - throw new InvalidOperationException("The ParentId cannot have a value of 0. Perhaps the parent object used to instantiate this object has not been persisted to the data store."); - } - return val; - } - set - { - _parentId = new Lazy(() => value); - OnPropertyChanged(ParentIdSelector); - } - } - - /// - /// Gets or sets the path - /// - [DataMember] - string IUmbracoEntity.Path - { - get { return _path; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _path = value; - return _path; - }, _path, PathSelector); - } - } - - /// - /// Gets or sets the sort order of the content entity - /// - [DataMember] - int IUmbracoEntity.SortOrder - { - get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } - } - - /// - /// Boolean indicating whether this Content is Trashed or not. - /// If Content is Trashed it will be located in the Recyclebin. - /// - [DataMember] - public virtual bool Trashed - { - get { return _trashed; } - internal set - { - SetPropertyValueAndDetectChanges(o => - { - _trashed = value; - return _trashed; - }, _trashed, TrashedSelector); - } - } - - [DataMember] - public PropertyCollection Properties - { - get { return _properties; } - set - { - _properties = value; - _properties.CollectionChanged += PropertiesChanged; - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/MemberType.cs b/src/Umbraco.Core/Models/Membership/MemberType.cs deleted file mode 100644 index 4ffa5713ee..0000000000 --- a/src/Umbraco.Core/Models/Membership/MemberType.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models.Membership -{ - /// - /// Represents the content type that a object is based on - /// - [Serializable] - [DataContract(IsReference = true)] - public class MemberType : ContentTypeCompositionBase, IMemberType - { - //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId - private IDictionary> _memberTypePropertyTypes; - - /// - /// Constuctor for creating a ContentType with the parent's id. - /// - /// You usually only want to use this for creating ContentTypes at the root. - /// - public MemberType(int parentId) : base(parentId) - { - _memberTypePropertyTypes = new Dictionary>(); - } - - /// - /// Constuctor for creating a ContentType with the parent as an inherited type. - /// - /// Use this to ensure inheritance from parent. - /// - public MemberType(IContentType parent) - : base(parent) - { - _memberTypePropertyTypes = new Dictionary>(); - } - - private static readonly PropertyInfo MemberTypePropertyTypesSelector = ExpressionHelper.GetPropertyInfo>>(x => x.MemberTypePropertyTypes); - - /// - /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias. - /// - [DataMember] - internal IDictionary> MemberTypePropertyTypes - { - get { return _memberTypePropertyTypes; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _memberTypePropertyTypes = value; - return _memberTypePropertyTypes; - }, _memberTypePropertyTypes, MemberTypePropertyTypesSelector); - } - } - - /// - /// Gets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to check - /// - public bool MemberCanEditProperty(string propertyTypeAlias) - { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) - { - return MemberTypePropertyTypes[propertyTypeAlias].Item1; - } - - return false; - } - - /// - /// Gets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - public bool MemberCanViewProperty(string propertyTypeAlias) - { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) - { - return MemberTypePropertyTypes[propertyTypeAlias].Item2; - } - - return false; - } - - /// - /// Sets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) - { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) - { - var tuple = MemberTypePropertyTypes[propertyTypeAlias]; - MemberTypePropertyTypes[propertyTypeAlias] = new Tuple(value, tuple.Item2, tuple.Item3); - } - else - { - var propertyType = PropertyTypes.First(x => x.Alias.Equals(propertyTypeAlias)); - var tuple = new Tuple(value, false, propertyType.Id); - MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); - } - } - - /// - /// Sets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) - { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) - { - var tuple = MemberTypePropertyTypes[propertyTypeAlias]; - MemberTypePropertyTypes[propertyTypeAlias] = new Tuple(tuple.Item1, value, tuple.Item3); - } - else - { - var propertyType = PropertyTypes.First(x => x.Alias.Equals(propertyTypeAlias)); - var tuple = new Tuple(false, value, propertyType.Id); - MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMember.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs similarity index 64% rename from src/Umbraco.Core/Models/Membership/UmbracoMember.cs rename to src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs index b890276432..6dbf963ff5 100644 --- a/src/Umbraco.Core/Models/Membership/UmbracoMember.cs +++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs @@ -2,11 +2,11 @@ namespace Umbraco.Core.Models.Membership { - internal class UmbracoMember : MembershipUser + internal class UmbracoMembershipMember : MembershipUser { - private readonly IMembershipUser _member; + private readonly IMember _member; - public UmbracoMember(IMembershipUser member) + public UmbracoMembershipMember(IMember member) { _member = member; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6d90a3cbc9..f2dd7eb2cb 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -203,9 +203,7 @@ - - - + @@ -223,10 +221,6 @@ - - - - From c5945789f061325ce64fef2385aa2dabf1bab256 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Tue, 3 Sep 2013 17:02:15 +0200 Subject: [PATCH 22/45] Refactoring the MemberRepository to use the new Member class --- src/Umbraco.Core/Models/IMember.cs | 111 ++++++++++- src/Umbraco.Core/Models/MemberType.cs | 2 +- .../Persistence/Factories/MemberFactory.cs | 2 +- .../Factories/MemberReadOnlyFactory.cs | 185 +++--------------- .../Persistence/Mappers/MappingResolver.cs | 2 - .../Persistence/Mappers/MemberMapper.cs | 3 +- .../Persistence/Mappers/MemberTypeMapper.cs | 2 +- .../Interfaces/IMemberRepository.cs | 2 +- .../Repositories/MemberRepository.cs | 31 ++- .../Persistence/RepositoryFactory.cs | 2 +- 10 files changed, 172 insertions(+), 170 deletions(-) diff --git a/src/Umbraco.Core/Models/IMember.cs b/src/Umbraco.Core/Models/IMember.cs index 226e3dc70d..0b347a2515 100644 --- a/src/Umbraco.Core/Models/IMember.cs +++ b/src/Umbraco.Core/Models/IMember.cs @@ -1,7 +1,114 @@ -namespace Umbraco.Core.Models +using System; + +namespace Umbraco.Core.Models { public interface IMember : IContentBase { - + /// + /// Gets or sets the Username + /// + string Username { get; set; } + + /// + /// Gets or sets the Email + /// + string Email { get; set; } + + /// + /// Gets or sets the Password + /// + string Password { get; set; } + + /// + /// Gets or sets the Password Question + /// + /// + /// Alias: umbracoPasswordRetrievalQuestionPropertyTypeAlias + /// Part of the standard properties collection. + /// + string PasswordQuestion { get; set; } + + /// + /// Gets or sets the Password Answer + /// + /// + /// Alias: umbracoPasswordRetrievalAnswerPropertyTypeAlias + /// Part of the standard properties collection. + /// + string PasswordAnswer { get; set; } + + /// + /// Gets or set the comments for the member + /// + /// + /// Alias: umbracoCommentPropertyTypeAlias + /// Part of the standard properties collection. + /// + string Comments { get; set; } + + /// + /// Gets or sets a boolean indicating whether the Member is approved + /// + /// + /// Alias: umbracoApprovePropertyTypeAlias + /// Part of the standard properties collection. + /// + bool IsApproved { get; set; } + + /// + /// Gets or sets a boolean indicating whether the Member is locked out + /// + /// + /// Alias: umbracoLockPropertyTypeAlias + /// Part of the standard properties collection. + /// + bool IsLockedOut { get; set; } + + /// + /// Gets or sets the date for last login + /// + /// + /// Alias: umbracoLastLoginPropertyTypeAlias + /// Part of the standard properties collection. + /// + DateTime LastLoginDate { get; set; } + + /// + /// Gest or sets the date for last password change + /// + /// + /// Alias: umbracoMemberLastPasswordChange + /// Part of the standard properties collection. + /// + DateTime LastPasswordChangeDate { get; set; } + + /// + /// Gets or sets the date for when Member was locked out + /// + /// + /// Alias: umbracoMemberLastLockout + /// Part of the standard properties collection. + /// + DateTime LastLockoutDate { get; set; } + + /// + /// Gets or sets the number of failed password attempts. + /// This is the number of times the password was entered incorrectly upon login. + /// + /// + /// Alias: umbracoFailedPasswordAttemptsPropertyTypeAlias + /// Part of the standard properties collection. + /// + int FailedPasswordAttempts { get; set; } + + /// + /// String alias of the default ContentType + /// + string ContentTypeAlias { get; } + + /// + /// Gets the ContentType used by this content object + /// + IMemberType ContentType { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 834f783e09..ed7d5cd3ab 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Models _memberTypePropertyTypes = new Dictionary>(); } - private static readonly PropertyInfo MemberTypePropertyTypesSelector = ExpressionHelper.GetPropertyInfo>>(x => x.MemberTypePropertyTypes); + private static readonly PropertyInfo MemberTypePropertyTypesSelector = ExpressionHelper.GetPropertyInfo>>(x => x.MemberTypePropertyTypes); /// /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, PropertyTypeId) by the PropertyTypes' alias. diff --git a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs index b22a074bca..1aa8f70bb8 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Factories diff --git a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs index 5e1a8a8147..eb6e2c787d 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs @@ -1,19 +1,26 @@ using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; -using IMember = Umbraco.Core.Models.Membership.IMember; namespace Umbraco.Core.Persistence.Factories { internal class MemberReadOnlyFactory : IEntityFactory { + private readonly IDictionary _memberTypes; + + public MemberReadOnlyFactory(IDictionary memberTypes) + { + _memberTypes = memberTypes; + } + public IMember BuildEntity(MemberReadOnlyDto dto) { - var member = new Umbraco.Core.Models.Membership.Member() + var properties = CreateProperties(_memberTypes[dto.ContentTypeAlias], dto.Properties, dto.CreateDate); + var propertyCollection = new PropertyCollection(properties); + + var member = new Member(dto.Text, dto.ParentId, _memberTypes[dto.ContentTypeAlias], propertyCollection) { Id = dto.NodeId, CreateDate = dto.CreateDate, @@ -25,34 +32,14 @@ namespace Umbraco.Core.Persistence.Factories ProviderUserKey = dto.UniqueId, Trashed = dto.Trashed, Key = dto.UniqueId.Value, - ProfileId = dto.UniqueId.Value, - ContentTypeId = dto.ContentTypeId, + CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0, + Level = dto.Level, + Path = dto.Path, + SortOrder = dto.SortOrder, + Version = dto.VersionId, ContentTypeAlias = dto.ContentTypeAlias }; - ((IUmbracoEntity)member).CreatorId = dto.UserId.Value; - ((IUmbracoEntity)member).Level = dto.Level; - ((IUmbracoEntity)member).ParentId = dto.ParentId; - ((IUmbracoEntity)member).Path = dto.Path; - ((IUmbracoEntity)member).SortOrder = dto.SortOrder; - - var propertyTypes = GetStandardPropertyTypeStubs(); - var propertiesDictionary = dto.Properties.ToDictionary(x => x.Alias); - foreach (var property in propertiesDictionary) - { - if (propertyTypes.ContainsKey(property.Key)) - { - UpdatePropertyType(propertyTypes[property.Key], property.Value); - } - else - { - propertyTypes.Add(property.Key, CreateProperty(property.Value)); - } - } - - var properties = CreateProperties(propertyTypes, propertiesDictionary); - member.Properties = new PropertyCollection(properties); - member.SetProviderUserKeyType(typeof(Guid)); member.ResetDirtyProperties(false); return member; @@ -63,135 +50,27 @@ namespace Umbraco.Core.Persistence.Factories throw new System.NotImplementedException(); } - private IEnumerable CreateProperties(Dictionary propertyTypes, Dictionary propertiesDictionary) + public IEnumerable CreateProperties(IMemberType memberType, IEnumerable dtos, DateTime createDate) { var properties = new List(); - foreach (var propertyType in propertyTypes) + + foreach (var propertyType in memberType.CompositionPropertyTypes) { - if (propertiesDictionary.ContainsKey(propertyType.Key)) - { - var prop = propertiesDictionary[propertyType.Key]; - if (prop.PropertyDataId.HasValue && prop.PropertyDataId.Value != default(int)) - { - properties.Add(propertyType.Value.CreatePropertyFromRawValue(prop.GetValue, prop.VersionId, prop.PropertyDataId.Value)); - } - else - { - properties.Add(propertyType.Value.CreatePropertyFromValue(prop.GetValue)); - } - } - else - { - properties.Add(propertyType.Value.CreatePropertyFromValue(null)); - } + var propertyDataDto = dtos.LastOrDefault(x => x.PropertyTypeId == propertyType.Id); + var property = propertyDataDto == null + ? propertyType.CreatePropertyFromValue(null) + : propertyType.CreatePropertyFromRawValue(propertyDataDto.GetValue, + propertyDataDto.VersionId, + propertyDataDto.Id); + //on initial construction we don't want to have dirty properties tracked + property.CreateDate = createDate; + property.UpdateDate = createDate; + // http://issues.umbraco.org/issue/U4-1946 + property.ResetDirtyProperties(false); + properties.Add(property); } return properties; } - - private PropertyType CreateProperty(PropertyDataReadOnlyDto property) - { - var propertyType = new PropertyType(property.ControlId, property.DbType.EnumParse(true)) - { - Id = property.Id, - Alias = property.Alias, - Name = property.Name, - Description = property.Description, - HelpText = property.HelpText, - Mandatory = property.Mandatory, - ValidationRegExp = property.ValidationRegExp, - SortOrder = property.SortOrder - }; - - if(property.PropertyTypeGroupId.HasValue) - propertyType.PropertyGroupId = new Lazy(() => property.PropertyTypeGroupId.Value); - - return propertyType; - } - - private void UpdatePropertyType(PropertyType propertyType, PropertyDataReadOnlyDto property) - { - propertyType.Id = property.Id; - propertyType.Alias = property.Alias; - propertyType.Name = property.Name; - propertyType.Description = property.Description; - propertyType.HelpText = property.HelpText; - propertyType.Mandatory = property.Mandatory; - propertyType.ValidationRegExp = property.ValidationRegExp; - propertyType.SortOrder = property.SortOrder; - - if (property.PropertyTypeGroupId.HasValue) - propertyType.PropertyGroupId = new Lazy(() => property.PropertyTypeGroupId.Value); - } - - private Dictionary GetStandardPropertyTypeStubs() - { - var propertyTypes = new Dictionary(); - - propertyTypes.Add(Constants.Conventions.Member.Comments, - new PropertyType(new Guid(Constants.PropertyEditors.TextboxMultiple), DataTypeDatabaseType.Ntext) - { - Alias = Constants.Conventions.Member.Comments, - Name = Constants.Conventions.Member.CommentsLabel - }); - - propertyTypes.Add(Constants.Conventions.Member.FailedPasswordAttempts, - new PropertyType(new Guid(Constants.PropertyEditors.Integer), DataTypeDatabaseType.Integer) - { - Alias = Constants.Conventions.Member.FailedPasswordAttempts, - Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel - }); - - propertyTypes.Add(Constants.Conventions.Member.IsApproved, - new PropertyType(new Guid(Constants.PropertyEditors.TrueFalse), DataTypeDatabaseType.Integer) - { - Alias = Constants.Conventions.Member.IsApproved, - Name = Constants.Conventions.Member.IsApprovedLabel - }); - - propertyTypes.Add(Constants.Conventions.Member.IsLockedOut, - new PropertyType(new Guid(Constants.PropertyEditors.TrueFalse), DataTypeDatabaseType.Integer) - { - Alias = Constants.Conventions.Member.IsLockedOut, - Name = Constants.Conventions.Member.IsLockedOutLabel - }); - - propertyTypes.Add(Constants.Conventions.Member.LastLockoutDate, - new PropertyType(new Guid(Constants.PropertyEditors.Date), DataTypeDatabaseType.Date) - { - Alias = Constants.Conventions.Member.LastLockoutDate, - Name = Constants.Conventions.Member.LastLockoutDateLabel - }); - - propertyTypes.Add(Constants.Conventions.Member.LastLoginDate, - new PropertyType(new Guid(Constants.PropertyEditors.Date), DataTypeDatabaseType.Date) - { - Alias = Constants.Conventions.Member.LastLoginDate, - Name = Constants.Conventions.Member.LastLoginDateLabel - }); - - propertyTypes.Add(Constants.Conventions.Member.LastPasswordChangeDate, - new PropertyType(new Guid(Constants.PropertyEditors.Date), DataTypeDatabaseType.Date) - { - Alias = Constants.Conventions.Member.LastPasswordChangeDate, - Name = Constants.Conventions.Member.LastPasswordChangeDateLabel - }); - - propertyTypes.Add(Constants.Conventions.Member.PasswordAnswer, - new PropertyType(new Guid(Constants.PropertyEditors.Textbox), DataTypeDatabaseType.Nvarchar) - { - Alias = Constants.Conventions.Member.PasswordAnswer, - Name = Constants.Conventions.Member.PasswordAnswerLabel - }); - - propertyTypes.Add(Constants.Conventions.Member.PasswordQuestion, - new PropertyType(new Guid(Constants.PropertyEditors.Textbox), DataTypeDatabaseType.Nvarchar) - { - Alias = Constants.Conventions.Member.PasswordQuestion, - Name = Constants.Conventions.Member.PasswordQuestionLabel - }); - - return propertyTypes; - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs index 9fa1846629..cb5d7c95a1 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs @@ -3,8 +3,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; using Umbraco.Core.ObjectResolution; namespace Umbraco.Core.Persistence.Mappers diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs index 6e93d7c4aa..d895e1c127 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Mappers @@ -41,7 +41,6 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Name, dto => dto.Text); CacheMap(src => src.Trashed, dto => dto.Trashed); CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.ProfileId, dto => dto.UniqueId); CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); CacheMap(src => src.ContentTypeAlias, dto => dto.Alias); CacheMap(src => src.UpdateDate, dto => dto.VersionDate); diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs index 27fb42a659..eafc1e5031 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs @@ -1,5 +1,5 @@ using System.Collections.Concurrent; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Mappers diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index eb5d8fb049..be959da07c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index a999cc8baa..fca06ad58a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -5,7 +5,7 @@ using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Factories; @@ -17,12 +17,17 @@ namespace Umbraco.Core.Persistence.Repositories { internal class MemberRepository : VersionableRepositoryBase, IMemberRepository { - public MemberRepository(IDatabaseUnitOfWork work) : base(work) + private readonly IMemberTypeRepository _memberTypeRepository; + + public MemberRepository(IDatabaseUnitOfWork work, IMemberTypeRepository memberTypeRepository) : base(work) { + _memberTypeRepository = memberTypeRepository; } - public MemberRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + public MemberRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, IMemberTypeRepository memberTypeRepository) + : base(work, cache) { + _memberTypeRepository = memberTypeRepository; } #region Overrides of RepositoryBase @@ -402,9 +407,18 @@ namespace Umbraco.Core.Persistence.Repositories { if (dtos == null || dtos.Any() == false) return null; + var dto = dtos.First(); - var factory = new MemberReadOnlyFactory(); - var member = factory.BuildEntity(dtos.First()); + var memberTypes = new Dictionary + { + { + dto.ContentTypeAlias, + _memberTypeRepository.Get(dto.ContentTypeId) + } + }; + + var factory = new MemberReadOnlyFactory(memberTypes); + var member = factory.BuildEntity(dto); return member; } @@ -414,7 +428,12 @@ namespace Umbraco.Core.Persistence.Repositories if (dtos == null || dtos.Any() == false) return Enumerable.Empty(); - var factory = new MemberReadOnlyFactory(); + //We assume that there won't exist a lot of MemberTypes, so the following should be fairly fast + var memberTypes = new Dictionary(); + var memberTypeList = _memberTypeRepository.GetAll(); + memberTypeList.ForEach(x => memberTypes.Add(x.Alias, x)); + + var factory = new MemberReadOnlyFactory(memberTypes); return dtos.Select(factory.BuildEntity); } } diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index a94fd0a59e..6985e427fe 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -117,7 +117,7 @@ namespace Umbraco.Core.Persistence internal virtual IMemberRepository CreateMemberRepository(IDatabaseUnitOfWork uow) { - return new MemberRepository(uow, RuntimeCacheProvider.Current); + return new MemberRepository(uow, RuntimeCacheProvider.Current, CreateMemberTypeRepository(uow)); } internal virtual IMemberTypeRepository CreateMemberTypeRepository(IDatabaseUnitOfWork uow) From 642c6c1afec903eede4fe00b1a98ac7659290d29 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Tue, 3 Sep 2013 17:03:35 +0200 Subject: [PATCH 23/45] Implementing a POC and tests for a simple TypedModel base class --- .../StronglyTypedModels/CallingMethodTests.cs | 28 ++++++++ .../StronglyTypedModels/TextpageModel.cs | 18 +++++ .../StronglyTypedModels/TypedModelBase.cs | 67 +++++++++++++++++++ .../UmbracoTemplatePage`T.cs | 25 +++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 4 ++ 5 files changed, 142 insertions(+) create mode 100644 src/Umbraco.Tests/PublishedContent/StronglyTypedModels/CallingMethodTests.cs create mode 100644 src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs create mode 100644 src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs create mode 100644 src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/CallingMethodTests.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/CallingMethodTests.cs new file mode 100644 index 0000000000..eae6593e5d --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/CallingMethodTests.cs @@ -0,0 +1,28 @@ +using System; +using System.Reflection; +using NUnit.Framework; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + [TestFixture] + public class CallingMethodTests + { + private readonly Func _myProperty = MethodBase.GetCurrentMethod; + + public string AField + { + get { return Resolve(_myProperty()); } + } + + private string Resolve(MethodBase m) + { + return m.Name.Replace("get_", ""); + } + + [Test] + public void GetMyName() + { + Assert.AreEqual("AField", AField); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs new file mode 100644 index 0000000000..dee4505eef --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs @@ -0,0 +1,18 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + public class TextpageModel : TypedModelBase + { + public TextpageModel(IPublishedContent publishedContent) : base(publishedContent) + { + } + + public string Title { get { return ResolveString(ForThis()); } } + + public string BodyText { get { return ResolveString(ForThis()); } } + + public DateTime Date { get { return ResolveDate(ForThis()); } } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs new file mode 100644 index 0000000000..90fa21f9ec --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs @@ -0,0 +1,67 @@ +using System; +using System.Reflection; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + public abstract class TypedModelBase + { + private IPublishedContent _publishedContent; + + protected TypedModelBase(){} + + protected TypedModelBase(IPublishedContent publishedContent) + { + _publishedContent = publishedContent; + } + + internal void Add(IPublishedContent publishedContent) + { + _publishedContent = publishedContent; + } + + public readonly Func ForThis = MethodBase.GetCurrentMethod; + + public object Resolve(Type type, MethodBase methodBase) + { + var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return _publishedContent.GetPropertyValue(propertyTypeAlias); + } + + public T Resolve(MethodBase methodBase) + { + var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return _publishedContent.GetPropertyValue(propertyTypeAlias); + } + + public string ResolveString(MethodBase methodBase) + { + return Resolve(methodBase); + } + + public int ResolveInt(MethodBase methodBase) + { + return Resolve(methodBase); + } + + public bool ResolveBool(MethodBase methodBase) + { + return Resolve(methodBase); + } + + public DateTime ResolveDate(MethodBase methodBase) + { + return Resolve(methodBase); + } + } + + public static class TypeExtensions + { + public static string ToUmbracoAlias(this MethodBase methodBase) + { + return methodBase.Name.Replace("get_", "").ToUmbracoAlias(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs new file mode 100644 index 0000000000..f76495531a --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs @@ -0,0 +1,25 @@ +using Umbraco.Web.Mvc; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + public abstract class UmbracoTemplatePage : UmbracoTemplatePage where T : TypedModelBase, new() + { + protected override void InitializePage() + { + base.InitializePage(); + + //set the model to the current node if it is not set, this is generally not the case + if (Model != null) + { + //Map CurrentModel here + TypedModel = new T(); + TypedModel.Add(Model.Content); + } + } + + /// + /// Returns the a strongly typed model + /// + public T TypedModel { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ec2c32202f..05b766619d 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -193,6 +193,10 @@ + + + + From 09de4a3666c5166a4199c6d7ff6f904e1bc78f3b Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 11:30:00 +0200 Subject: [PATCH 24/45] Adding LocalizationService to ctor --- src/Umbraco.Core/Services/PackagingService.cs | 13 ++++++++++--- src/Umbraco.Core/Services/ServiceContext.cs | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index b53cedfd0c..0199a76180 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -28,6 +28,7 @@ namespace Umbraco.Core.Services private readonly IMediaService _mediaService; private readonly IDataTypeService _dataTypeService; private readonly IFileService _fileService; + private readonly ILocalizationService _localizationService; private readonly RepositoryFactory _repositoryFactory; private readonly IDatabaseUnitOfWorkProvider _uowProvider; private Dictionary _importedContentTypes; @@ -36,13 +37,21 @@ namespace Umbraco.Core.Services //for example, the Move method needs to be locked but this calls the Save method which also needs to be locked. private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - public PackagingService(IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IDataTypeService dataTypeService, IFileService fileService, RepositoryFactory repositoryFactory, IDatabaseUnitOfWorkProvider uowProvider) + public PackagingService(IContentService contentService, + IContentTypeService contentTypeService, + IMediaService mediaService, + IDataTypeService dataTypeService, + IFileService fileService, + ILocalizationService localizationService, + RepositoryFactory repositoryFactory, + IDatabaseUnitOfWorkProvider uowProvider) { _contentService = contentService; _contentTypeService = contentTypeService; _mediaService = mediaService; _dataTypeService = dataTypeService; _fileService = fileService; + _localizationService = localizationService; _repositoryFactory = repositoryFactory; _uowProvider = uowProvider; @@ -329,8 +338,6 @@ namespace Umbraco.Core.Services return content; } - - #endregion #region ContentTypes diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index d03307eaa5..cc850d94ba 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -79,7 +79,7 @@ namespace Umbraco.Core.Services _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory.Value)); if(_packagingService == null) - _packagingService = new Lazy(() => new PackagingService(_contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _fileService.Value, repositoryFactory.Value, provider)); + _packagingService = new Lazy(() => new PackagingService(_contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, repositoryFactory.Value, provider)); if (_entityService == null) _entityService = new Lazy(() => new EntityService(provider, repositoryFactory.Value, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value)); From 966416c0a5d534116b844c2ed42e1213ae98d7fe Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 11:30:21 +0200 Subject: [PATCH 25/45] Updating classes for small TypedModel POC --- .../StronglyTypedModels/TextpageModel.cs | 8 ++++--- .../StronglyTypedModels/TypedModelBase.cs | 24 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs index dee4505eef..47f2f817f1 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs @@ -9,10 +9,12 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels { } - public string Title { get { return ResolveString(ForThis()); } } + public string Title { get { return Resolve(Property(), DefaultString); } } - public string BodyText { get { return ResolveString(ForThis()); } } + public string BodyText { get { return Resolve(Property(), DefaultString); } } - public DateTime Date { get { return ResolveDate(ForThis()); } } + public string AuthorName { get { return Resolve(Property()); } } + + public DateTime Date { get { return Resolve(Property(), DefaultDateTime); } } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs index 90fa21f9ec..f466a9045a 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs @@ -22,13 +22,11 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels _publishedContent = publishedContent; } - public readonly Func ForThis = MethodBase.GetCurrentMethod; - - public object Resolve(Type type, MethodBase methodBase) - { - var propertyTypeAlias = methodBase.ToUmbracoAlias(); - return _publishedContent.GetPropertyValue(propertyTypeAlias); - } + public readonly Func Property = MethodBase.GetCurrentMethod; + public static string DefaultString = default(string); + public static int DefaultInteger = default(int); + public static bool DefaultBool = default(bool); + public static DateTime DefaultDateTime = default(DateTime); public T Resolve(MethodBase methodBase) { @@ -36,6 +34,18 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels return _publishedContent.GetPropertyValue(propertyTypeAlias); } + public T Resolve(MethodBase methodBase, T ifCannotConvert) + { + var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return _publishedContent.GetPropertyValue(propertyTypeAlias, false, ifCannotConvert); + } + + public T Resolve(MethodBase methodBase, bool recursive, T ifCannotConvert) + { + var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return _publishedContent.GetPropertyValue(propertyTypeAlias, recursive, ifCannotConvert); + } + public string ResolveString(MethodBase methodBase) { return Resolve(methodBase); From d65e94f519cbed92fbb6ddfa232be04fc0a743a3 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 14:43:48 +0200 Subject: [PATCH 26/45] Adding LocalizationService test fixture class --- .../Services/LocalizationServiceTests.cs | 25 +++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 2 files changed, 26 insertions(+) create mode 100644 src/Umbraco.Tests/Services/LocalizationServiceTests.cs diff --git a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs new file mode 100644 index 0000000000..fa5c6f3253 --- /dev/null +++ b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering all methods in the LocalizationService class. + /// This is more of an integration test as it involves multiple layers + /// as well as configuration. + /// + [TestFixture, RequiresSTA] + public class LocalizationServiceTests : BaseServiceTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 05b766619d..e9ac0dbc8f 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -197,6 +197,7 @@ + From 24091991fa524e1d962b02fd604d4301eefa25ba Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 14:47:42 +0200 Subject: [PATCH 27/45] Updating MemberRepository test fixture. All tests ignored while using local db for testing. --- .../Repositories/MemberRepositoryTest.cs | 248 +++++++++++++++++- 1 file changed, 238 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 76108692c1..288afe4fa9 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -1,8 +1,13 @@ using System; +using System.Linq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; @@ -12,10 +17,8 @@ using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Persistence.Repositories { [TestFixture, NUnit.Framework.Ignore] - public abstract class MemberRepositoryTest : MemberRepositoryBaseTest + public class MemberRepositoryTest : MemberRepositoryBaseTest { - private Database _database; - #region Overrides of MemberRepositoryBaseTest [SetUp] @@ -24,9 +27,6 @@ namespace Umbraco.Tests.Persistence.Repositories base.Initialize(); SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; - - _database = new Database(@"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco", - "System.Data.SqlClient"); } [TearDown] @@ -35,14 +35,230 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); } - public override Database Database + public override string ConnectionString { - get { return _database; } + get { return @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco"; } + } + + public override string ProviderName + { + get { return "System.Data.SqlClient"; } } #endregion + [Test] + public void MemberRepository_Can_Get_Member_By_Id() + { + var unitOfWork = UnitOfWorkProvider.GetUnitOfWork(); + var repository = RepositoryResolver.Current.Factory.CreateMemberRepository(unitOfWork); + var member = repository.Get(1341); + + Assert.That(member, Is.Not.Null); + } + + [Test] + public void MemberRepository_Can_Get_Specific_Members() + { + var unitOfWork = UnitOfWorkProvider.GetUnitOfWork(); + var repository = RepositoryResolver.Current.Factory.CreateMemberRepository(unitOfWork); + + var members = repository.GetAll(1341, 1383); + + Assert.That(members, Is.Not.Null); + Assert.That(members.Any(x => x == null), Is.False); + Assert.That(members.Count(), Is.EqualTo(2)); + } + + [Test] + public void MemberRepository_Can_Get_All_Members() + { + var unitOfWork = UnitOfWorkProvider.GetUnitOfWork(); + var repository = RepositoryResolver.Current.Factory.CreateMemberRepository(unitOfWork); + + var members = repository.GetAll(); + + Assert.That(members, Is.Not.Null); + Assert.That(members.Any(x => x == null), Is.False); + Assert.That(members.Count(), Is.EqualTo(6)); + } + + [Test] + public void MemberRepository_Can_Perform_GetByQuery_With_Key() + { + // Arrange + var unitOfWork = UnitOfWorkProvider.GetUnitOfWork(); + var repository = RepositoryResolver.Current.Factory.CreateMemberRepository(unitOfWork); + + // Act + var query = Query.Builder.Where(x => x.Key == new Guid("A6B9CA6B-0615-42CA-B5F5-338417EC76BE")); + var result = repository.GetByQuery(query); + + // Assert + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Id, Is.EqualTo(1341)); + } + + [Test] + public void MemberRepository_Can_Perform_GetByQuery_With_Property_Value() + { + // Arrange + var unitOfWork = UnitOfWorkProvider.GetUnitOfWork(); + var repository = RepositoryResolver.Current.Factory.CreateMemberRepository(unitOfWork); + + // Act + var query = Query.Builder.Where(x => ((Member)x).ShortStringPropertyValue.EndsWith("piquet_h")); + var result = repository.GetByQuery(query); + + // Assert + Assert.That(result.Any(x => x == null), Is.False); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Id, Is.EqualTo(1341)); + } + + [Test] + public void MemberRepository_Can_Perform_GetByQuery_With_Property_Alias_And_Value() + { + // Arrange + var unitOfWork = UnitOfWorkProvider.GetUnitOfWork(); + var repository = RepositoryResolver.Current.Factory.CreateMemberRepository(unitOfWork); + + // Act + var query = Query.Builder.Where(x => ((Member)x).LongStringPropertyValue.Contains("1095") && ((Member)x).PropertyTypeAlias == "headshot"); + var result = repository.GetByQuery(query); + + // Assert + Assert.That(result.Any(x => x == null), Is.False); + Assert.That(result.Count(), Is.EqualTo(5)); + } + + [Test] + public void Can_Create_Correct_Subquery() + { + var query = Query.Builder.Where(x => + ((Member) x).LongStringPropertyValue.Contains("1095") && + ((Member) x).PropertyTypeAlias == "headshot"); + + var sqlSubquery = GetSubquery(); + var translator = new SqlTranslator(sqlSubquery, query); + var subquery = translator.Translate(); + var sql = GetBaseQuery(false) + .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + Console.WriteLine(sql.SQL); + Assert.That(sql.SQL, Is.Not.Empty); + } + + private Sql GetBaseQuery(bool isCount) + { + if (isCount) + { + var sqlCount = new Sql() + .Select("COUNT(*)") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sqlCount; + } + + var sql = new Sql(); + sql.Select("umbracoNode.*", "cmsContent.contentType", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", + "cmsContentVersion.VersionDate", "cmsContentVersion.LanguageLocale", "cmsMember.Email", + "cmsMember.LoginName", "cmsMember.Password", "cmsPropertyData.id AS PropertyDataId", "cmsPropertyData.propertytypeid", + "cmsPropertyData.dataDate", "cmsPropertyData.dataInt", "cmsPropertyData.dataNtext", "cmsPropertyData.dataNvarchar", + "cmsPropertyType.id", "cmsPropertyType.Alias", "cmsPropertyType.Description", + "cmsPropertyType.Name", "cmsPropertyType.mandatory", "cmsPropertyType.validationRegExp", + "cmsPropertyType.helpText", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId", + "cmsPropertyType.dataTypeId", "cmsDataType.controlId", "cmsDataType.dbType") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + private Sql GetSubquery() + { + var sql = new Sql(); + sql.Select("umbracoNode.id") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + private Guid NodeObjectTypeId + { + get { return new Guid(Constants.ObjectTypes.Member); } + } + } + + [TestFixture, NUnit.Framework.Ignore] + public class MemberTypeRepositoryTest : MemberRepositoryBaseTest + { + #region Overrides of MemberRepositoryBaseTest + + [SetUp] + public override void Initialize() + { + base.Initialize(); + + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + public override string ConnectionString + { + get { return @"server=.\SQLEXPRESS;database=Kloud-Website-Production;user id=umbraco;password=umbraco"; } + } + + public override string ProviderName + { + get { return "System.Data.SqlClient"; } + } + + #endregion + + [Test] + public void MemberTypeRepository_Can_Get_MemberType_By_Id() + { + var unitOfWork = UnitOfWorkProvider.GetUnitOfWork(); + var repository = RepositoryResolver.Current.Factory.CreateMemberTypeRepository(unitOfWork); + + var memberType = repository.Get(1340); + + Assert.That(memberType, Is.Not.Null); + Assert.That(memberType.PropertyTypes.Count(), Is.EqualTo(13)); + Assert.That(memberType.PropertyGroups.Any(), Is.False); + + repository.AddOrUpdate(memberType); + unitOfWork.Commit(); + Assert.That(memberType.PropertyTypes.Any(x => x.HasIdentity == false), Is.False); + } } [TestFixture] @@ -59,12 +275,19 @@ namespace Umbraco.Tests.Persistence.Repositories RepositoryResolver.Current = new RepositoryResolver( new RepositoryFactory()); + + MappingResolver.Current = new MappingResolver( + () => PluginManager.Current.ResolveAssignedMapperTypes()); + + + var dbFactory = new DefaultDatabaseFactory(ConnectionString, ProviderName); + UnitOfWorkProvider = new PetaPocoUnitOfWorkProvider(dbFactory); ApplicationContext.Current = new ApplicationContext( //assign the db context new DatabaseContext(new DefaultDatabaseFactory()), //assign the service context - new ServiceContext(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy()), + new ServiceContext(UnitOfWorkProvider, new FileUnitOfWorkProvider(), new PublishingStrategy()), //disable cache false) { @@ -84,8 +307,13 @@ namespace Umbraco.Tests.Persistence.Repositories ApplicationContext.Current = null; RepositoryResolver.Reset(); + MappingResolver.Reset(); } - public abstract Database Database { get; } + public abstract string ConnectionString { get; } + + public abstract string ProviderName { get; } + + public PetaPocoUnitOfWorkProvider UnitOfWorkProvider { get; set; } } } \ No newline at end of file From 1f68cad45b68e5229825e60c9010b25ef38cf253 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 15:42:01 +0200 Subject: [PATCH 28/45] Refactoring MemberService and MembersMembershipProvider from Darrens pull request to fit the changes made the past week. SecurityHelper is moved to the Umbraco.Web.Security namespace. MembershipExtensions are changed to the new models --- .../Models/Membership/MembershipExtensions.cs | 25 +++ .../Membership/UmbracoMembershipMember.cs | 5 + src/Umbraco.Core/Services/IMemberService.cs | 16 +- src/Umbraco.Core/Services/MemberService.cs | 69 ++++++--- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Providers/MembersMembershipProvider.cs | 144 +++++++++++------- .../{Helpers => Security}/SecurityHelper.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- src/umbraco.providers/SecUtility.cs | 5 +- 9 files changed, 181 insertions(+), 88 deletions(-) create mode 100644 src/Umbraco.Core/Models/Membership/MembershipExtensions.cs rename src/Umbraco.Web/{Helpers => Security}/SecurityHelper.cs (98%) diff --git a/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs b/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs new file mode 100644 index 0000000000..a986869371 --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Web.Security; + +namespace Umbraco.Core.Models.Membership +{ + internal static class MembershipExtensions + { + internal static MembershipUser AsConcreteMembershipUser(this IMember member) + { + var membershipMember = new UmbracoMembershipMember(member); + return membershipMember; + } + + internal static IMember AsIMember(this MembershipUser membershipMember) + { + var member = membershipMember as UmbracoMembershipMember; + if (member != null) + { + return member.Member; + } + + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs index 6dbf963ff5..06e50ecd9b 100644 --- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs +++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs @@ -11,6 +11,11 @@ namespace Umbraco.Core.Models.Membership _member = member; } + internal IMember Member + { + get { return _member; } + } + public override string Email { get { return _member.Email; } diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 0216639cb3..2b86ac8e99 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -1,4 +1,4 @@ -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models; namespace Umbraco.Core.Services { @@ -18,16 +18,16 @@ namespace Umbraco.Core.Services /// internal interface IMembershipMemberService : IService { - IMembershipUser CreateMember(string username, string email, string password, string memberType, int userId = 0); - - IMembershipUser GetByUsername(string login); + IMember CreateMember(string username, string email, string password, string memberTypeAlias, int userId = 0); - IMembershipUser GetByEmail(string email); + IMember GetByUsername(string login); - IMembershipUser GetById(object id); + IMember GetByEmail(string email); - void Delete(IMembershipUser membershipUser); + IMember GetById(object id); - void Save(IMembershipUser membershipUser); + void Delete(IMember membershipUser); + + void Save(IMember membershipUser); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 0d6a6e471b..6bd1fe4284 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models.Membership; +using System; +using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; @@ -30,11 +31,21 @@ namespace Umbraco.Core.Services _uowProvider = provider; } - public IMembershipUser CreateMember(string username, string email, string password, string memberType, int userId = 0) + public IMember CreateMember(string username, string email, string password, string memberTypeAlias, int userId = 0) { var uow = _uowProvider.GetUnitOfWork(); + IMemberType memberType; - var member = new Member(); + using (var repository = _repositoryFactory.CreateMemberTypeRepository(uow)) + { + var query = Query.Builder.Where(x => x.Alias == memberTypeAlias); + memberType = repository.GetByQuery(query).FirstOrDefault(); + } + + if (memberType == null) + throw new Exception(string.Format("No MemberType matching the passed in Alias: '{0}' was found", memberTypeAlias)); + + var member = new Member(email, -1, memberType, new PropertyCollection()); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { @@ -49,50 +60,68 @@ namespace Umbraco.Core.Services return member; } - public IMembershipUser GetByUsername(string userName) + public IMember GetByUsername(string userName) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Username == userName); - var membershipUser = repository.GetByQuery(query).FirstOrDefault(); + var query = Query.Builder.Where(x => x.Username == userName); + var member = repository.GetByQuery(query).FirstOrDefault(); - return membershipUser; + return member; } } - public IMembershipUser GetByEmail(string email) + public IMember GetByEmail(string email) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Email == email); - var membershipUser = repository.GetByQuery(query).FirstOrDefault(); + var query = Query.Builder.Where(x => x.Email == email); + var member = repository.GetByQuery(query).FirstOrDefault(); - return membershipUser; + return member; } } - public IMembershipUser GetById(object id) + public IMember GetById(object id) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Id == id); - var membershipUser = repository.GetByQuery(query).FirstOrDefault(); + if (id is int) + { + var query = Query.Builder.Where(x => x.Id == (int)id); + var member = repository.GetByQuery(query).FirstOrDefault(); + return member; + } - return membershipUser; + if (id is Guid) + { + var query = Query.Builder.Where(x => x.Key == (Guid)id); + var member = repository.GetByQuery(query).FirstOrDefault(); + return member; + } + + return null; } } - public void Delete(IMembershipUser membershipUser) + public void Delete(IMember member) { - using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { - repository.Delete(membershipUser); + repository.Delete(member); + uow.Commit(); } } - public void Save(IMembershipUser membershipUser) + public void Save(IMember member) { - throw new System.NotImplementedException(); + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) + { + repository.AddOrUpdate(member); + uow.Commit(); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f2dd7eb2cb..43b8891c92 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -203,6 +203,7 @@ + diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index b4e9ebbb2b..b638dcdff5 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -4,67 +4,20 @@ using System.Configuration.Provider; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +using System.Web.Hosting; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Logging; -using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -using Umbraco.Web.Helpers; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Security.Providers { - /// - /// Extension methods - /// - static internal class Extensions - { - public static MembershipUser AsConcreteMembershipUser(this IMembershipUser member) - { - // TODO: Provider name?? first constructor arg... Replace DateTime.Now; - var m = new MembershipUser("", - member.Username, - member.ProviderUserKey, - member.Email, - member.PasswordQuestion, - member.Comments, - member.IsApproved, - member.IsLockedOut, - member.CreateDate, - member.LastLoginDate, - member.UpdateDate, - member.LastPasswordChangeDate, - member.LastLockoutDate); - - return m; - } - - public static IMembershipUser AsIMembershipUser(this MembershipUser membershipUser) - { - var m = new Member - { - Username = membershipUser.UserName, - Password = membershipUser.GetPassword(), - Email = membershipUser.Email, - CreateDate = membershipUser.CreationDate, - Comments = membershipUser.Comment, - IsApproved = membershipUser.IsApproved, - IsLockedOut = membershipUser.IsLockedOut, - LastLockoutDate = membershipUser.LastLockoutDate, - LastLoginDate = membershipUser.LastLoginDate, - LastPasswordChangeDate = membershipUser.LastPasswordChangedDate - }; - - return m; - } - - } - /// /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) /// internal class MembersMembershipProvider : MembershipProvider { - #region Fields private string _applicationName; private bool _enablePasswordReset; @@ -76,9 +29,9 @@ namespace Umbraco.Web.Security.Providers private MembershipPasswordFormat _passwordFormat; - private string _passwordStrengthRegularExpression; - private bool _requiresQuestionAndAnswer; - private bool _requiresUniqueEmail; + private string _passwordStrengthRegularExpression; + private bool _requiresQuestionAndAnswer; + private bool _requiresUniqueEmail; #endregion @@ -534,7 +487,7 @@ namespace Umbraco.Web.Security.Providers /// A object that represents the user to update and the updated information for the user. public override void UpdateUser(MembershipUser user) { - var member = user.AsIMembershipUser(); + var member = user.AsIMember(); MemberService.Save(member); } @@ -742,6 +695,8 @@ namespace Umbraco.Web.Security.Providers throw new System.NotImplementedException(); } + #region Private methods + private bool IsPasswordValid(string password) { if (_minRequiredNonAlphanumericCharacters > 0) @@ -796,6 +751,87 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("Unsupported password format."); } return encodedPassword; - } + } + + /// + /// Gets the boolean value. + /// + /// The config. + /// Name of the value. + /// if set to true [default value]. + /// + private bool GetBooleanValue(NameValueCollection config, string valueName, bool defaultValue) + { + bool flag; + var str = config[valueName]; + if (str == null) + return defaultValue; + + if (bool.TryParse(str, out flag) == false) + { + throw new ProviderException("Value must be boolean."); + } + return flag; + } + + /// + /// Gets the int value. + /// + /// The config. + /// Name of the value. + /// The default value. + /// if set to true [zero allowed]. + /// The max value allowed. + /// + private int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed) + { + int num; + var s = config[valueName]; + if (s == null) + { + return defaultValue; + } + if (int.TryParse(s, out num) == false) + { + if (zeroAllowed) + { + throw new ProviderException("Value must be non negative integer"); + } + throw new ProviderException("Value must be positive integer"); + } + if (zeroAllowed && (num < 0)) + { + throw new ProviderException("Value must be non negativeinteger"); + } + if (zeroAllowed == false && (num <= 0)) + { + throw new ProviderException("Value must be positive integer"); + } + if ((maxValueAllowed > 0) && (num > maxValueAllowed)) + { + throw new ProviderException("Value too big"); + } + return num; + } + + + /// + /// Gets the name of the default app. + /// + /// + private string GetDefaultAppName() + { + try + { + var applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath; + return string.IsNullOrEmpty(applicationVirtualPath) ? "/" : applicationVirtualPath; + } + catch + { + return "/"; + } + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Helpers/SecurityHelper.cs b/src/Umbraco.Web/Security/SecurityHelper.cs similarity index 98% rename from src/Umbraco.Web/Helpers/SecurityHelper.cs rename to src/Umbraco.Web/Security/SecurityHelper.cs index a731d516b2..ca22aa9901 100644 --- a/src/Umbraco.Web/Helpers/SecurityHelper.cs +++ b/src/Umbraco.Web/Security/SecurityHelper.cs @@ -2,7 +2,7 @@ using System.Configuration.Provider; using System.Web.Hosting; -namespace Umbraco.Web.Helpers +namespace Umbraco.Web.Security { internal class SecurityHelper { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a3e57c95f6..a87f2fa449 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -294,7 +294,7 @@ - + diff --git a/src/umbraco.providers/SecUtility.cs b/src/umbraco.providers/SecUtility.cs index 0bf43bfa08..572d8fbc12 100644 --- a/src/umbraco.providers/SecUtility.cs +++ b/src/umbraco.providers/SecUtility.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Collections.Specialized; using System.Collections; using System.Globalization; using System.Web.Hosting; -using System.Diagnostics; using System.Configuration.Provider; namespace umbraco.providers @@ -13,7 +10,7 @@ namespace umbraco.providers /// /// Security Helper Methods /// - [ObsoleteAttribute("This clas is obsolete. Use Umbraco.Web.Security.Helper.SecurityHelper instead.", false)] + [ObsoleteAttribute("This clas is obsolete and wil be removed along with the legacy Membership Provider.", false)] internal class SecUtility { /// From 72dcb32ac23cba23ccd08d66782df2a40aacacd1 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 15:42:25 +0200 Subject: [PATCH 29/45] Refactoring the TypedModelBase and UmbracoTempaltePage for the TypedModel POC --- .../StronglyTypedModels/TypedModelBase.cs | 9 +-------- .../StronglyTypedModels/UmbracoTemplatePage`T.cs | 12 ++++++++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs index f466a9045a..4cc6e2ebf2 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs @@ -8,20 +8,13 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels { public abstract class TypedModelBase { - private IPublishedContent _publishedContent; - - protected TypedModelBase(){} + private readonly IPublishedContent _publishedContent; protected TypedModelBase(IPublishedContent publishedContent) { _publishedContent = publishedContent; } - internal void Add(IPublishedContent publishedContent) - { - _publishedContent = publishedContent; - } - public readonly Func Property = MethodBase.GetCurrentMethod; public static string DefaultString = default(string); public static int DefaultInteger = default(int); diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs index f76495531a..6e27886b83 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs @@ -1,8 +1,9 @@ -using Umbraco.Web.Mvc; +using Umbraco.Core.Models; +using Umbraco.Web.Mvc; namespace Umbraco.Tests.PublishedContent.StronglyTypedModels { - public abstract class UmbracoTemplatePage : UmbracoTemplatePage where T : TypedModelBase, new() + public abstract class UmbracoTemplatePage : UmbracoTemplatePage where T : TypedModelBase { protected override void InitializePage() { @@ -12,8 +13,11 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels if (Model != null) { //Map CurrentModel here - TypedModel = new T(); - TypedModel.Add(Model.Content); + var constructorInfo = typeof(T).GetConstructor(new []{typeof(IPublishedContent)}); + if (constructorInfo != null) + { + TypedModel = constructorInfo.Invoke(new object[]{Model.Content}) as T; + } } } From 2ec367fd5981a704e8ee8bf0f913ebae124a1cfb Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 15:44:29 +0200 Subject: [PATCH 30/45] Removing the SecurityHelper and moving the 3 methods into the Provider as private classes, as they aren't used anywhere else. The SecUtility will be removed along with the legacy MembershipProvider when cleanup is possible. --- .../Providers/MembersMembershipProvider.cs | 18 ++-- src/Umbraco.Web/Security/SecurityHelper.cs | 88 ------------------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 3 files changed, 9 insertions(+), 98 deletions(-) delete mode 100644 src/Umbraco.Web/Security/SecurityHelper.cs diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index b638dcdff5..f02cd8eaae 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -183,16 +183,16 @@ namespace Umbraco.Web.Security.Providers // Initialize base provider class base.Initialize(name, config); - _applicationName = string.IsNullOrEmpty(config["applicationName"]) ? SecurityHelper.GetDefaultAppName() : config["applicationName"]; + _applicationName = string.IsNullOrEmpty(config["applicationName"]) ? GetDefaultAppName() : config["applicationName"]; - _enablePasswordRetrieval = SecurityHelper.GetBooleanValue(config, "enablePasswordRetrieval", false); - _enablePasswordReset = SecurityHelper.GetBooleanValue(config, "enablePasswordReset", false); - _requiresQuestionAndAnswer = SecurityHelper.GetBooleanValue(config, "requiresQuestionAndAnswer", false); - _requiresUniqueEmail = SecurityHelper.GetBooleanValue(config, "requiresUniqueEmail", true); - _maxInvalidPasswordAttempts = SecurityHelper.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); - _passwordAttemptWindow = SecurityHelper.GetIntValue(config, "passwordAttemptWindow", 10, false, 0); - _minRequiredPasswordLength = SecurityHelper.GetIntValue(config, "minRequiredPasswordLength", 7, true, 0x80); - _minRequiredNonAlphanumericCharacters = SecurityHelper.GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80); + _enablePasswordRetrieval = GetBooleanValue(config, "enablePasswordRetrieval", false); + _enablePasswordReset = GetBooleanValue(config, "enablePasswordReset", false); + _requiresQuestionAndAnswer = GetBooleanValue(config, "requiresQuestionAndAnswer", false); + _requiresUniqueEmail = GetBooleanValue(config, "requiresUniqueEmail", true); + _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); + _passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0); + _minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", 7, true, 0x80); + _minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80); _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; // make sure password format is Hashed by default. diff --git a/src/Umbraco.Web/Security/SecurityHelper.cs b/src/Umbraco.Web/Security/SecurityHelper.cs deleted file mode 100644 index ca22aa9901..0000000000 --- a/src/Umbraco.Web/Security/SecurityHelper.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Collections.Specialized; -using System.Configuration.Provider; -using System.Web.Hosting; - -namespace Umbraco.Web.Security -{ - internal class SecurityHelper - { - /// - /// Gets the boolean value. - /// - /// The config. - /// Name of the value. - /// if set to true [default value]. - /// - internal static bool GetBooleanValue(NameValueCollection config, string valueName, bool defaultValue) - { - bool flag; - var str = config[valueName]; - if (str == null) - return defaultValue; - - if (bool.TryParse(str, out flag) == false) - { - throw new ProviderException("Value must be boolean."); - } - return flag; - } - - /// - /// Gets the int value. - /// - /// The config. - /// Name of the value. - /// The default value. - /// if set to true [zero allowed]. - /// The max value allowed. - /// - internal static int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed) - { - int num; - var s = config[valueName]; - if (s == null) - { - return defaultValue; - } - if (int.TryParse(s, out num) == false) - { - if (zeroAllowed) - { - throw new ProviderException("Value must be non negative integer"); - } - throw new ProviderException("Value must be positive integer"); - } - if (zeroAllowed && (num < 0)) - { - throw new ProviderException("Value must be non negativeinteger"); - } - if (zeroAllowed == false && (num <= 0)) - { - throw new ProviderException("Value must be positive integer"); - } - if ((maxValueAllowed > 0) && (num > maxValueAllowed)) - { - throw new ProviderException("Value too big"); - } - return num; - } - - - /// - /// Gets the name of the default app. - /// - /// - internal static string GetDefaultAppName() - { - try - { - var applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath; - return string.IsNullOrEmpty(applicationVirtualPath) ? "/" : applicationVirtualPath; - } - catch - { - return "/"; - } - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a87f2fa449..ed3c6a1822 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -294,7 +294,6 @@ - From 269f573b82e8943c42367616f2c9cdb9370f42c1 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 16:00:38 +0200 Subject: [PATCH 31/45] Refactoring the GetById method in the MemberService. --- src/Umbraco.Core/Services/IMemberService.cs | 6 ++- src/Umbraco.Core/Services/MemberService.cs | 47 ++++++++++++------- .../Providers/MembersMembershipProvider.cs | 31 +++++------- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 2b86ac8e99..525dec86dc 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models; +using System; +using Umbraco.Core.Models; namespace Umbraco.Core.Services { @@ -7,7 +8,8 @@ namespace Umbraco.Core.Services /// internal interface IMemberService : IMembershipMemberService { - + IMember GetById(int id); + IMember GetByKey(Guid id); } /// diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 6bd1fe4284..d942a51944 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -31,6 +31,26 @@ namespace Umbraco.Core.Services _uowProvider = provider; } + public IMember GetById(int id) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } + + public IMember GetByKey(Guid id) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Key == id); + var member = repository.GetByQuery(query).FirstOrDefault(); + return member; + } + } + + #region IMembershipMemberService Implementation + public IMember CreateMember(string username, string email, string password, string memberTypeAlias, int userId = 0) { var uow = _uowProvider.GetUnitOfWork(); @@ -84,24 +104,17 @@ namespace Umbraco.Core.Services public IMember GetById(object id) { - using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + if (id is int) { - if (id is int) - { - var query = Query.Builder.Where(x => x.Id == (int)id); - var member = repository.GetByQuery(query).FirstOrDefault(); - return member; - } - - if (id is Guid) - { - var query = Query.Builder.Where(x => x.Key == (Guid)id); - var member = repository.GetByQuery(query).FirstOrDefault(); - return member; - } - - return null; + return GetById((int)id); } + + if (id is Guid) + { + return GetByKey((Guid)id); + } + + return null; } public void Delete(IMember member) @@ -123,5 +136,7 @@ namespace Umbraco.Core.Services uow.Commit(); } } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index f02cd8eaae..0f7d255f74 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -35,6 +35,13 @@ namespace Umbraco.Web.Security.Providers #endregion + private IMembershipMemberService _memberService; + + protected IMembershipMemberService MemberService + { + get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } + } + /// /// The name of the application using the custom membership provider. /// @@ -153,17 +160,10 @@ namespace Umbraco.Web.Security.Providers /// Member type alias public string DefaultMemberTypeAlias { get; private set; } - public string ProviderName { + public string ProviderName { get { return "MembersMembershipProvider"; } } - - private IMembershipMemberService _memberService; - - protected IMembershipMemberService MemberService - { - get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } - } - + /// /// Initializes the provider. /// @@ -229,7 +229,6 @@ namespace Umbraco.Web.Security.Providers DefaultMemberTypeAlias = config["defaultMemberTypeAlias"]; LogHelper.Debug("Finished initialising member ship provider " + GetType().FullName); - } public override string ToString() @@ -405,8 +404,7 @@ namespace Umbraco.Web.Security.Providers return member.Password; } - - + /// /// Processes a request to update the password for a membership user. /// @@ -477,8 +475,7 @@ namespace Umbraco.Web.Security.Providers return null; } - - + /// /// Updates e-mail and potentially approved status, lock status and comment on a user. /// Note: To automatically support lock, approve and comments you'll need to add references to the membertype properties in the @@ -564,8 +561,7 @@ namespace Umbraco.Web.Security.Providers return true; } - - + /// /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. /// @@ -663,8 +659,7 @@ namespace Umbraco.Web.Security.Providers { throw new System.NotImplementedException(); } - - + /// /// Gets a collection of membership users where the user name contains the specified user name to match. /// From 33bba5704749a3b177f779cff6cd33a9d0287cfe Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 16:30:51 +0200 Subject: [PATCH 32/45] Implementing addtional methods in the MemberService --- .../Repositories/MemberRepository.cs | 3 + src/Umbraco.Core/Services/IMemberService.cs | 8 +- src/Umbraco.Core/Services/MemberService.cs | 190 ++++++++++++++++-- 3 files changed, 177 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index fca06ad58a..856ede87d1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -15,6 +15,9 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { + /// + /// Represents a repository for doing CRUD operations for + /// internal class MemberRepository : VersionableRepositoryBase, IMemberRepository { private readonly IMemberTypeRepository _memberTypeRepository; diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 525dec86dc..a6a2a14424 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Services @@ -10,6 +11,9 @@ namespace Umbraco.Core.Services { IMember GetById(int id); IMember GetByKey(Guid id); + IEnumerable GetMembersByMemberType(string memberTypeAlias); + IEnumerable GetMembersByGroup(string memberGroupName); + IEnumerable GetAllMembers(params int[] ids); } /// @@ -22,11 +26,11 @@ namespace Umbraco.Core.Services { IMember CreateMember(string username, string email, string password, string memberTypeAlias, int userId = 0); - IMember GetByUsername(string login); + IMember GetById(object id); IMember GetByEmail(string email); - IMember GetById(object id); + IMember GetByUsername(string login); void Delete(IMember membershipUser); diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index d942a51944..a24a7d8d49 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; @@ -31,6 +33,13 @@ namespace Umbraco.Core.Services _uowProvider = provider; } + #region IMemberService Implementation + + /// + /// Gets a Member by its integer Id + /// + /// + /// public IMember GetById(int id) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) @@ -39,6 +48,15 @@ namespace Umbraco.Core.Services } } + /// + /// Gets a Member by its Guid key + /// + /// + /// The guid key corresponds to the unique id in the database + /// and the user id in the membership provider. + /// + /// + /// public IMember GetByKey(Guid id) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) @@ -49,6 +67,134 @@ namespace Umbraco.Core.Services } } + /// + /// Gets a list of Members by their MemberType + /// + /// + /// + public IEnumerable GetMembersByMemberType(string memberTypeAlias) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ContentTypeAlias == memberTypeAlias); + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members by the MemberGroup they are part of + /// + /// + /// + public IEnumerable GetMembersByGroup(string memberGroupName) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + return repository.GetByMemberGroup(memberGroupName); + } + } + + /// + /// Gets a list of all Members + /// + /// + /// + public IEnumerable GetAllMembers(params int[] ids) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids); + } + } + + /// + /// Gets a list of Members with a certain string property value + /// + /// + /// + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.Contains(value) || + ((Member)x).ShortStringPropertyValue.Contains(value))); + + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members with a certain integer property value + /// + /// + /// + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue == value); + + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members with a certain boolean property value + /// + /// + /// + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).BoolPropertyValue == value); + + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members with a certain date time property value + /// + /// + /// + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue == value); + + var members = repository.GetByQuery(query); + return members; + } + } + + #endregion + #region IMembershipMemberService Implementation public IMember CreateMember(string username, string email, string password, string memberTypeAlias, int userId = 0) @@ -80,28 +226,6 @@ namespace Umbraco.Core.Services return member; } - public IMember GetByUsername(string userName) - { - using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Username == userName); - var member = repository.GetByQuery(query).FirstOrDefault(); - - return member; - } - } - - public IMember GetByEmail(string email) - { - using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Email == email); - var member = repository.GetByQuery(query).FirstOrDefault(); - - return member; - } - } - public IMember GetById(object id) { if (id is int) @@ -117,6 +241,28 @@ namespace Umbraco.Core.Services return null; } + public IMember GetByEmail(string email) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Email == email); + var member = repository.GetByQuery(query).FirstOrDefault(); + + return member; + } + } + + public IMember GetByUsername(string userName) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Username == userName); + var member = repository.GetByQuery(query).FirstOrDefault(); + + return member; + } + } + public void Delete(IMember member) { var uow = _uowProvider.GetUnitOfWork(); From 3393b6b0da25aad0ff21ded2f5826e3e709ffcce Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 4 Sep 2013 16:35:54 +0200 Subject: [PATCH 33/45] Adding comments to the MemberService methods --- src/Umbraco.Core/Services/MemberService.cs | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index a24a7d8d49..237e7286c0 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -197,6 +197,15 @@ namespace Umbraco.Core.Services #region IMembershipMemberService Implementation + /// + /// Creates a new Member + /// + /// + /// + /// + /// + /// + /// public IMember CreateMember(string username, string email, string password, string memberTypeAlias, int userId = 0) { var uow = _uowProvider.GetUnitOfWork(); @@ -226,6 +235,14 @@ namespace Umbraco.Core.Services return member; } + /// + /// Gets a Member by its Id + /// + /// + /// The Id should be an integer or Guid. + /// + /// + /// public IMember GetById(object id) { if (id is int) @@ -241,6 +258,11 @@ namespace Umbraco.Core.Services return null; } + /// + /// Gets a Member by its Email + /// + /// + /// public IMember GetByEmail(string email) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) @@ -252,6 +274,11 @@ namespace Umbraco.Core.Services } } + /// + /// Gets a Member by its Username + /// + /// + /// public IMember GetByUsername(string userName) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) @@ -263,6 +290,10 @@ namespace Umbraco.Core.Services } } + /// + /// Deletes a Member + /// + /// public void Delete(IMember member) { var uow = _uowProvider.GetUnitOfWork(); @@ -273,6 +304,10 @@ namespace Umbraco.Core.Services } } + /// + /// Saves an updated Member + /// + /// public void Save(IMember member) { var uow = _uowProvider.GetUnitOfWork(); From 177486418ded48de2f2411c090a517dc8822e743 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 5 Sep 2013 14:01:28 +1000 Subject: [PATCH 34/45] Implements: U4-1639 Add support to generate GET urls to SurfaceController actions --- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 357 +++++++++++++++--- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 53 ++- 2 files changed, 346 insertions(+), 64 deletions(-) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 79d6ec3f7c..1a0bee2586 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -188,23 +188,29 @@ namespace Umbraco.Web /// internal class UmbracoForm : MvcForm { - /// - /// Creates an UmbracoForm - /// - /// - /// - /// - /// - /// - public UmbracoForm( + + + /// + /// Creates an UmbracoForm + /// + /// + /// + /// + /// + /// + /// + public UmbracoForm( ViewContext viewContext, string surfaceController, string surfaceAction, string area, + FormMethod method, object additionalRouteVals = null) : base(viewContext) { - //need to create a params string as Base64 to put into our hidden field to use during the routes + _viewContext = viewContext; + _method = method; + //need to create a params string as Base64 to put into our hidden field to use during the routes var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", viewContext.HttpContext.Server.UrlEncode(surfaceController), viewContext.HttpContext.Server.UrlEncode(surfaceAction), @@ -212,21 +218,19 @@ namespace Umbraco.Web var additionalRouteValsAsQuery = additionalRouteVals != null ? additionalRouteVals.ToDictionary().ToQueryString() : null; - if (!additionalRouteValsAsQuery.IsNullOrWhiteSpace()) + if (additionalRouteValsAsQuery.IsNullOrWhiteSpace() == false) surfaceRouteParams += "&" + additionalRouteValsAsQuery; - if (!string.IsNullOrWhiteSpace(surfaceRouteParams)) + if (string.IsNullOrWhiteSpace(surfaceRouteParams) == false) { _encryptedString = surfaceRouteParams.EncryptWithMachineKey(); } - - _textWriter = viewContext.Writer; } - + private readonly ViewContext _viewContext; + private readonly FormMethod _method; private bool _disposed; private readonly string _encryptedString; - private readonly TextWriter _textWriter; protected override void Dispose(bool disposing) { @@ -234,13 +238,46 @@ namespace Umbraco.Web return; this._disposed = true; - //write out the hidden surface form routes - _textWriter.Write(""); + //Need to ensure any stale GET cookies are cleared before continuing + foreach (var c in _viewContext.HttpContext.Request.Cookies.AllKeys.Where(x => x.StartsWith("ufprt_"))) + { + _viewContext.HttpContext.Request.Cookies[c].Expires = DateTime.Now.AddDays(-1); + _viewContext.HttpContext.Response.SetCookie(_viewContext.HttpContext.Request.Cookies[c]); + } + + if (_method == FormMethod.Post) + { + //write out the hidden surface form routes + _viewContext.Writer.Write(""); + } + else + { + //since we are getting and we don't want this ugly value in the query string, we'll chuck it into a cookie with an id so we can retreive + //it on the server side with the same id and then remove it. + var id = Guid.NewGuid().ToString("N"); + var cookie = new HttpCookie("ufprt_" + id, _encryptedString); + _viewContext.HttpContext.Response.SetCookie(cookie); + + _viewContext.Writer.Write(""); + } + base.Dispose(disposing); } } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, null, new Dictionary(), method); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller @@ -254,6 +291,20 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary(), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller /// @@ -267,7 +318,25 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, htmlAttributes.ToDictionary(), method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller /// /// @@ -283,7 +352,28 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, htmlAttributes.ToDictionary()); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller /// /// @@ -302,6 +392,19 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary(), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -314,6 +417,20 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -327,6 +444,21 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T)); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary(), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -341,6 +473,21 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals, FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -355,7 +502,25 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes.ToDictionary(), method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -371,7 +536,25 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes.ToDictionary()); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -388,7 +571,40 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNull(surfaceType, "surfaceType"); + + var area = ""; + + var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers + .SingleOrDefault(x => x == surfaceType); + if (surfaceController == null) + throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); + var metaData = PluginController.GetMetadata(surfaceController); + if (metaData.AreaName.IsNullOrWhiteSpace() == false) + { + //set the area to the plugin area + area = metaData.AreaName; + } + return html.BeginUmbracoForm(action, metaData.ControllerName, area, additionalRouteVals, htmlAttributes, method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -400,26 +616,30 @@ namespace Umbraco.Web public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, object additionalRouteVals, IDictionary htmlAttributes) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNull(surfaceType, "surfaceType"); + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes, FormMethod.Post); + } - var area = ""; - - var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers - .SingleOrDefault(x => x == surfaceType); - if (surfaceController == null) - throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); - var metaData = PluginController.GetMetadata(surfaceController); - if (!metaData.AreaName.IsNullOrWhiteSpace()) - { - //set the area to the plugin area - area = metaData.AreaName; - } - return html.BeginUmbracoForm(action, metaData.ControllerName, area, additionalRouteVals, htmlAttributes); - } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); + } - /// + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -436,6 +656,20 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary(), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -449,7 +683,30 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary()); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery; + return html.RenderForm(formAction, method, htmlAttributes, controllerName, action, area, additionalRouteVals); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -462,13 +719,9 @@ namespace Umbraco.Web public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, object additionalRouteVals, IDictionary htmlAttributes) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - - var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery; - return html.RenderForm(formAction, FormMethod.Post, htmlAttributes, controllerName, action, area, additionalRouteVals); - } + { + return html.BeginUmbracoForm(action, controllerName, area, additionalRouteVals, htmlAttributes, FormMethod.Post); + } /// /// This renders out the form for us @@ -496,7 +749,7 @@ namespace Umbraco.Web { //ensure that the multipart/form-data is added to the html attributes - if (!htmlAttributes.ContainsKey("enctype")) + if (htmlAttributes.ContainsKey("enctype") == false) { htmlAttributes.Add("enctype", "multipart/form-data"); } @@ -507,7 +760,7 @@ namespace Umbraco.Web tagBuilder.MergeAttribute("action", formAction); // method is an explicit parameter, so it takes precedence over the htmlAttributes. tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); - var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled; + var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled == false; if (traditionalJavascriptEnabled) { // forms must have an ID for client validation @@ -516,7 +769,7 @@ namespace Umbraco.Web htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); //new UmbracoForm: - var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, additionalRouteVals); + var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); if (traditionalJavascriptEnabled) { diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 6756847980..ef894d266f 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -109,23 +109,52 @@ namespace Umbraco.Web.Mvc /// /// Checks the request and query strings to see if it matches the definition of having a Surface controller - /// posted value, if so, then we return a PostedDataProxyInfo object with the correct information. + /// posted/get value, if so, then we return a PostedDataProxyInfo object with the correct information. /// /// /// - private static PostedDataProxyInfo GetPostedFormInfo(RequestContext requestContext) - { - if (requestContext.HttpContext.Request.RequestType != "POST") - return null; + private static PostedDataProxyInfo GetFormInfo(RequestContext requestContext) + { + //if it is a POST/GET then a value must be in the request + if ((requestContext.HttpContext.Request.RequestType == "POST" || requestContext.HttpContext.Request.RequestType == "GET") + && requestContext.HttpContext.Request["ufprt"].IsNullOrWhiteSpace()) + { + return null; + } - //this field will contain a base64 encoded version of the surface route vals - if (requestContext.HttpContext.Request["uformpostroutevals"].IsNullOrWhiteSpace()) - return null; + string encodedVal; + + switch (requestContext.HttpContext.Request.RequestType) + { + case "POST": + //get the value from the request. + //this field will contain an encrypted version of the surface route vals. + encodedVal = requestContext.HttpContext.Request["ufprt"]; + break; + case "GET": + //get the value from the cookie based on the id sent as a query string. + var cookieId = "ufprt_" + requestContext.HttpContext.Request["ufprt"]; + if (requestContext.HttpContext.Request.Cookies[cookieId] == null || requestContext.HttpContext.Request.Cookies[cookieId].Value.IsNullOrWhiteSpace()) + { + LogHelper.Warn("Umbraco cannot process the GET form action, could not find the required cookie value for cookie name " + cookieId); + //we cannot continue if there is no cookie value + return null; + } - var encodedVal = requestContext.HttpContext.Request["uformpostroutevals"]; - var decryptedString = encodedVal.DecryptWithMachineKey(); - var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); + //we need to ensure the cookie is gone + var outgoingCookie = requestContext.HttpContext.Request.Cookies[cookieId]; + outgoingCookie.Expires = DateTime.Now.AddDays(-1); + requestContext.HttpContext.Response.SetCookie(outgoingCookie); + //this field will contain an encrypted version of the surface route vals. + encodedVal = requestContext.HttpContext.Request.Cookies[cookieId].Value; + break; + default: + return null; + } + + var decryptedString = encodedVal.DecryptWithMachineKey(); + var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); var decodedParts = new Dictionary(); foreach (var key in parsedQueryString.AllKeys) @@ -336,7 +365,7 @@ namespace Umbraco.Web.Mvc var routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); //Need to check for a special case if there is form data being posted back to an Umbraco URL - var postedInfo = GetPostedFormInfo(requestContext); + var postedInfo = GetFormInfo(requestContext); if (postedInfo != null) { return HandlePostedValues(requestContext, postedInfo); From 0c0583653716c46bc2b8b70d5f46fe2997ff2fda Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 5 Sep 2013 10:44:28 +0200 Subject: [PATCH 35/45] Fixes U4-2791 Error creating node containing @ v6.1.4. Adding unit test which covers both U4-2791 and U4-2607 --- .../Querying/BaseExpressionHelper.cs | 7 +++-- .../Querying/ModelToSqlExpressionHelper.cs | 4 +-- .../Querying/PocoToSqlExpressionHelper.cs | 4 +-- .../Repositories/ContentRepositoryTest.cs | 31 +++++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 70d89d7940..ac84b5cee3 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -64,8 +64,11 @@ namespace Umbraco.Core.Persistence.Querying public virtual string EscapeAtArgument(string exp) { - if (exp.StartsWith("@")) - return string.Concat("@", exp); + /*if (exp.StartsWith("@")) + return string.Concat("@", exp);*/ + + if (exp.Contains("@")) + return exp.Replace("@", "@@"); return exp; } diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index 76ca62139f..738cc8a23c 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -248,9 +248,9 @@ namespace Umbraco.Core.Persistence.Querying case "StartsWith": return string.Format("upper({0}) like '{1}%'", r, EscapeAtArgument(RemoveQuote(args[0].ToString().ToUpper()))); case "EndsWith": - return string.Format("upper({0}) like '%{1}'", r, RemoveQuote(args[0].ToString()).ToUpper()); + return string.Format("upper({0}) like '%{1}'", r, EscapeAtArgument(RemoveQuote(args[0].ToString()).ToUpper())); case "Contains": - return string.Format("{0} like '%{1}%'", r, RemoveQuote(args[0].ToString()).ToUpper()); + return string.Format("{0} like '%{1}%'", r, EscapeAtArgument(RemoveQuote(args[0].ToString()).ToUpper())); case "Substring": var startIndex = Int32.Parse(args[0].ToString()) + 1; if (args.Count == 2) diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs index 41057ed6c5..555b87f6e4 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs @@ -252,9 +252,9 @@ namespace Umbraco.Core.Persistence.Querying case "StartsWith": return string.Format("upper({0}) like '{1}%'", r, EscapeAtArgument(RemoveQuote(args[0].ToString().ToUpper()))); case "EndsWith": - return string.Format("upper({0}) like '%{1}'", r, RemoveQuote(args[0].ToString()).ToUpper()); + return string.Format("upper({0}) like '%{1}'", r, EscapeAtArgument(RemoveQuote(args[0].ToString()).ToUpper())); case "Contains": - return string.Format("upper({0}) like '%{1}%'", r, RemoveQuote(args[0].ToString()).ToUpper()); + return string.Format("upper({0}) like '%{1}%'", r, EscapeAtArgument(RemoveQuote(args[0].ToString()).ToUpper())); case "Substring": var startIndex = Int32.Parse(args[0].ToString()) + 1; if (args.Count == 2) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 973000b9ca..fec3d9ca62 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -106,6 +106,37 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(textpage.HasIdentity, Is.True); } + //Covers issue U4-2791 and U4-2607 + [Test] + public void Can_Save_Content_With_AtSign_In_Name_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + var contentTypeRepository = RepositoryResolver.Current.ResolveByType(unitOfWork); + var repository = RepositoryResolver.Current.ResolveByType(unitOfWork); + + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); + Content textpage = MockedContent.CreateSimpleContent(contentType, "test@umbraco.org", -1); + Content anotherTextpage = MockedContent.CreateSimpleContent(contentType, "@lightgiants", -1); + + // Act + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(textpage); + repository.AddOrUpdate(anotherTextpage); + unitOfWork.Commit(); + + // Assert + Assert.That(contentType.HasIdentity, Is.True); + Assert.That(textpage.HasIdentity, Is.True); + + var content = repository.Get(textpage.Id); + Assert.That(content.Name, Is.EqualTo(textpage.Name)); + + var content2 = repository.Get(anotherTextpage.Id); + Assert.That(content2.Name, Is.EqualTo(anotherTextpage.Name)); + } + [Test] public void Can_Perform_Multiple_Adds_On_ContentRepository() { From 4770e903fbee600154777fbb9fcb0392068c0f2b Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 5 Sep 2013 18:22:48 +0100 Subject: [PATCH 36/45] Minor corrected to uQuery doco/comments. --- .../umbraco.presentation/umbraco/uQuery/uQuery-DataTypes.cs | 2 +- .../umbraco.presentation/umbraco/uQuery/uQuery-Dictionary.cs | 2 +- .../umbraco.presentation/umbraco/uQuery/uQuery-PreValues.cs | 2 +- src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-DataTypes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-DataTypes.cs index 3aca4adda3..28a72cd1a1 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-DataTypes.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-DataTypes.cs @@ -9,7 +9,7 @@ using umbraco.cms.businesslogic.datatype; namespace umbraco { /// - /// Static helper methods, previously this class was UmbracoHelper + /// Static helper methods /// public static partial class uQuery { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Dictionary.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Dictionary.cs index 298fb983ba..700b223b81 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Dictionary.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Dictionary.cs @@ -4,7 +4,7 @@ using umbraco.cms.businesslogic; namespace umbraco { /// - /// Static helper methods, previously this class was UmbracoHelper + /// Static helper methods /// public static partial class uQuery { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-PreValues.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-PreValues.cs index 01ff449a3c..987ceb0303 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-PreValues.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-PreValues.cs @@ -8,7 +8,7 @@ using umbraco.NodeFactory; namespace umbraco { /// - /// Static helper methods, previously this class was UmbracoHelper + /// Static helper methods /// public static partial class uQuery { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery.cs index 0b46a6582f..34d3ce69b3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery.cs @@ -13,7 +13,7 @@ using umbraco.DataLayer; namespace umbraco { /// - /// uQuery - static helper methods, previously this class was UmbracoHelper + /// uQuery - static helper methods /// public static partial class uQuery { From fee02af4eb9c7da5f20669c3eec78d0658823409 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 6 Sep 2013 11:40:37 +1000 Subject: [PATCH 37/45] Completes: U4-1639 Add support to generate GET urls to SurfaceController actions --- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 52 ++----- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 69 ++++----- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/UmbracoHelper.cs | 28 ++++ src/Umbraco.Web/UrlHelperRenderExtensions.cs | 133 ++++++++++++++++++ 5 files changed, 197 insertions(+), 86 deletions(-) create mode 100644 src/Umbraco.Web/UrlHelperRenderExtensions.cs diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 1a0bee2586..2a009dfaa2 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -188,21 +188,19 @@ namespace Umbraco.Web /// internal class UmbracoForm : MvcForm { - - /// /// Creates an UmbracoForm /// /// - /// - /// + /// + /// /// /// /// public UmbracoForm( ViewContext viewContext, - string surfaceController, - string surfaceAction, + string controllerName, + string controllerAction, string area, FormMethod method, object additionalRouteVals = null) @@ -210,21 +208,7 @@ namespace Umbraco.Web { _viewContext = viewContext; _method = method; - //need to create a params string as Base64 to put into our hidden field to use during the routes - var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", - viewContext.HttpContext.Server.UrlEncode(surfaceController), - viewContext.HttpContext.Server.UrlEncode(surfaceAction), - area); - - var additionalRouteValsAsQuery = additionalRouteVals != null ? additionalRouteVals.ToDictionary().ToQueryString() : null; - - if (additionalRouteValsAsQuery.IsNullOrWhiteSpace() == false) - surfaceRouteParams += "&" + additionalRouteValsAsQuery; - - if (string.IsNullOrWhiteSpace(surfaceRouteParams) == false) - { - _encryptedString = surfaceRouteParams.EncryptWithMachineKey(); - } + _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); } private readonly ViewContext _viewContext; @@ -238,29 +222,8 @@ namespace Umbraco.Web return; this._disposed = true; - //Need to ensure any stale GET cookies are cleared before continuing - foreach (var c in _viewContext.HttpContext.Request.Cookies.AllKeys.Where(x => x.StartsWith("ufprt_"))) - { - _viewContext.HttpContext.Request.Cookies[c].Expires = DateTime.Now.AddDays(-1); - _viewContext.HttpContext.Response.SetCookie(_viewContext.HttpContext.Request.Cookies[c]); - } - - if (_method == FormMethod.Post) - { - //write out the hidden surface form routes - _viewContext.Writer.Write(""); - } - else - { - //since we are getting and we don't want this ugly value in the query string, we'll chuck it into a cookie with an id so we can retreive - //it on the server side with the same id and then remove it. - var id = Guid.NewGuid().ToString("N"); - var cookie = new HttpCookie("ufprt_" + id, _encryptedString); - _viewContext.HttpContext.Response.SetCookie(cookie); - - _viewContext.Writer.Write(""); - } - + //write out the hidden surface form routes + _viewContext.Writer.Write(""); base.Dispose(disposing); } @@ -544,6 +507,7 @@ namespace Umbraco.Web /// /// /// + /// /// public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals, diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index ef894d266f..1ebfebf1e4 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -116,8 +116,8 @@ namespace Umbraco.Web.Mvc private static PostedDataProxyInfo GetFormInfo(RequestContext requestContext) { //if it is a POST/GET then a value must be in the request - if ((requestContext.HttpContext.Request.RequestType == "POST" || requestContext.HttpContext.Request.RequestType == "GET") - && requestContext.HttpContext.Request["ufprt"].IsNullOrWhiteSpace()) + if (requestContext.HttpContext.Request.QueryString["ufprt"].IsNullOrWhiteSpace() + && requestContext.HttpContext.Request.Form["ufprt"].IsNullOrWhiteSpace()) { return null; } @@ -129,31 +129,28 @@ namespace Umbraco.Web.Mvc case "POST": //get the value from the request. //this field will contain an encrypted version of the surface route vals. - encodedVal = requestContext.HttpContext.Request["ufprt"]; + encodedVal = requestContext.HttpContext.Request.Form["ufprt"]; break; case "GET": - //get the value from the cookie based on the id sent as a query string. - var cookieId = "ufprt_" + requestContext.HttpContext.Request["ufprt"]; - if (requestContext.HttpContext.Request.Cookies[cookieId] == null || requestContext.HttpContext.Request.Cookies[cookieId].Value.IsNullOrWhiteSpace()) - { - LogHelper.Warn("Umbraco cannot process the GET form action, could not find the required cookie value for cookie name " + cookieId); - //we cannot continue if there is no cookie value - return null; - } - - //we need to ensure the cookie is gone - var outgoingCookie = requestContext.HttpContext.Request.Cookies[cookieId]; - outgoingCookie.Expires = DateTime.Now.AddDays(-1); - requestContext.HttpContext.Response.SetCookie(outgoingCookie); - //this field will contain an encrypted version of the surface route vals. - encodedVal = requestContext.HttpContext.Request.Cookies[cookieId].Value; + encodedVal = requestContext.HttpContext.Request.QueryString["ufprt"]; break; default: return null; } - var decryptedString = encodedVal.DecryptWithMachineKey(); + + string decryptedString; + try + { + decryptedString = encodedVal.DecryptWithMachineKey(); + } + catch (FormatException) + { + LogHelper.Warn("A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); + return null; + } + var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); var decodedParts = new Dictionary(); @@ -165,31 +162,19 @@ namespace Umbraco.Web.Mvc //validate all required keys exist //the controller - if (!decodedParts.Any(x => x.Key == ReservedAdditionalKeys.Controller)) + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Controller)) return null; //the action - if (!decodedParts.Any(x => x.Key == ReservedAdditionalKeys.Action)) + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Action)) return null; //the area - if (!decodedParts.Any(x => x.Key == ReservedAdditionalKeys.Area)) + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Area)) return null; - - ////the controller type, if it contains this then it is a plugin controller, not locally declared. - //if (decodedParts.Any(x => x.Key == "t")) - //{ - // return new PostedDataProxyInfo - // { - // ControllerName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "c").Value), - // ActionName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "a").Value), - // Area = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "ar").Value), - // ControllerType = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "t").Value) - // }; - //} - - foreach (var item in decodedParts.Where(x => !new string[] { - ReservedAdditionalKeys.Controller, - ReservedAdditionalKeys.Action, - ReservedAdditionalKeys.Area }.Contains(x.Key))) + + foreach (var item in decodedParts.Where(x => new[] { + ReservedAdditionalKeys.Controller, + ReservedAdditionalKeys.Action, + ReservedAdditionalKeys.Area }.Contains(x.Key) == false)) { // Populate route with additional values which aren't reserved values so they eventually to action parameters requestContext.RouteData.Values[item.Key] = item.Value; @@ -198,9 +183,9 @@ namespace Umbraco.Web.Mvc //return the proxy info without the surface id... could be a local controller. return new PostedDataProxyInfo { - ControllerName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Controller).Value), - ActionName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Action).Value), - Area = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Area).Value), + ControllerName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Controller).Value), + ActionName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Action).Value), + Area = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Area).Value), }; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ed3c6a1822..6ad8ce6049 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -405,6 +405,7 @@ + diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 9666506cb4..c0b59819c4 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1305,6 +1305,34 @@ namespace Umbraco.Web #endregion + /// + /// This is used in methods like BeginUmbracoForm and SurfaceAction to generate an encrypted string which gets submitted in a request for which + /// Umbraco can decrypt during the routing process in order to delegate the request to a specific MVC Controller. + /// + /// + /// + /// + /// + /// + internal static string CreateEncryptedRouteString(string controllerName, string controllerAction, string area, object additionalRouteVals = null) + { + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + Mandate.ParameterNotNullOrEmpty(controllerAction, "controllerAction"); + Mandate.ParameterNotNull(area, "area"); + + //need to create a params string as Base64 to put into our hidden field to use during the routes + var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", + HttpUtility.UrlEncode(controllerName), + HttpUtility.UrlEncode(controllerAction), + area); + + var additionalRouteValsAsQuery = additionalRouteVals != null ? additionalRouteVals.ToDictionary().ToQueryString() : null; + + if (additionalRouteValsAsQuery.IsNullOrWhiteSpace() == false) + surfaceRouteParams += "&" + additionalRouteValsAsQuery; + + return surfaceRouteParams.EncryptWithMachineKey(); + } } } diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs new file mode 100644 index 0000000000..2eb1d8a060 --- /dev/null +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -0,0 +1,133 @@ +using System; +using System.Linq; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web +{ + /// + /// Extension methods for UrlHelper for use in templates + /// + public static class UrlHelperRenderExtensions + { + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, string controllerName) + { + return url.SurfaceAction(action, controllerName, null); + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, string controllerName, object additionalRouteVals) + { + return url.SurfaceAction(action, controllerName, "", additionalRouteVals); + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, string controllerName, string area, object additionalRouteVals) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + var encryptedRoute = UmbracoHelper.CreateEncryptedRouteString(controllerName, action, area, additionalRouteVals); + + var result = UmbracoContext.Current.OriginalRequestUrl.AbsolutePath.EnsureEndsWith('?') + "ufprt=" + encryptedRoute; + return result; + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, Type surfaceType) + { + return url.SurfaceAction(action, surfaceType, null); + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, Type surfaceType, object additionalRouteVals) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNull(surfaceType, "surfaceType"); + + var area = ""; + + var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers + .SingleOrDefault(x => x == surfaceType); + if (surfaceController == null) + throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); + var metaData = PluginController.GetMetadata(surfaceController); + if (metaData.AreaName.IsNullOrWhiteSpace() == false) + { + //set the area to the plugin area + area = metaData.AreaName; + } + + var encryptedRoute = UmbracoHelper.CreateEncryptedRouteString(metaData.ControllerName, action, area, additionalRouteVals); + + var result = UmbracoContext.Current.OriginalRequestUrl.AbsolutePath.EnsureEndsWith('?') + "ufprt=" + encryptedRoute; + return result; + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action) + where T : SurfaceController + { + return url.SurfaceAction(action, typeof (T)); + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, object additionalRouteVals) + where T : SurfaceController + { + return url.SurfaceAction(action, typeof (T), additionalRouteVals); + } + + + } +} \ No newline at end of file From 303c1e16b8498c287a8d0389c6cd9f414a7ddb04 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 6 Sep 2013 16:17:08 +0200 Subject: [PATCH 38/45] Change document type did unnecessary checks on root node which resulted in YSOD --- .../umbraco/dialogs/ChangeDocType.aspx.cs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs index 39c0efc74d..6e4562b757 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs } private IContent _content; - + protected void Page_Load(object sender, EventArgs e) { var contentNodeId = int.Parse(Request.QueryString["id"]); @@ -42,14 +42,14 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs { DisplayNotAvailable(); } - } + } } private void LocalizeTexts() { ChangeDocTypePane.Text = global::umbraco.ui.Text("changeDocType", "selectNewDocType"); ContentNamePropertyPanel.Text = global::umbraco.ui.Text("changeDocType", "selectedContent"); - CurrentTypePropertyPanel.Text = global::umbraco.ui.Text("changeDocType", "currentType"); + CurrentTypePropertyPanel.Text = global::umbraco.ui.Text("changeDocType", "currentType"); NewTypePropertyPanel.Text = global::umbraco.ui.Text("changeDocType", "newType"); NewTemplatePropertyPanel.Text = global::umbraco.ui.Text("changeDocType", "newTemplate"); ChangeDocTypePropertyMappingPane.Text = global::umbraco.ui.Text("changeDocType", "mapProperties"); @@ -67,17 +67,13 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs // Get all content types var documentTypes = ApplicationContext.Current.Services.ContentTypeService.GetAllContentTypes(); - // Save a flag if the allowed at root option has been set for any document types (if not, then all are allowed there) - var haveTypesAllowedAtRootBeenDefined = documentTypes.Any(x => x.AllowedAsRoot); - // Remove current one documentTypes = documentTypes.Where(x => x.Id != _content.ContentType.Id); // Remove any not valid for current location - if (_content.ParentId == -1 && haveTypesAllowedAtRootBeenDefined) + if (_content.ParentId == -1) { - // Root content, and at least one type has been defined as allowed at root, so only include those that have - // been selected as allowed + // Root content, only include those that have been selected as allowed at root documentTypes = documentTypes.Where(x => x.AllowedAsRoot); } else @@ -90,7 +86,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs } // If we have at least one, bind to list and return true - if (documentTypes.Any()) + if (documentTypes.Any()) { NewDocumentTypeList.DataSource = documentTypes.OrderBy(x => x.Name); NewDocumentTypeList.DataValueField = "Id"; @@ -138,7 +134,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs // Get properties of new document type (including any from parent types) var properties = GetPropertiesOfContentType(contentType); - + // Loop through list of source properties and populate destination options with all those of same property type foreach (RepeaterItem ri in PropertyMappingRepeater.Items) { @@ -146,10 +142,10 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs { // Get data type from hidden field var dataTypeId = Guid.Parse(((HiddenField)ri.FindControl("DataTypeId")).Value); - + // Bind destination list with properties that match data type var ddl = (DropDownList)ri.FindControl("DestinationProperty"); - ddl.DataSource = properties.Where(x => x.DataTypeId == dataTypeId); + ddl.DataSource = properties.Where(x => x.DataTypeId == dataTypeId); ddl.DataValueField = "Alias"; ddl.DataTextField = "Name"; ddl.DataBind(); @@ -162,7 +158,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs { item.Selected = true; } - } + } } } @@ -207,7 +203,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs // Get flag for if content already published var wasPublished = _content.Published; - + // Change the document type passing flag to clear the properties var newContentType = GetSelectedDocumentType(); _content.ChangeContentType(newContentType, true); @@ -226,7 +222,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs var propertiesMappedMessageBuilder = new StringBuilder("
    "); foreach (var propertyMapping in propertyMappings) { - propertiesMappedMessageBuilder.AppendFormat("
  • {0} {1} {2}
  • ", + propertiesMappedMessageBuilder.AppendFormat("
  • {0} {1} {2}
  • ", propertyMapping.FromName, global::umbraco.ui.Text("changeDocType", "to"), propertyMapping.ToName); _content.SetValue(propertyMapping.ToAlias, propertyMapping.Value); } @@ -284,9 +280,9 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs var mappedPropertyAlias = ddl.SelectedItem.Value; if (!string.IsNullOrEmpty(mappedPropertyAlias)) { - if (mappedPropertyAliases.Contains(mappedPropertyAlias)) + if (mappedPropertyAliases.Contains(mappedPropertyAlias)) { - ValidationError.Text = global::umbraco.ui.Text("changeDocType", "validationErrorPropertyWithMoreThanOneMapping"); + ValidationError.Text = global::umbraco.ui.Text("changeDocType", "validationErrorPropertyWithMoreThanOneMapping"); return false; } From 39316a345ff0b44e365cf1be00fcb22a78a11a87 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 6 Sep 2013 20:21:47 +0200 Subject: [PATCH 39/45] Tests - stop deleting dummy.txt files --- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 58cc7c6974..0ba2a08654 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Configuration; using System.IO; using System.Linq; @@ -116,12 +117,18 @@ namespace Umbraco.Tests.TestHelpers } public static void CleanDirectories(string[] directories) - { + { + var preserves = new Dictionary + { + { SystemDirectories.Masterpages, new[] {"dummy.txt"} }, + { SystemDirectories.MvcViews, new[] {"dummy.txt"} } + }; foreach (var directory in directories) { var directoryInfo = new DirectoryInfo(IOHelper.MapPath(directory)); + var preserve = preserves.ContainsKey(directory) ? preserves[directory] : null; if (directoryInfo.Exists) - directoryInfo.GetFiles().ForEach(x => x.Delete()); + directoryInfo.GetFiles().Where(x => preserve == null || preserve.Contains(x.Name) == false).ForEach(x => x.Delete()); } } From c5197dbe1720fecaf84f9a9a95a845eecc97f92f Mon Sep 17 00:00:00 2001 From: Stephan Date: Sat, 7 Sep 2013 10:24:52 +0200 Subject: [PATCH 40/45] U4-2807 - better exception message on bogus domain --- src/Umbraco.Core/StringExtensions.cs | 52 +++++++++++++++++++++++++ src/Umbraco.Web/Routing/DomainAndUri.cs | 12 +++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 87b4c676b5..e730bafbdc 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -28,6 +28,17 @@ namespace Umbraco.Core [UmbracoWillObsolete("Do not use this constants. See IShortStringHelper.CleanStringForSafeAliasJavaScriptCode.")] public const string UmbracoInvalidFirstCharacters = "01234567890"; + private static readonly char[] ToCSharpHexDigitLower = "0123456789abcdef".ToCharArray(); + private static readonly char[] ToCSharpEscapeChars; + + static StringExtensions() + { + var escapes = new[] { "\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00" }; + ToCSharpEscapeChars = new char[escapes.Max(e => e[0]) + 1]; + foreach (var escape in escapes) + ToCSharpEscapeChars[escape[0]] = escape[1]; + } + internal static string ReplaceNonAlphanumericChars(this string input, char replacement) { //any character that is not alphanumeric, convert to a hyphen @@ -1085,5 +1096,46 @@ namespace Umbraco.Core return source; } + + /// + /// Converts a literal string into a C# expression. + /// + /// Current instance of the string. + /// The string in a C# format. + public static string ToCSharpString(this string s) + { + if (s == null) return ""; + + // http://stackoverflow.com/questions/323640/can-i-convert-a-c-sharp-string-value-to-an-escaped-string-literal + + var sb = new StringBuilder(s.Length + 2); + for (var rp = 0; rp < s.Length; rp++) + { + var c = s[rp]; + if (c < ToCSharpEscapeChars.Length && '\0' != ToCSharpEscapeChars[c]) + sb.Append('\\').Append(ToCSharpEscapeChars[c]); + else if ('~' >= c && c >= ' ') + sb.Append(c); + else + sb.Append(@"\x") + .Append(ToCSharpHexDigitLower[c >> 12 & 0x0F]) + .Append(ToCSharpHexDigitLower[c >> 8 & 0x0F]) + .Append(ToCSharpHexDigitLower[c >> 4 & 0x0F]) + .Append(ToCSharpHexDigitLower[c & 0x0F]); + } + + return sb.ToString(); + + // requires full trust + /* + using (var writer = new StringWriter()) + using (var provider = CodeDomProvider.CreateProvider("CSharp")) + { + provider.GenerateCodeFromExpression(new CodePrimitiveExpression(s), writer, null); + return writer.ToString().Replace(string.Format("\" +{0}\t\"", Environment.NewLine), ""); + } + */ + } + } } diff --git a/src/Umbraco.Web/Routing/DomainAndUri.cs b/src/Umbraco.Web/Routing/DomainAndUri.cs index ba13508812..ec3e52ff11 100644 --- a/src/Umbraco.Web/Routing/DomainAndUri.cs +++ b/src/Umbraco.Web/Routing/DomainAndUri.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core; using umbraco.cms.businesslogic.web; namespace Umbraco.Web.Routing @@ -20,7 +21,16 @@ namespace Umbraco.Web.Routing public DomainAndUri(Domain domain, string scheme) { Domain = domain; - Uri = new Uri(UriUtility.TrimPathEndSlash(UriUtility.StartWithScheme(domain.Name, scheme))); + try + { + Uri = new Uri(UriUtility.TrimPathEndSlash(UriUtility.StartWithScheme(domain.Name, scheme))); + } + catch (UriFormatException) + { + var name = domain.Name.ToCSharpString(); + throw new ArgumentException(string.Format("Failed to parse invalid domain: node id={0}, hostname=\"{1}\"." + + " Hostname should be a valid uri.", domain.RootNodeId, name), "domain"); + } } /// From 686250c58689fb96b7f33fae522eb158420e97df Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 8 Sep 2013 19:42:34 +0200 Subject: [PATCH 41/45] Fixed U4-608 Empty tinymce config throws null ref exception --- .../tinymce/tinyMCEConfiguration.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/umbraco.editorControls/tinymce/tinyMCEConfiguration.cs b/src/umbraco.editorControls/tinymce/tinyMCEConfiguration.cs index 29bcee442b..5e25de43f8 100644 --- a/src/umbraco.editorControls/tinymce/tinyMCEConfiguration.cs +++ b/src/umbraco.editorControls/tinymce/tinyMCEConfiguration.cs @@ -12,11 +12,11 @@ namespace umbraco.editorControls.tinymce { private static bool _init = false; - private static Hashtable _commands = new Hashtable(); + private static Hashtable _commands = new Hashtable(StringComparer.InvariantCultureIgnoreCase); private static string _validElements; - private static Hashtable _configOptions = new Hashtable(); + private static Hashtable _configOptions = new Hashtable(StringComparer.InvariantCultureIgnoreCase); public static Hashtable ConfigOptions { @@ -59,7 +59,7 @@ namespace umbraco.editorControls.tinymce set { _invalidElements = value; } } - private static Hashtable _plugins = new Hashtable(); + private static Hashtable _plugins = new Hashtable(StringComparer.InvariantCultureIgnoreCase); public static Hashtable Plugins { @@ -103,7 +103,7 @@ namespace umbraco.editorControls.tinymce { // Load config XmlDocument xd = new XmlDocument(); - xd.Load( IOHelper.MapPath( SystemFiles.TinyMceConfig ) ); + xd.Load(IOHelper.MapPath( SystemFiles.TinyMceConfig )); foreach (XmlNode n in xd.DocumentElement.SelectNodes("//command")) { @@ -146,11 +146,15 @@ namespace umbraco.editorControls.tinymce foreach (XmlNode n in xd.DocumentElement.SelectNodes("//config")) { - if (!_configOptions.ContainsKey(n.FirstChild.Value)) + if (!_configOptions.ContainsKey(n.Attributes["key"].FirstChild.Value)) { + var value = ""; + if (n.FirstChild != null) + value = n.FirstChild.Value; + _configOptions.Add( n.Attributes["key"].FirstChild.Value.ToLower(), - n.FirstChild.Value); + value); } } From 7ddb77db66adf5d08f19bbf681dedaefada4bcab Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 8 Sep 2013 21:06:22 +0200 Subject: [PATCH 42/45] Fixed U4-576 Umb 4.7.x - Dashboards --- src/Umbraco.Web.UI/config/Dashboard.Release.config | 6 ++++++ src/Umbraco.Web.UI/config/Dashboard.config | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/Umbraco.Web.UI/config/Dashboard.Release.config b/src/Umbraco.Web.UI/config/Dashboard.Release.config index 9ef51f9e32..63e1816c9d 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.Release.config +++ b/src/Umbraco.Web.UI/config/Dashboard.Release.config @@ -38,6 +38,9 @@ + + admin + /umbraco/dashboard/mediadashboardintro.ascx @@ -58,6 +61,9 @@ content + + admin + /umbraco/dashboard/startupdashboardintro.ascx diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config index fd9f356b72..2418f8122e 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.config +++ b/src/Umbraco.Web.UI/config/Dashboard.config @@ -36,6 +36,9 @@ + + admin + /umbraco/dashboard/mediadashboardintro.ascx @@ -55,6 +58,9 @@ content + + admin + /umbraco/dashboard/startupdashboardintro.ascx From edd9aa80aea36130bfc68ae775a52f899c2325f3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 8 Sep 2013 21:18:00 +0200 Subject: [PATCH 43/45] Fixes U4-489 Installer, link to folder permission video broken --- .../install/steps/validatePermissions.ascx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI/install/steps/validatePermissions.ascx b/src/Umbraco.Web.UI/install/steps/validatePermissions.ascx index fc326e44b5..3d533a46a4 100644 --- a/src/Umbraco.Web.UI/install/steps/validatePermissions.ascx +++ b/src/Umbraco.Web.UI/install/steps/validatePermissions.ascx @@ -43,16 +43,8 @@ In order to run umbraco, you'll need to update your permission settings.

    How to Resolve

    -

    Watch our video tutorial on setting up folder permissions for umbraco or read the text version.

    - - -
    - - Watch: Setting up folder permissions -
    - -