diff --git a/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs b/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs index e9c79ae57e..9833d9ab74 100644 --- a/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs @@ -30,7 +30,6 @@ namespace Umbraco.Core.Models.Rdbms public DateTime DateRegistered { get; set; } [Column("lastNotifiedDate")] - [Constraint(Default = "getdate()")] public DateTime LastNotified { get; set; } [Column("isActive")] diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index 7ae81aaa16..cf3fd1511f 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -21,14 +21,18 @@ namespace Umbraco.Core.Persistence /// /// This will escape single @ symbols for peta poco values so it doesn't think it's a parameter /// - /// /// /// - public static string EscapeAtSymbols(this Database db, string value) + public static string EscapeAtSymbols(string value) { - //this fancy regex will only match a single @ not a double, etc... - var regex = new Regex("(?(this Database db) diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 44570adcd4..aff816515d 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -64,13 +64,7 @@ namespace Umbraco.Core.Persistence.Querying public virtual string EscapeAtArgument(string exp) { - /*if (exp.StartsWith("@")) - return string.Concat("@", exp);*/ - - if (exp.Contains("@")) - return exp.Replace("@", "@@"); - - return exp; + return PetaPocoExtensions.EscapeAtSymbols(exp); } public virtual bool ShouldQuoteValue(Type fieldType) diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index 738cc8a23c..611d9c63a3 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; +using System.Linq; using System.Linq.Expressions; using System.Text; using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { @@ -226,6 +228,39 @@ namespace Umbraco.Core.Persistence.Querying } + private string HandleStringComparison(string col, string val, string verb, TextColumnType columnType) + { + switch (verb) + { + case "Equals": + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); + case "StartsWith": + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnStartsWithComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); + case "EndsWith": + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEndsWithComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); + case "Contains": + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnContainsComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); + case "InvariantEquals": + case "SqlEquals": + //recurse + return HandleStringComparison(col, val, "Equals", columnType); + case "InvariantStartsWith": + case "SqlStartsWith": + //recurse + return HandleStringComparison(col, val, "StartsWith", columnType); + case "InvariantEndsWith": + case "SqlEndsWith": + //recurse + return HandleStringComparison(col, val, "EndsWith", columnType); + case "InvariantContains": + case "SqlContains": + //recurse + return HandleStringComparison(col, val, "Contains", columnType); + default: + throw new ArgumentOutOfRangeException("verb"); + } + } + protected virtual string VisitMethodCall(MethodCallExpression m) { List args = this.VisitExpressionList(m.Arguments); @@ -245,12 +280,31 @@ namespace Umbraco.Core.Persistence.Querying return string.Format("upper({0})", r); case "ToLower": return string.Format("lower({0})", r); + 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, EscapeAtArgument(RemoveQuote(args[0].ToString()).ToUpper())); case "Contains": - return string.Format("{0} like '%{1}%'", r, EscapeAtArgument(RemoveQuote(args[0].ToString()).ToUpper())); + case "Equals": + case "SqlStartsWith": + case "SqlEndsWith": + case "SqlContains": + case "SqlEquals": + case "InvariantStartsWith": + case "InvariantEndsWith": + case "InvariantContains": + case "InvariantEquals": + //default + var colType = TextColumnType.NVarchar; + //then check if this arg has been passed in + if (m.Arguments.Count > 1) + { + var colTypeArg = m.Arguments.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); + if (colTypeArg != null) + { + colType = (TextColumnType) ((ConstantExpression) colTypeArg).Value; + } + } + return HandleStringComparison(r.ToString(), args[0].ToString(), m.Method.Name, colType); case "Substring": var startIndex = Int32.Parse(args[0].ToString()) + 1; if (args.Count == 2) diff --git a/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs b/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs new file mode 100644 index 0000000000..82c158a9c6 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs @@ -0,0 +1,30 @@ +using System; + +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// String extension methods used specifically to translate into SQL + /// + internal static class SqlStringExtensions + { + public static bool SqlContains(this string str, string txt, TextColumnType columnType) + { + return str.InvariantContains(txt); + } + + public static bool SqlEquals(this string str, string txt, TextColumnType columnType) + { + return str.InvariantEquals(txt); + } + + public static bool SqlStartsWith(this string str, string txt, TextColumnType columnType) + { + return str.InvariantStartsWith(txt); + } + + public static bool SqlEndsWith(this string str, string txt, TextColumnType columnType) + { + return str.InvariantEndsWith(txt); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs new file mode 100644 index 0000000000..f4245b931a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// Determines how to match a string property value + /// + public enum StringPropertyMatchType + { + Exact, + Contains, + StartsWith, + EndsWith + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/TextColumnType.cs b/src/Umbraco.Core/Persistence/Querying/TextColumnType.cs new file mode 100644 index 0000000000..f33022fcbe --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/TextColumnType.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Persistence.Querying +{ + public enum TextColumnType + { + NVarchar, + NText + } +} \ 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 f186bc066c..614d40396b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -449,7 +449,7 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(string username) { var sql = new Sql(); - var escapedUserName = Database.EscapeAtSymbols(username); + var escapedUserName = PetaPocoExtensions.EscapeAtSymbols(username); sql.Select("COUNT(*)") .From() .Where(x => x.LoginName == escapedUserName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 1182487376..d423bdf6aa 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { @@ -25,6 +26,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// public interface ISqlSyntaxProvider { + string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); + string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); + string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); + string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); + string GetQuotedTableName(string tableName); string GetQuotedColumnName(string columnName); string GetQuotedName(string name); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntax.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntax.cs new file mode 100644 index 0000000000..00b07ae15a --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntax.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Static class that provides simple access to the MySql SqlSyntax Provider + /// + internal static class MySqlSyntax + { + public static ISqlSyntaxProvider Provider { get { return new MySqlSyntaxProvider(); } } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 8f466e3829..5f5d412ab3 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -6,14 +6,6 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Persistence.SqlSyntax { - /// - /// Static class that provides simple access to the MySql SqlSyntax Provider - /// - internal static class MySqlSyntax - { - public static ISqlSyntaxProvider Provider { get { return new MySqlSyntaxProvider(); } } - } - /// /// Represents an SqlSyntaxProvider for MySql /// diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntax.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntax.cs new file mode 100644 index 0000000000..29660355fe --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntax.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Static class that provides simple access to the Sql CE SqlSyntax Provider + /// + internal static class SqlCeSyntax + { + public static ISqlSyntaxProvider Provider { get { return new SqlCeSyntaxProvider(); } } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 106b45ffed..d908f5c9b6 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -3,17 +3,10 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { - /// - /// Static class that provides simple access to the Sql CE SqlSyntax Provider - /// - internal static class SqlCeSyntax - { - public static ISqlSyntaxProvider Provider { get { return new SqlCeSyntaxProvider(); } } - } - /// /// Represents an SqlSyntaxProvider for Sql Ce /// @@ -67,6 +60,62 @@ namespace Umbraco.Core.Persistence.SqlSyntax return indexType; } + public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnEqualComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for = comparison with NText columns but allows this syntax + return string.Format("{0} LIKE '{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnStartsWithComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '{1}%'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnEndsWithComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '%{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '%{1}%'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + public override string GetQuotedTableName(string tableName) { return string.Format("[{0}]", tableName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntax.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntax.cs new file mode 100644 index 0000000000..a2f000e4b8 --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntax.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Static class that provides simple access to the Sql Server SqlSyntax Provider + /// + internal static class SqlServerSyntax + { + public static ISqlSyntaxProvider Provider { get { return new SqlServerSyntaxProvider(); } } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 3e37674ddf..24177c61d5 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -2,31 +2,10 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { - /// - /// Static class that provides simple access to the Sql Server SqlSyntax Provider - /// - internal static class SqlServerSyntax - { - public static ISqlSyntaxProvider Provider { get { return new SqlServerSyntaxProvider(); } } - } - - /// - /// Represents the version name of SQL server (i.e. the year 2008, 2005, etc...) - /// - internal enum SqlServerVersionName - { - Invalid = -1, - V7 = 0, - V2000 = 1, - V2005 = 2, - V2008 = 3, - V2012 = 4, - Other = 5 - } - /// /// Represents an SqlSyntaxProvider for Sql Server /// @@ -55,6 +34,62 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// internal Lazy VersionName { get; set; } + public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnEqualComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for = comparison with NText columns but allows this syntax + return string.Format("{0} LIKE '{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnStartsWithComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '{1}%'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnEndsWithComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '%{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '%{1}%'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + public override string GetQuotedTableName(string tableName) { return string.Format("[{0}]", tableName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs new file mode 100644 index 0000000000..37870a9536 --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Represents the version name of SQL server (i.e. the year 2008, 2005, etc...) + /// + internal enum SqlServerVersionName + { + Invalid = -1, + V7 = 0, + V2000 = 1, + V2005 = 2, + V2008 = 3, + V2012 = 4, + Other = 5 + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index e86222ac50..8505d1e553 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { @@ -102,6 +103,30 @@ namespace Umbraco.Core.Persistence.SqlSyntax DbTypeMap.Set(DbType.Binary, BlobColumnDefinition); } + public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) = '{1}'", column, value.ToUpper()); + } + + public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) like '{1}%'", column, value.ToUpper()); + } + + public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) like '%{1}'", column, value.ToUpper()); + } + + public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) like '%{1}%'", column, value.ToUpper()); + } + public virtual string GetQuotedTableName(string tableName) { return string.Format("\"{0}\"", tableName); diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index ca083a555a..44fce3eba2 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Services { @@ -25,38 +26,11 @@ namespace Umbraco.Core.Services //TODO: Need to get all members that start with a certain letter - //TODO: Need to get all members that start with a certain letter - void DeleteMembersOfType(int memberTypeId); - } - /// - /// Defines part of the MemberService, which is specific to methods used by the membership provider. - /// - /// - /// 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 - { - /// - /// Checks if a member with the username exists - /// - /// - /// - bool Exists(string username); - - IMember CreateMember(string username, string email, string password, string memberTypeAlias); - - IMember GetById(object id); - - IMember GetByEmail(string email); - - IMember GetByUsername(string login); - - void Delete(IMember membershipUser); - - void Save(IMember membershipUser, bool raiseEvents = true); - - IEnumerable FindMembersByEmail(string emailStringToMatch); + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact); + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value); + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value); + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs new file mode 100644 index 0000000000..18edae2bea --- /dev/null +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Services +{ + /// + /// Defines part of the MemberService, which is specific to methods used by the membership provider. + /// + /// + /// 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 + { + /// + /// Checks if a member with the username exists + /// + /// + /// + bool Exists(string username); + + /// + /// Creates and persists a new member + /// + /// + /// + /// + /// + /// + IMember CreateMember(string username, string email, string password, string memberTypeAlias); + + IMember GetById(object id); + + IMember GetByEmail(string email); + + IMember GetByUsername(string login); + + void Delete(IMember membershipUser); + + void Save(IMember membershipUser, bool raiseEvents = true); + + void Save(IEnumerable members, bool raiseEvents = true); + + IEnumerable FindMembersByEmail(string emailStringToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + + IEnumerable FindMembersByUsername(string login, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index f32b47dc94..d4ffbd2e06 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -760,30 +760,31 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(medias), this)) return; } + using (new WriteLock(Locker)) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMediaRepository(uow)) + { + foreach (var media in medias) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + } - var mediaXml = new Dictionary>(); - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateMediaRepository(uow)) - { - foreach (var media in medias) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - } + //commit the whole lot in one go + uow.Commit(); - //commit the whole lot in one go - uow.Commit(); + foreach (var media in medias) + { + CreateAndSaveMediaXml(media.ToXml(), media.Id, uow.Database); + } + } - foreach (var media in medias) - { - CreateAndSaveMediaXml(media.ToXml(), media.Id, uow.Database); - } - } + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(medias, false), this); - if(raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(medias, false), this); - - Audit.Add(AuditTypes.Save, "Save Media items performed by user", userId, -1); + Audit.Add(AuditTypes.Save, "Save Media items performed by user", userId, -1); + } } /// diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 35271c63d6..64bca9994e 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -185,16 +185,61 @@ namespace Umbraco.Core.Services /// Does a search for members that contain the specified string in their email address /// /// + /// /// - public IEnumerable FindMembersByEmail(string emailStringToMatch) + public IEnumerable FindMembersByEmail(string emailStringToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { var query = new Query(); + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Email.Equals(emailStringToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Email.Contains(emailStringToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Email.StartsWith(emailStringToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Email.EndsWith(emailStringToMatch)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } - query.Where(member => member.Email.Contains(emailStringToMatch)); + return repository.GetByQuery(query); + } + } + + public IEnumerable FindMembersByUsername(string login, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) + { + var query = new Query(); + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Username.Equals(login)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Username.Contains(login)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Username.StartsWith(login)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Username.EndsWith(login)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } return repository.GetByQuery(query); } @@ -205,17 +250,51 @@ namespace Umbraco.Core.Services /// /// /// + /// /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value) + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) { 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))); + IQuery query; + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.Contains: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.StartsWith: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.EndsWith: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } var members = repository.GetByQuery(query); return members; @@ -359,7 +438,7 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { - var escapedEmail = uow.Database.EscapeAtSymbols(email); + var query = Query.Builder.Where(x => x.Email.Equals(email)); var query = Query.Builder.Where(x => x.Email == escapedEmail); var member = repository.GetByQuery(query).FirstOrDefault(); @@ -377,8 +456,7 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { - var escapedUser = uow.Database.EscapeAtSymbols(userName); - var query = Query.Builder.Where(x => x.Username == escapedUser); + var query = Query.Builder.Where(x => x.Username == userName); var member = repository.GetByQuery(query).FirstOrDefault(); return member; @@ -431,6 +509,37 @@ namespace Umbraco.Core.Services Saved.RaiseEvent(new SaveEventArgs(member, false), this); } + public void Save(IEnumerable members, bool raiseEvents = true) + { + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(members), this)) + return; + } + using (new WriteLock(Locker)) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) + { + foreach (var member in members) + { + repository.AddOrUpdate(member); + } + + //commit the whole lot in one go + uow.Commit(); + + foreach (var member in members) + { + CreateAndSaveMemberXml(member.ToXml(), member.Id, uow.Database); + } + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(members, false), this); + } + } + #endregion /// diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index de36cca531..4847df3fc4 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -520,6 +520,11 @@ namespace Umbraco.Core return compare.StartsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); } + public static bool InvariantEndsWith(this string compare, string compareTo) + { + return compare.EndsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); + } + public static bool InvariantContains(this string compare, string compareTo) { return compare.IndexOf(compareTo, StringComparison.OrdinalIgnoreCase) >= 0; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3619c26d13..cb00bfca8a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -342,6 +342,13 @@ + + + + + + + @@ -974,6 +981,7 @@ + diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 7ddbe972ec..54fa355ce5 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -1,6 +1,9 @@ +using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Services @@ -46,5 +49,260 @@ namespace Umbraco.Tests.Services var xml = DatabaseContext.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = member.Id }); Assert.IsNotNull(xml); } + + [Test] + public void Exists_By_Username() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.IsTrue(ServiceContext.MemberService.Exists("test")); + Assert.IsFalse(ServiceContext.MemberService.Exists("notFound")); + } + + [Test] + public void Exists_By_Id() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.IsTrue(ServiceContext.MemberService.Exists(member.Id)); + Assert.IsFalse(ServiceContext.MemberService.Exists(9876)); + } + + [Test] + public void Get_By_Email() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.IsNotNull(ServiceContext.MemberService.GetByEmail(member.Email)); + Assert.IsNull(ServiceContext.MemberService.GetByEmail("do@not.find")); + } + + [Test] + public void Get_By_Username() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.IsNotNull(ServiceContext.MemberService.GetByUsername(member.Username)); + Assert.IsNull(ServiceContext.MemberService.GetByUsername("notFound")); + } + + [Test] + public void Get_By_Object_Id() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.IsNotNull(ServiceContext.MemberService.GetById((object)member.Id)); + Assert.IsNull(ServiceContext.MemberService.GetById((object)9876)); + } + + [Test] + public void Find_By_Email_Starts_With() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + //don't find this + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello","hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.FindMembersByEmail("tes", StringPropertyMatchType.StartsWith); + + Assert.AreEqual(10, found.Count()); + } + + [Test] + public void Find_By_Email_Ends_With() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + //include this + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.FindMembersByEmail("test.com", StringPropertyMatchType.EndsWith); + + Assert.AreEqual(11, found.Count()); + } + + [Test] + public void Find_By_Email_Contains() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + //include this + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.FindMembersByEmail("test", StringPropertyMatchType.Contains); + + Assert.AreEqual(11, found.Count()); + } + + [Test] + public void Find_By_Email_Exact() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + //include this + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.FindMembersByEmail("hello@test.com", StringPropertyMatchType.Exact); + + Assert.AreEqual(1, found.Count()); + } + + [Test] + public void Find_By_Login_Starts_With() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + //don't find this + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.FindMembersByUsername("tes", StringPropertyMatchType.StartsWith); + + Assert.AreEqual(10, found.Count()); + } + + [Test] + public void Find_By_Login_Ends_With() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + //include this + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.FindMembersByUsername("llo", StringPropertyMatchType.EndsWith); + + Assert.AreEqual(1, found.Count()); + } + + [Test] + public void Find_By_Login_Contains() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + //include this + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hellotest"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.FindMembersByUsername("test", StringPropertyMatchType.Contains); + + Assert.AreEqual(11, found.Count()); + } + + [Test] + public void Find_By_Login_Exact() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + //include this + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.FindMembersByUsername("hello", StringPropertyMatchType.Exact); + + Assert.AreEqual(1, found.Count()); + } + + [Test] + public void Get_By_Property_Value_Exact() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "title", "hello member", StringPropertyMatchType.Exact); + + Assert.AreEqual(1, found.Count()); + } + + [Test] + public void Get_By_Property_Value_Contains() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "title", " member", StringPropertyMatchType.Contains); + + Assert.AreEqual(11, found.Count()); + } + + [Test] + public void Get_By_Property_Value_Starts_With() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "title", "Member No", StringPropertyMatchType.StartsWith); + + Assert.AreEqual(10, found.Count()); + } + + [Test] + public void Get_By_Property_Value_Ends_With() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("title", "title of mine"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "title", "mine", StringPropertyMatchType.EndsWith); + + Assert.AreEqual(1, found.Count()); + } + + } } \ No newline at end of file