diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index df34b2ca4f..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 { @@ -90,6 +94,210 @@ 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"; + + 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"; + + 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 + } + } + }; + } + /// /// Defines the alias identifiers for Umbraco member types. /// 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/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/IMember.cs b/src/Umbraco.Core/Models/IMember.cs new file mode 100644 index 0000000000..0b347a2515 --- /dev/null +++ b/src/Umbraco.Core/Models/IMember.cs @@ -0,0 +1,114 @@ +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/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..b3baac8351 --- /dev/null +++ b/src/Umbraco.Core/Models/Member.cs @@ -0,0 +1,436 @@ +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; + } + + /// + /// 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 + /// + [IgnoreDataMember] + public IMemberType ContentType + { + get { return _contentType; } + } + + public override void ChangeTrashedState(bool isTrashed, int parentId = -20) + { + 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. + * 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..ed7d5cd3ab --- /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, PropertyTypeId) 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/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 deleted file mode 100644 index 3fa26af085..0000000000 --- a/src/Umbraco.Core/Models/Membership/Member.cs +++ /dev/null @@ -1,159 +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, IMembershipUser - { - private bool _hasIdentity; - private int _id; - private Guid _key; - private DateTime _createDate; - private DateTime _updateDate; - - 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); - - /// - /// Integer Id - /// - [DataMember] - public new 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 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 DateTime CreateDate - { - get { return _createDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _createDate = value; - return _createDate; - }, _createDate, CreateDateSelector); - } - } - - /// - /// Gets or sets the Modified Date - /// - [DataMember] - public DateTime UpdateDate - { - get { return _updateDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _updateDate = value; - return _updateDate; - }, _updateDate, UpdateDateSelector); - } - } - - /// - /// Indicates whether the current entity has an identity, eg. Id. - /// - public virtual bool HasIdentity - { - get - { - return _hasIdentity; - } - protected set - { - SetPropertyValueAndDetectChanges(o => - { - _hasIdentity = value; - return _hasIdentity; - }, _hasIdentity, HasIdentitySelector); - } - } - - public string Username { get; set; } - public string Email { get; set; } - - 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; } - - public object ProfileId { get; set; } - public IEnumerable Groups { get; set; } - - #region Internal methods - - 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 aef6af3fd5..0000000000 --- a/src/Umbraco.Core/Models/Membership/MemberProfile.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Models.Membership -{ - internal class MemberProfile : Profile - { - - } -} \ 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 d6baa03e68..0000000000 --- a/src/Umbraco.Core/Models/Membership/MemberType.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models.Membership -{ - /// - /// Represents the Type for an Umbraco Member - /// - /// - /// Should be internal until a proper user/membership implementation - /// is part of the roadmap. - /// - [Serializable] - [DataContract(IsReference = true)] - internal class MemberType : Entity - { - - } -} \ No newline at end of file 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 new file mode 100644 index 0000000000..06e50ecd9b --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs @@ -0,0 +1,25 @@ +using System.Web.Security; + +namespace Umbraco.Core.Models.Membership +{ + internal class UmbracoMembershipMember : MembershipUser + { + private readonly IMember _member; + + public UmbracoMembershipMember(IMember member) + { + _member = member; + } + + internal IMember Member + { + get { return _member; } + } + + public override string Email + { + get { return _member.Email; } + set { _member.Email = value; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 4a0e09518a..52dc022a50 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -4,7 +4,6 @@ using System.Diagnostics; 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/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/Models/Rdbms/MemberReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs index 260c853359..f2ff7c1fd7 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")] @@ -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/MemberTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs new file mode 100644 index 0000000000..3694588f56 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +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 + [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/PropertyDataReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs index 31f65e14aa..6e700db210 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs @@ -3,37 +3,15 @@ using Umbraco.Core.Persistence; namespace Umbraco.Core.Models.Rdbms { - [TableName("cmsPropertyData")] + [TableName("cmsPropertyType")] [PrimaryKey("id")] [ExplicitColumns] - public class PropertyDataReadOnlyDto + internal class PropertyDataReadOnlyDto { - /* cmsPropertyData */ + /* cmsPropertyType */ [Column("id")] public int Id { get; set; } - [Column("contentNodeId")] - public int NodeId { 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; } @@ -58,8 +36,65 @@ 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; } + + [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 + { + 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 string.Empty; + } + } } } \ 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/MemberFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs new file mode 100644 index 0000000000..1aa8f70bb8 --- /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; +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/MemberReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs new file mode 100644 index 0000000000..eb6e2c787d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +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 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, + UpdateDate = dto.UpdateDate, + Name = dto.Text, + Email = dto.Email, + Username = dto.LoginName, + Password = dto.Password, + ProviderUserKey = dto.UniqueId, + Trashed = dto.Trashed, + Key = dto.UniqueId.Value, + CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0, + Level = dto.Level, + Path = dto.Path, + SortOrder = dto.SortOrder, + Version = dto.VersionId, + ContentTypeAlias = dto.ContentTypeAlias + }; + + member.SetProviderUserKeyType(typeof(Guid)); + member.ResetDirtyProperties(false); + return member; + } + + public MemberReadOnlyDto BuildDto(IMember entity) + { + throw new System.NotImplementedException(); + } + + public IEnumerable CreateProperties(IMemberType memberType, IEnumerable dtos, DateTime createDate) + { + var properties = new List(); + + foreach (var propertyType in memberType.CompositionPropertyTypes) + { + 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; + } + } +} \ 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..f83cada0f9 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Models; +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.PropertyTypes.Any() == false) + return Enumerable.Empty(); + + 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) + { + 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; + } + + 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 new file mode 100644 index 0000000000..e9e7fe19c4 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class MemberTypeReadOnlyFactory : IEntityFactory + { + public IMemberType BuildEntity(MemberTypeReadOnlyDto dto) + { + 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/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/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/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/MemberMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs new file mode 100644 index 0000000000..d895e1c127 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs @@ -0,0 +1,74 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models; +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.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); + + /* 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 + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs new file mode 100644 index 0000000000..eafc1e5031 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs @@ -0,0 +1,57 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +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/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index d01eed30f9..44570adcd4 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 3042510b05..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("upper({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.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/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 6e07cfecd7..a4351ef99f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -172,31 +172,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(); - } - } - + 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 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 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/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/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index 2168e3382d..db9a1b4f19 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; namespace Umbraco.Core.Persistence.Repositories { - internal interface IMemberRepository : IRepositoryVersionable + 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..41194c29e3 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IMemberTypeRepository : IRepositoryQueryable + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index a2035023b4..10f5d02865 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 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 contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); } #endregion @@ -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); diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index fe322e8c31..cc2cdcd583 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -1,60 +1,85 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +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 { - internal class MemberRepository : VersionableRepositoryBase, IMemberRepository + /// + /// Represents a repository for doing CRUD operations for + /// + 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; } - /* 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) - * 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 - protected override IMembershipUser PerformGet(int id) + protected override IMember PerformGet(int id) { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); sql.OrderByDescending(x => x.VersionDate); - var dto = + var dtos = Database.Fetch( new PropertyDataRelator().Map, sql); - if (dto == null || dto.Any() == false) - return null; - - return new Member(); + return BuildFromDto(dtos); } - protected override IEnumerable PerformGetAll(params int[] ids) + 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) + 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)) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + var dtos = + Database.Fetch( + new PropertyDataRelator().Map, sql); + + return BuildFromDtos(dtos); } #endregion @@ -63,24 +88,37 @@ namespace Umbraco.Core.Persistence.Repositories protected override Sql GetBaseQuery(bool isCount) { - //TODO Count var sql = new Sql(); - sql.Select("umbracoNode.*", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", + + if (isCount) + { + 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 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") + "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; } @@ -90,9 +128,50 @@ namespace Umbraco.Core.Persistence.Repositories return "umbracoNode.id = @Id"; } + protected Sql GetSubquery() + { + var sql = new Sql(); + sql.Select("DISTINCT(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 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() { - 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 @@ -104,30 +183,261 @@ 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(); + ((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(IMembershipUser entity) + 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 IMembershipUser GetByVersion(Guid versionId) + 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) + return null; + var dto = dtos.First(); + + var memberTypes = new Dictionary + { + { + dto.ContentTypeAlias, + _memberTypeRepository.Get(dto.ContentTypeId) + } + }; + + var factory = new MemberReadOnlyFactory(memberTypes); + var member = factory.BuildEntity(dto); + + return member; + } + + private IEnumerable BuildFromDtos(List dtos) + { + if (dtos == null || dtos.Any() == false) + return Enumerable.Empty(); + + //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); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 0dadf2acd4..a8c167ba02 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -1,7 +1,239 @@ -namespace Umbraco.Core.Persistence.Repositories +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 { - 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) + { + 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) + { + 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) + { + 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 + + #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.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.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) + .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(); + + //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); + + 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 + + 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/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 1bbb960a7c..64e0e833f0 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", new { VersionId = versionId }); - if(dto == null) return; + 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 ContentId = @Id AND VersionDate < @VersionDate", new { Id = id, VersionDate = versionDate }); - if (list.Any() == false) return; + 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.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index bbf9237a5d..c46c64a2ff 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -117,7 +117,12 @@ 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) + { + return new MemberTypeRepository(uow, InMemoryCacheProvider.Current); } internal virtual IEntityRepository CreateEntityRepository(IDatabaseUnitOfWork uow) diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 9c0f9a14cf..a6a2a14424 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -1,11 +1,19 @@ -namespace Umbraco.Core.Services +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services { /// /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. /// internal interface IMemberService : IMembershipMemberService { - + IMember GetById(int id); + IMember GetByKey(Guid id); + IEnumerable GetMembersByMemberType(string memberTypeAlias); + IEnumerable GetMembersByGroup(string memberGroupName); + IEnumerable GetAllMembers(params int[] ids); } /// @@ -15,5 +23,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 - {} + { + IMember CreateMember(string username, string email, string password, string memberTypeAlias, int userId = 0); + + IMember GetById(object id); + + IMember GetByEmail(string email); + + IMember GetByUsername(string login); + + 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 a977069045..237e7286c0 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -1,5 +1,11 @@ -using Umbraco.Core.Persistence; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; +using System.Linq; namespace Umbraco.Core.Services { @@ -26,5 +32,292 @@ namespace Umbraco.Core.Services _repositoryFactory = repositoryFactory; _uowProvider = provider; } + + #region IMemberService Implementation + + /// + /// Gets a Member by its integer Id + /// + /// + /// + public IMember GetById(int id) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } + + /// + /// 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())) + { + var query = Query.Builder.Where(x => x.Key == id); + var member = repository.GetByQuery(query).FirstOrDefault(); + return member; + } + } + + /// + /// 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 + + /// + /// Creates a new Member + /// + /// + /// + /// + /// + /// + /// + public IMember CreateMember(string username, string email, string password, string memberTypeAlias, int userId = 0) + { + var uow = _uowProvider.GetUnitOfWork(); + IMemberType memberType; + + 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)) + { + member.Username = username; + member.Email = email; + member.Password = password; + + repository.AddOrUpdate(member); + uow.Commit(); + } + + 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) + { + return GetById((int)id); + } + + if (id is Guid) + { + return GetByKey((Guid)id); + } + + return null; + } + + /// + /// Gets a Member by its Email + /// + /// + /// + 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; + } + } + + /// + /// Gets a Member by its Username + /// + /// + /// + 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; + } + } + + /// + /// Deletes a Member + /// + /// + public void Delete(IMember member) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) + { + repository.Delete(member); + uow.Commit(); + } + } + + /// + /// Saves an updated Member + /// + /// + public void Save(IMember member) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) + { + repository.AddOrUpdate(member); + uow.Commit(); + } + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index fb7a3252cd..29e5ec75fe 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 9034e2d36d..89bb6955bd 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -82,7 +82,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)); diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index b0dc53c22f..b08ca1985d 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]; + } + /// /// This tries to detect a json string, this is not a fail safe way but it is quicker than doing /// a try/catch when deserializing when it is not json. @@ -1150,5 +1161,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.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7b5de2e441..a0f2a94da5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -282,11 +282,20 @@ + + + + + + + + + @@ -297,10 +306,6 @@ - - - - @@ -334,6 +339,10 @@ + + + + @@ -350,6 +359,8 @@ + + @@ -560,6 +571,7 @@ + @@ -577,6 +589,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index afea9fab5c..301b844be4 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() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 95dedd134b..5f10bb6500 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,17 +275,21 @@ namespace Umbraco.Tests.Persistence.Repositories RepositoryResolver.Current = new RepositoryResolver( new RepositoryFactory()); - + + MappingResolver.Current = new MappingResolver( + () => PluginManager.Current.ResolveAssignedMapperTypes()); //disable cache var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); - var dbFactory = new DefaultDatabaseFactory(); + + var dbFactory = new DefaultDatabaseFactory(ConnectionString, ProviderName); + UnitOfWorkProvider = new PetaPocoUnitOfWorkProvider(dbFactory); ApplicationContext.Current = new ApplicationContext( //assign the db context new DatabaseContext(dbFactory), //assign the service context - new ServiceContext(new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper), + new ServiceContext(UnitOfWorkProvider, new FileUnitOfWorkProvider(), new PublishingStrategy()), //disable cache cacheHelper) { @@ -89,8 +309,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 diff --git a/src/Umbraco.Tests/PluginManagerTests.cs b/src/Umbraco.Tests/PluginManagerTests.cs index 1703c4be75..75c075c104 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] 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..47f2f817f1 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs @@ -0,0 +1,20 @@ +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 Resolve(Property(), DefaultString); } } + + public string BodyText { get { return Resolve(Property(), DefaultString); } } + + 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 new file mode 100644 index 0000000000..4cc6e2ebf2 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs @@ -0,0 +1,70 @@ +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 readonly IPublishedContent _publishedContent; + + protected TypedModelBase(IPublishedContent publishedContent) + { + _publishedContent = publishedContent; + } + + 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) + { + var propertyTypeAlias = methodBase.ToUmbracoAlias(); + 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); + } + + 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..6e27886b83 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs @@ -0,0 +1,29 @@ +using Umbraco.Core.Models; +using Umbraco.Web.Mvc; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + public abstract class UmbracoTemplatePage : UmbracoTemplatePage where T : TypedModelBase + { + 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 + var constructorInfo = typeof(T).GetConstructor(new []{typeof(IPublishedContent)}); + if (constructorInfo != null) + { + TypedModel = constructorInfo.Invoke(new object[]{Model.Content}) as T; + } + } + } + + /// + /// Returns the a strongly typed model + /// + public T TypedModel { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index a8d3f6d30c..089fc25ae2 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -861,22 +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)); - } - + [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"); 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/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index b4e7f55a89..47dc795a47 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()); } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 60398719b2..b1840c6915 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -251,6 +251,11 @@ + + + + + diff --git a/src/Umbraco.Web.UI/config/Dashboard.Release.config b/src/Umbraco.Web.UI/config/Dashboard.Release.config index c88db468ca..e43e8fc938 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.Release.config +++ b/src/Umbraco.Web.UI/config/Dashboard.Release.config @@ -36,6 +36,9 @@ + + admin + /umbraco/dashboard/mediadashboardintro.ascx @@ -56,6 +59,9 @@ content + + admin + views/dashboard/startupdashboardintro.html @@ -105,4 +111,4 @@ /umbraco/dashboard/ExamineManagement.ascx - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config index 6b88e00379..f05c78c4ae 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 + views/dashboard/startupdashboardintro.html 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 -
- -
internal class UmbracoForm : MvcForm { - /// - /// Creates an UmbracoForm - /// - /// - /// - /// - /// - /// - public UmbracoForm( + /// + /// Creates an UmbracoForm + /// + /// + /// + /// + /// + /// + /// + public UmbracoForm( ViewContext viewContext, - string surfaceController, - string surfaceAction, + string controllerName, + string controllerAction, 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 - 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()) - surfaceRouteParams += "&" + additionalRouteValsAsQuery; - - if (!string.IsNullOrWhiteSpace(surfaceRouteParams)) - { - _encryptedString = surfaceRouteParams.EncryptWithMachineKey(); - } - - _textWriter = viewContext.Writer; + _viewContext = viewContext; + _method = method; + _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); } - + 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 +222,25 @@ namespace Umbraco.Web return; this._disposed = true; - //write out the hidden surface form routes - _textWriter.Write(""); + //write out the hidden surface form routes + _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 +254,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 +281,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 +315,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 +355,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 +380,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 +407,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 +436,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 +465,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 +499,26 @@ 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 +535,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 +580,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 +620,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 +647,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 +683,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 +713,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 +724,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 +733,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..1ebfebf1e4 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -109,23 +109,49 @@ 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.QueryString["ufprt"].IsNullOrWhiteSpace() + && requestContext.HttpContext.Request.Form["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.Form["ufprt"]; + break; + case "GET": + //this field will contain an encrypted version of the surface route vals. + encodedVal = requestContext.HttpContext.Request.QueryString["ufprt"]; + break; + default: + return null; + } - var encodedVal = requestContext.HttpContext.Request["uformpostroutevals"]; - var decryptedString = encodedVal.DecryptWithMachineKey(); - var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); + + 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(); foreach (var key in parsedQueryString.AllKeys) @@ -136,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; @@ -169,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), }; } @@ -336,7 +350,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); diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPage.cs b/src/Umbraco.Web/Mvc/UmbracoViewPage.cs index 25641d3540..25897dfcc2 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/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"); + } } /// 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/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 8518b52362..0f7d255f74 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -1,14 +1,40 @@ -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.Hosting; +using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Services; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Security.Providers { /// - /// Custom Membership Provider for Umbraco Members (User authentication for Umbraco based Websites) + /// 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 + private IMembershipMemberService _memberService; protected IMembershipMemberService MemberService @@ -16,138 +42,791 @@ namespace Umbraco.Web.Security.Providers get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } } + /// + /// 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"; } + } + + /// + /// 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"]) ? GetDefaultAppName() : config["applicationName"]; + + _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. + 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.AsIMember(); + 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 + #region Private methods + + 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) + { + 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; } - public override bool RequiresQuestionAndAnswer + /// + /// 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) { - get { throw new System.NotImplementedException(); } + 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; } - public override string ApplicationName { get; set; } - - public override int MaxInvalidPasswordAttempts + /// + /// 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) { - get { throw new System.NotImplementedException(); } + 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; } - public override int PasswordAttemptWindow + + /// + /// Gets the name of the default app. + /// + /// + private string GetDefaultAppName() { - get { throw new System.NotImplementedException(); } + try + { + var applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath; + return string.IsNullOrEmpty(applicationVirtualPath) ? "/" : applicationVirtualPath; + } + catch + { + return "/"; + } } - 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(); } - } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b8e2969da4..a6ffcefb0b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -542,6 +542,7 @@ + @@ -1922,6 +1923,7 @@ Component + diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 9d84e497dd..c0b59819c4 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 { @@ -1304,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 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)); + } + } +} 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 86446782fe..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,15 +272,15 @@ namespace umbraco.dialogs { if (CurrentApp == Constants.Applications.Content) { - //Backwards comp. change, so old events are fired #U4-2731 - var doc = new Document(currContent as IContent); - doc.Move(Request.GetItemAs("copyTo")); + //Backwards comp. change, so old events are fired #U4-2731 + var doc = new Document(currContent as IContent); + doc.Move(Request.GetItemAs("copyTo")); } else { - //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")); + //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); } @@ -294,9 +294,9 @@ namespace umbraco.dialogs { //NOTE: We ONLY support Copy on content not media for some reason. - //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); + //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; 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 { 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; diff --git a/src/umbraco.cms/businesslogic/macro/Macro.cs b/src/umbraco.cms/businesslogic/macro/Macro.cs index 5b4fa9d2aa..d2f79dcecd 100644 --- a/src/umbraco.cms/businesslogic/macro/Macro.cs +++ b/src/umbraco.cms/businesslogic/macro/Macro.cs @@ -541,13 +541,13 @@ namespace umbraco.cms.businesslogic.macro //we need to check if the file path saved is a virtual path starting with ~/Views/MacroPartials, if so then this is //a partial view macro, not a script macro //we also check if the file exists in ~/App_Plugins/[Packagename]/Views/MacroPartials, if so then it is also a partial view. - return (scriptFile.StartsWith(SystemDirectories.MvcViews + "/MacroPartials/") - || (Regex.IsMatch(scriptFile, "~/App_Plugins/.+?/Views/MacroPartials", RegexOptions.Compiled))) + return (scriptFile.InvariantStartsWith(SystemDirectories.MvcViews + "/MacroPartials/") + || (Regex.IsMatch(scriptFile, "~/App_Plugins/.+?/Views/MacroPartials", RegexOptions.Compiled | RegexOptions.IgnoreCase))) ? MacroTypes.PartialView : MacroTypes.Script; } - if (!string.IsNullOrEmpty(scriptType) && scriptType.ToLowerInvariant().IndexOf(".ascx", StringComparison.InvariantCultureIgnoreCase) > -1) + if (!string.IsNullOrEmpty(scriptType) && scriptType.InvariantContains(".ascx")) return MacroTypes.UserControl; if (!string.IsNullOrEmpty(scriptType) && !string.IsNullOrEmpty(scriptAssembly)) diff --git a/src/umbraco.editorControls/tinymce/tinyMCEConfiguration.cs b/src/umbraco.editorControls/tinymce/tinyMCEConfiguration.cs index fcfcbedcd1..1ff6e3642d 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); } } diff --git a/src/umbraco.providers/SecUtility.cs b/src/umbraco.providers/SecUtility.cs index 72af5f45e0..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,6 +10,7 @@ namespace umbraco.providers /// /// Security Helper Methods /// + [ObsoleteAttribute("This clas is obsolete and wil be removed along with the legacy Membership Provider.", false)] internal class SecUtility { ///