diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 88c498a147..81b5272a58 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -54,6 +54,11 @@ namespace Umbraco.Core.Models { public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> TemplateComparer = new DelegateEqualityComparer>( + (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), + templates => templates.GetHashCode()); } /// @@ -91,10 +96,7 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), - templates => templates.GetHashCode())); + Ps.Value.TemplateComparer); } } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 88476f946d..db0bc0e900 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -88,6 +88,12 @@ namespace Umbraco.Core.Models public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> ContentTypeSortComparer = + new DelegateEqualityComparer>( + (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), + sorts => sorts.GetHashCode()); } @@ -254,7 +260,7 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } } - private IDictionary _additionalData; + private readonly IDictionary _additionalData; /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// @@ -273,11 +279,8 @@ namespace Umbraco.Core.Models get { return _allowedContentTypes; } set { - SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), - sorts => sorts.GetHashCode())); + SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, + Ps.Value.ContentTypeSortComparer); } } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 42b047e35b..d5fcc89994 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -37,6 +37,12 @@ namespace Umbraco.Core.Models public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> DictionaryTranslationComparer = + new DelegateEqualityComparer>( + (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), + enumerable => enumerable.GetHashCode()); } /// @@ -79,10 +85,7 @@ namespace Umbraco.Core.Models } SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), - enumerable => enumerable.GetHashCode())); + Ps.Value.DictionaryTranslationComparer); } } } diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 03631bda05..52f56fc2d5 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -14,8 +14,8 @@ namespace Umbraco.Core.Models.Identity public BackOfficeIdentityUser() { - StartMediaId = -1; - StartContentId = -1; + StartMediaIds = new int[] { }; + StartContentIds = new int[] { }; Culture = Configuration.GlobalSettings.DefaultUILanguage; } @@ -31,8 +31,8 @@ namespace Umbraco.Core.Models.Identity /// Gets/sets the user's real name /// public string Name { get; set; } - public int StartContentId { get; set; } - public int StartMediaId { get; set; } + public int[] StartContentIds { get; set; } + public int[] StartMediaIds { get; set; } public string[] AllowedSections { get; set; } public string[] Groups { get; set; } public string Culture { get; set; } diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index 8295ae566d..9d01056f48 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -21,8 +21,8 @@ namespace Umbraco.Core.Models.Identity .ForMember(user => user.PasswordHash, expression => expression.MapFrom(user => GetPasswordHash(user.RawPasswordValue))) .ForMember(user => user.Culture, expression => expression.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember(user => user.Name, expression => expression.MapFrom(user => user.Name)) - .ForMember(user => user.StartMediaId, expression => expression.MapFrom(user => user.StartMediaId)) - .ForMember(user => user.StartContentId, expression => expression.MapFrom(user => user.StartContentId)) + .ForMember(user => user.StartMediaIds, expression => expression.MapFrom(user => user.StartMediaIds)) + .ForMember(user => user.StartContentIds, expression => expression.MapFrom(user => user.StartContentIds)) .ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts)) .ForMember(user => user.Groups, expression => expression.MapFrom(user => user.Groups.ToArray())) .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray())); @@ -33,8 +33,8 @@ namespace Umbraco.Core.Models.Identity .ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections)) .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Groups)) .ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name)) - .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaNodes, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.UserName)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture)) .ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp)); diff --git a/src/Umbraco.Core/Models/Membership/IProfile.cs b/src/Umbraco.Core/Models/Membership/IProfile.cs index 749c3371b8..0e6267a10b 100644 --- a/src/Umbraco.Core/Models/Membership/IProfile.cs +++ b/src/Umbraco.Core/Models/Membership/IProfile.cs @@ -1,15 +1,11 @@ namespace Umbraco.Core.Models.Membership { /// - /// Defines the the Profile interface - /// - /// - /// This interface is pretty useless but has been exposed publicly from 6.x so we're stuck with it. It would make more sense - /// if the Id was an int but since it's not people have to cast it to int all of the time! - /// + /// Defines the the User Profile interface + /// public interface IProfile { - object Id { get; set; } - string Name { get; set; } + int Id { get; } + string Name { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index edb9d9aa7d..0b17fea05b 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -13,8 +13,8 @@ namespace Umbraco.Core.Models.Membership string Name { get; set; } int SessionTimeout { get; set; } - int StartContentId { get; set; } - int StartMediaId { get; set; } + int[] StartContentIds { get; set; } + int[] StartMediaIds { get; set; } string Language { get; set; } /// diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 055b3e439d..f06c373360 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -27,8 +27,8 @@ namespace Umbraco.Core.Models.Membership _language = GlobalSettings.DefaultUILanguage; _isApproved = true; _isLockedOut = false; - _startContentId = -1; - _startMediaId = -1; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; //cannot be null _rawPasswordValue = ""; _allowedSections = new List(); @@ -57,8 +57,8 @@ namespace Umbraco.Core.Models.Membership _userGroups = new List(); _isApproved = true; _isLockedOut = false; - _startContentId = -1; - _startMediaId = -1; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; } /// @@ -88,15 +88,15 @@ namespace Umbraco.Core.Models.Membership _userGroups = new List(userGroups); _isApproved = true; _isLockedOut = false; - _startContentId = -1; - _startMediaId = -1; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; } private string _name; private string _securityStamp; private int _sessionTimeout; - private int _startContentId; - private int _startMediaId; + private int[] _startContentIds; + private int[] _startMediaIds; private int _failedLoginAttempts; private string _username; @@ -124,8 +124,8 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); - public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); - public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); + public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentIds); + public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaIds); public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); @@ -138,6 +138,12 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); public readonly PropertyInfo UserGroupsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Groups); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> IntegerEnumerableComparer = + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); } #region Implementation of IMembershipUser @@ -252,7 +258,7 @@ namespace Umbraco.Core.Models.Membership public IProfile ProfileData { - get { return new UserProfile(this); } + get { return new WrappedUserProfile(this); } } /// @@ -285,10 +291,11 @@ namespace Umbraco.Core.Models.Membership /// The start content id. /// [DataMember] - public int StartContentId + [DoNotClone] + public int[] StartContentIds { - get { return _startContentId; } - set { SetPropertyValueAndDetectChanges(value, ref _startContentId, Ps.Value.StartContentIdSelector); } + get { return _startContentIds; } + set { SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdSelector, Ps.Value.IntegerEnumerableComparer); } } /// @@ -297,11 +304,12 @@ namespace Umbraco.Core.Models.Membership /// /// The start media id. /// - [DataMember] - public int StartMediaId + [DataMember] + [DoNotClone] + public int[] StartMediaIds { - get { return _startMediaId; } - set { SetPropertyValueAndDetectChanges(value, ref _startMediaId, Ps.Value.StartMediaIdSelector); } + get { return _startMediaIds; } + set { SetPropertyValueAndDetectChanges(value, ref _startMediaIds, Ps.Value.StartMediaIdSelector, Ps.Value.IntegerEnumerableComparer); } } [DataMember] @@ -349,7 +357,10 @@ namespace Umbraco.Core.Models.Membership public override object DeepClone() { - var clone = (User)base.DeepClone(); + var clone = (User)base.DeepClone(); + //manually clone the start node props + clone._startContentIds = _startContentIds.ToArray(); + clone._startMediaIds = _startMediaIds.ToArray(); //turn off change tracking clone.DisableChangeTracking(); //need to create new collections otherwise they'll get copied by ref @@ -367,28 +378,26 @@ namespace Umbraco.Core.Models.Membership /// /// Internal class used to wrap the user in a profile /// - private class UserProfile : IProfile + private class WrappedUserProfile : IProfile { private readonly IUser _user; - public UserProfile(IUser user) + public WrappedUserProfile(IUser user) { _user = user; } - public object Id + public int Id { get { return _user.Id; } - set { _user.Id = (int)value; } } public string Name { get { return _user.Name; } - set { _user.Name = value; } } - protected bool Equals(UserProfile other) + private bool Equals(WrappedUserProfile other) { return _user.Equals(other._user); } @@ -398,7 +407,7 @@ namespace Umbraco.Core.Models.Membership if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((UserProfile) obj); + return Equals((WrappedUserProfile) obj); } public override int GetHashCode() diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index b008f4652f..d6674b579d 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -32,6 +32,12 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> StringEnumerableComparer = + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); } public UserGroup() @@ -93,10 +99,7 @@ namespace Umbraco.Core.Models.Membership set { SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode())); + Ps.Value.StringEnumerableComparer); } } diff --git a/src/Umbraco.Core/Models/Membership/UserProfile.cs b/src/Umbraco.Core/Models/Membership/UserProfile.cs new file mode 100644 index 0000000000..d32854ab1b --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/UserProfile.cs @@ -0,0 +1,46 @@ +using System; + +namespace Umbraco.Core.Models.Membership +{ + internal class UserProfile : IProfile, IEquatable + { + public UserProfile(int id, string name) + { + Id = id; + Name = name; + } + + public int Id { get; private set; } + public string Name { get; private set; } + + public bool Equals(UserProfile other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UserProfile) obj); + } + + public override int GetHashCode() + { + return Id; + } + + public static bool operator ==(UserProfile left, UserProfile right) + { + return Equals(left, right); + } + + public static bool operator !=(UserProfile left, UserProfile right) + { + return Equals(left, right) == false; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index dff6a712c7..5df2eaae90 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -49,36 +49,36 @@ namespace Umbraco.Core.Models { public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); - } - private static readonly DelegateEqualityComparer ValueComparer = new DelegateEqualityComparer( - (o, o1) => - { - if (o == null && o1 == null) return true; - - //custom comparer for strings. - if (o is string || o1 is string) + public readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( + (o, o1) => { - //if one is null and another is empty then they are the same - if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) + if (o == null && o1 == null) return true; + + //custom comparer for strings. + if (o is string || o1 is string) { - return true; + //if one is null and another is empty then they are the same + if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) + { + return true; + } + if (o == null || o1 == null) return false; + return o.Equals(o1); } + if (o == null || o1 == null) return false; + + //Custom comparer for enumerable if it is enumerable + var enum1 = o as IEnumerable; + var enum2 = o1 as IEnumerable; + if (enum1 != null && enum2 != null) + { + return enum1.Cast().UnsortedSequenceEqual(enum2.Cast()); + } return o.Equals(o1); - } - - if (o == null || o1 == null) return false; - - //Custom comparer for enumerable if it is enumerable - var enum1 = o as IEnumerable; - var enum2 = o1 as IEnumerable; - if (enum1 != null && enum2 != null) - { - return enum1.Cast().UnsortedSequenceEqual(enum2.Cast()); - } - return o.Equals(o1); - }, o => o.GetHashCode()); + }, o => o.GetHashCode()); + } /// /// Returns the instance of the tag support, by default tags are not enabled @@ -200,7 +200,7 @@ namespace Umbraco.Core.Models } } - SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector, ValueComparer); + SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector, Ps.Value.PropertyValueComparer); } } diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Models/Rdbms/UserDto.cs index 5d2257d01b..0f74a92b7b 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserDto.cs @@ -14,6 +14,7 @@ namespace Umbraco.Core.Models.Rdbms public UserDto() { UserGroupDtos = new List(); + UserStartNodeDtos = new List(); } [Column("id")] @@ -27,14 +28,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("userNoConsole")] [Constraint(Default = "0")] public bool NoConsole { get; set; } - - [Column("startStructureID")] - public int ContentStartId { get; set; } - - [Column("startMediaID")] - [NullSetting(NullSetting = NullSettings.Null)] - public int? MediaStartId { get; set; } - + [Column("userName")] public string UserName { get; set; } @@ -88,5 +82,8 @@ namespace Umbraco.Core.Models.Rdbms [ResultColumn] public List UserGroupDtos { get; set; } + + [ResultColumn] + public List UserStartNodeDtos { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs b/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs new file mode 100644 index 0000000000..e872111829 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs @@ -0,0 +1,36 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoUserStartNode")] + [PrimaryKey("id", autoIncrement = true)] + [ExplicitColumns] + internal class UserStartNodeDto + { + [Column("id")] + [PrimaryKeyColumn(Name = "PK_userStartNode")] + public int Id { get; set; } + + [Column("userId")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [ForeignKey(typeof(UserDto))] + public int UserId { get; set; } + + [Column("startNode")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [ForeignKey(typeof(NodeDto))] + public int StartNode { get; set; } + + [Column("startNodeType")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.UniqueNonClustered, ForColumns = "startNodeType, startNode", Name = "IX_umbracoUserStartNode_startNodeType")] + public int StartNodeType { get; set; } + + public enum StartNodeTypeValue + { + Content = 1, + Media = 2 + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 87b97df602..4894be7b87 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -49,24 +49,34 @@ namespace Umbraco.Core.Models { if (user == null) throw new ArgumentNullException("user"); if (content == null) throw new ArgumentNullException("content"); - return HasPathAccess(content.Path, user.StartContentId, Constants.System.RecycleBinContent); + return HasPathAccess(content.Path, user.StartContentIds, Constants.System.RecycleBinContent); } - internal static bool HasPathAccess(string path, int startNodeId, int recycleBinId) + internal static bool HasPathAccess(string path, int[] startNodeIds, int recycleBinId) { Mandate.ParameterNotNullOrEmpty(path, "path"); var formattedPath = "," + path + ","; - var formattedStartNodeId = "," + startNodeId.ToInvariantString() + ","; var formattedRecycleBinId = "," + recycleBinId.ToInvariantString() + ","; //only users with root access have access to the recycle bin if (formattedPath.Contains(formattedRecycleBinId)) { - return startNodeId == Constants.System.Root; + var hasAccess = startNodeIds.Length == 0 || startNodeIds.Contains(Constants.System.Root); + return hasAccess; + } + + //check for normal paths + foreach (var startNodeId in startNodeIds) + { + var formattedStartNodeId = "," + startNodeId.ToInvariantString() + ","; + + var hasAccess = formattedPath.Contains(formattedStartNodeId); + if (hasAccess) + return true; } - return formattedPath.Contains(formattedStartNodeId); + return false; } /// @@ -79,7 +89,7 @@ namespace Umbraco.Core.Models { if (user == null) throw new ArgumentNullException("user"); if (media == null) throw new ArgumentNullException("media"); - return HasPathAccess(media.Path, user.StartMediaId, Constants.System.RecycleBinMedia); + return HasPathAccess(media.Path, user.StartMediaIds, Constants.System.RecycleBinMedia); } /// diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 81a4b88289..e7f2aba08b 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; @@ -31,8 +32,8 @@ namespace Umbraco.Core.Persistence.Factories user.DisableChangeTracking(); user.Key = guidId; - user.StartContentId = dto.ContentStartId; - user.StartMediaId = dto.MediaStartId ?? -1; + user.StartContentIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.Id).ToArray(); + user.StartMediaIds = dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int) UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.Id).ToArray(); user.IsLockedOut = dto.NoConsole; user.IsApproved = dto.Disabled == false; user.Language = dto.UserLanguage; @@ -59,9 +60,7 @@ namespace Umbraco.Core.Persistence.Factories public static UserDto BuildDto(IUser entity) { var dto = new UserDto - { - ContentStartId = entity.StartContentId, - MediaStartId = entity.StartMediaId, + { Disabled = entity.IsApproved == false, Email = entity.Email, Login = entity.Username, @@ -78,6 +77,26 @@ namespace Umbraco.Core.Persistence.Factories UpdateDate = entity.UpdateDate }; + foreach (var startNodeId in entity.StartContentIds) + { + dto.UserStartNodeDtos.Add(new UserStartNodeDto + { + StartNode = startNodeId, + StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Content, + UserId = entity.Id + }); + } + + foreach (var startNodeId in entity.StartMediaIds) + { + dto.UserStartNodeDtos.Add(new UserStartNodeDto + { + StartNode = startNodeId, + StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Media, + UserId = entity.Id + }); + } + if (entity.HasIdentity) { dto.Id = entity.Id.SafeCast(); diff --git a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs index 9ea69abbf6..06e906143a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs @@ -34,9 +34,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.RawPasswordValue, dto => dto.Password); CacheMap(src => src.Name, dto => dto.UserName); //NOTE: This column in the db is *not* used! - //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); - CacheMap(src => src.StartMediaId, dto => dto.MediaStartId); - CacheMap(src => src.StartContentId, dto => dto.ContentStartId); + //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); CacheMap(src => src.IsApproved, dto => dto.Disabled); CacheMap(src => src.IsLockedOut, dto => dto.NoConsole); CacheMap(src => src.Language, dto => dto.UserLanguage); diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 2e0bbf9ad2..456859a5ea 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -169,8 +169,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateUmbracoUserData() { - _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, ContentStartId = -1, MediaStartId = -1, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); - //_database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); + _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); } private void CreateUmbracoUserGroupData() diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 61a6e66bb4..d23b23b4b6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -90,6 +90,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {51, typeof (User2UserGroupDto) }, {52, typeof (UserGroup2NodePermissionDto) }, {53, typeof (UserGroup2AppDto) }, + {54, typeof (UserStartNodeDto) }, }; #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 45438da9d8..c11c413f56 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -143,7 +143,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial } //if the error is for umbracoUserGroup it must be the previous version to 7.7 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoUserGroup")))) + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoUserStartNode")))) { return new Version(7, 6, 0); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs index 5e99a47417..af9805221d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs @@ -3,6 +3,7 @@ using System.Data; using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.SqlSyntax; @@ -63,8 +64,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe Create.Table(); updated = true; } + + + return updated; - } + } private void MigrateUserTypesToGroups() { @@ -158,7 +162,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe if (tables.InvariantContains("umbracoUserType") && tables.InvariantContains("umbracoUser")) { if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id"))) - { + { Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser"); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs new file mode 100644 index 0000000000..086949663b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs @@ -0,0 +1,49 @@ +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero +{ + [Migration("7.7.0", 2, Constants.System.UmbracoMigrationName)] + public class AddUserStartNodeTable : MigrationBase + { + public AddUserStartNodeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) + { + } + + public override void Up() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains("umbracoUserStartNode") == false) + { + Create.Table(); + + MigrateUserStartNodes(); + + //now remove the old columns + + Delete.Column("startStructureID").FromTable("umbracoUser"); + Delete.Column("startMediaID").FromTable("umbracoUser"); + } + } + + private void MigrateUserStartNodes() + { + Execute.Sql(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) + SELECT id, startStructureID, 1 + FROM umbracoUser + WHERE startStructureID IS NOT NULL AND startStructureID > 0 AND startStructureID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Document + "')"); + + Execute.Sql(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) + SELECT id, startMediaID, 2 + FROM umbracoUser + WHERE startMediaID IS NOT NULL AND startMediaID > 0 AND startMediaID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Media + "')"); + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs index 0d9b1d5552..435b02c2e3 100644 --- a/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs +++ b/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Persistence.Relators { private UserDto _currentUser; - internal UserDto Map(UserDto user, UserGroupDto group, UserGroup2AppDto section) + internal UserDto Map(UserDto user, UserGroupDto group, UserGroup2AppDto section, UserStartNodeDto startNode) { // Terminating call. Since we can return null from this function // we need to be ready for PetaPoco to callback later with null @@ -20,6 +20,7 @@ namespace Umbraco.Core.Persistence.Relators if (_currentUser != null && _currentUser.Id == user.Id) { AddOrUpdateGroup(group, section); + AddOrUpdateStartNode(startNode); // Return null to indicate we're not done with this object yet return null; @@ -34,13 +35,33 @@ namespace Umbraco.Core.Persistence.Relators // Setup the new current user _currentUser = user; _currentUser.UserGroupDtos = new List(); + _currentUser.UserStartNodeDtos = new List(); AddOrUpdateGroup(group, section); + AddOrUpdateStartNode(startNode); // Return the now populated previous user (or null if first time through) return prev; } + private void AddOrUpdateStartNode(UserStartNodeDto startNode) + { + //this can be null since we are left joining + if (startNode == null || startNode.Id == default(int)) + return; + + //check if this is a new start node + var latestStartNode = _currentUser.UserStartNodeDtos.Count > 0 + ? _currentUser.UserStartNodeDtos[_currentUser.UserStartNodeDtos.Count - 1] + : null; + + if (latestStartNode == null || latestStartNode.Id != startNode.Id) + { + //add the current (new) start node + _currentUser.UserStartNodeDtos.Add(startNode); + } + } + private void AddOrUpdateGroup(UserGroupDto group, UserGroup2AppDto section) { //I don't even think this situation can happen but if it could, we'd want the section added to the latest group check if this is a new group @@ -50,21 +71,21 @@ namespace Umbraco.Core.Persistence.Relators } //this can be null since we are doing a left join - if (group != null && group.Alias.IsNullOrWhiteSpace() == false) - { - //check if this is a new group - var latestGroup = _currentUser.UserGroupDtos.Count > 0 - ? _currentUser.UserGroupDtos[_currentUser.UserGroupDtos.Count - 1] - : null; + if (group == null || group.Alias.IsNullOrWhiteSpace()) + return; - if (latestGroup == null || latestGroup.Id != group.Id) - { - //add the current (new) group - _currentUser.UserGroupDtos.Add(group); - } - - AddSection(section); + //check if this is a new group + var latestGroup = _currentUser.UserGroupDtos.Count > 0 + ? _currentUser.UserGroupDtos[_currentUser.UserGroupDtos.Count - 1] + : null; + + if (latestGroup == null || latestGroup.Id != group.Id) + { + //add the current (new) group + _currentUser.UserGroupDtos.Add(group); } + + AddSection(section); } private void AddSection(UserGroup2AppDto section) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index 59335daac0..63a6900634 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -54,5 +54,8 @@ namespace Umbraco.Core.Persistence.Repositories /// Optional parameter to filter by specfied user state /// IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Expression> orderBy, Direction orderDirection, string[] userGroups = null, UserState? userState = null, IQuery filter = null); + + IProfile GetProfile(string username); + IProfile GetProfile(int id); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 4c203f6dbb..4b05b5a47a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -38,18 +38,45 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where(GetBaseWhereClause(), new { Id = id }); sql //must be included for relator to work .OrderBy(d => d.Id, SqlSyntax) - .OrderBy(d => d.Id, SqlSyntax); + .OrderBy(d => d.Id, SqlSyntax) + .OrderBy(d => d.Id, SqlSyntax); - var dto = Database.Fetch(new UserGroupRelator().Map, sql) + var dto = Database.Fetch(new UserGroupRelator().Map, sql) .FirstOrDefault(); if (dto == null) - return null; + return null; var user = UserFactory.BuildEntity(dto); return user; } - + + public IProfile GetProfile(string username) + { + var sql = GetBaseQuery(false).Where(userDto => userDto.UserName == username, SqlSyntax); + + var dto = Database.Fetch(sql) + .FirstOrDefault(); + + if (dto == null) + return null; + + return new UserProfile(dto.Id, dto.UserName); + } + + public IProfile GetProfile(int id) + { + var sql = GetBaseQuery(false).Where(userDto => userDto.Id == id, SqlSyntax); + + var dto = Database.Fetch(sql) + .FirstOrDefault(); + + if (dto == null) + return null; + + return new UserProfile(dto.Id, dto.UserName); + } + protected override IEnumerable PerformGetAll(params int[] ids) { var sql = GetQueryWithGroups(); @@ -59,9 +86,10 @@ namespace Umbraco.Core.Persistence.Repositories } sql //must be included for relator to work .OrderBy(d => d.Id, SqlSyntax) - .OrderBy(d => d.Id, SqlSyntax); + .OrderBy(d => d.Id, SqlSyntax) + .OrderBy(d => d.Id, SqlSyntax); - var users = ConvertFromDtos(Database.Fetch(new UserGroupRelator().Map, sql)) + var users = ConvertFromDtos(Database.Fetch(new UserGroupRelator().Map, sql)) .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. return users; @@ -74,9 +102,10 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate(); sql //must be included for relator to work .OrderBy(d => d.Id, SqlSyntax) - .OrderBy(d => d.Id, SqlSyntax); + .OrderBy(d => d.Id, SqlSyntax) + .OrderBy(d => d.Id, SqlSyntax); - var dtos = Database.Fetch(new UserGroupRelator().Map, sql) + var dtos = Database.Fetch(new UserGroupRelator().Map, sql) .DistinctBy(x => x.Id); var users = ConvertFromDtos(dtos) @@ -110,7 +139,7 @@ namespace Umbraco.Core.Persistence.Repositories private Sql GetQueryWithGroups() { //base query includes user groups - var sql = GetBaseQuery("umbracoUser.*, umbracoUserGroup.*, umbracoUserGroup2App.*"); + var sql = GetBaseQuery("umbracoUser.*, umbracoUserGroup.*, umbracoUserGroup2App.*, umbracoUserStartNode.*"); AddGroupLeftJoin(sql); return sql; } @@ -122,7 +151,9 @@ namespace Umbraco.Core.Persistence.Repositories .LeftJoin(SqlSyntax) .On(SqlSyntax, dto => dto.Id, dto => dto.UserGroupId) .LeftJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.UserGroupId, dto => dto.Id); + .On(SqlSyntax, dto => dto.UserGroupId, dto => dto.Id) + .LeftJoin(SqlSyntax) + .On(SqlSyntax, dto => dto.UserId, dto => dto.Id); } private Sql GetBaseQuery(string columns) @@ -396,6 +427,8 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, mappedField, orderDirection, userGroups, userState, filter); } + + private IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, string[] userGroups = null, UserState? userState = null, IQuery filter = null) { if (string.IsNullOrWhiteSpace(orderBy)) throw new ArgumentException("Value cannot be null or whitespace.", "orderBy"); @@ -448,7 +481,7 @@ namespace Umbraco.Core.Persistence.Repositories string sqlStringCount, sqlStringPage; Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); - var sqlQueryFull = GetBaseQuery("umbracoUser.*, umbracoUserGroup.*, umbracoUserGroup2App.*"); + var sqlQueryFull = GetBaseQuery("umbracoUser.*, umbracoUserGroup.*, umbracoUserGroup2App.*, umbracoUserStartNode.*"); var fullQueryWithPagedInnerJoin = sqlQueryFull .Append("INNER JOIN (") @@ -464,7 +497,7 @@ namespace Umbraco.Core.Persistence.Repositories GetFilteredSqlForPagedResults(fullQueryWithPagedInnerJoin, filterSql), orderDirection, orderBy); - var users = ConvertFromDtos(Database.Fetch(new UserGroupRelator().Map, fullQuery)) + var users = ConvertFromDtos(Database.Fetch(new UserGroupRelator().Map, fullQuery)) .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. return users; diff --git a/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs b/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs index 7c2373a000..63880e369e 100644 --- a/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs +++ b/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs @@ -35,8 +35,8 @@ namespace Umbraco.Core.Security AllowedApplications = user.AllowedSections, Culture = user.Culture, Roles = user.Roles.Select(x => x.RoleId).ToArray(), - StartContentNode = user.StartContentId, - StartMediaNode = user.StartMediaId, + StartContentNodes = user.StartContentIds, + StartMediaNodes = user.StartMediaIds, SessionId = user.SecurityStamp }); diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 4206cc731e..611242b088 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -75,8 +75,8 @@ namespace Umbraco.Core.Security Language = user.Culture ?? Configuration.GlobalSettings.DefaultUILanguage, Name = user.Name, Username = user.UserName, - StartContentId = user.StartContentId == 0 ? -1 : user.StartContentId, - StartMediaId = user.StartMediaId == 0 ? -1 : user.StartMediaId, + StartContentIds = user.StartContentIds ?? new int[] { }, + StartMediaIds = user.StartMediaIds ?? new int[] { }, IsLockedOut = user.IsLockedOut, IsApproved = true }; @@ -673,15 +673,15 @@ namespace Umbraco.Core.Security anythingChanged = true; user.Language = identityUser.Culture; } - if (user.StartMediaId != identityUser.StartMediaId) + if (user.StartMediaIds.UnsortedSequenceEqual(identityUser.StartMediaIds) == false) { anythingChanged = true; - user.StartMediaId = identityUser.StartMediaId; + user.StartMediaIds = identityUser.StartMediaIds; } - if (user.StartContentId != identityUser.StartContentId) + if (user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds)) { anythingChanged = true; - user.StartContentId = identityUser.StartContentId; + user.StartContentIds = identityUser.StartContentIds; } if (user.SecurityStamp != identityUser.SecurityStamp) { diff --git a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs index 1bc9902da5..39931341fa 100644 --- a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs @@ -48,12 +48,20 @@ namespace Umbraco.Core.Security || realName == null || session == null) throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since there are missing required claims"); - int startContentIdAsInt; - int startMediaIdAsInt; - if (int.TryParse(startContentId, out startContentIdAsInt) == false || int.TryParse(startMediaId, out startMediaIdAsInt) == false) + int[] startContentIdsAsInt; + int[] startMediaIdsAsInt; + if (startContentId.DetectIsJson() == false || startMediaId.DetectIsJson() == false) + throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the data is not formatted correctly - either content or media start Ids are not JSON"); + + try { - throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the data is not formatted correctly"); + startContentIdsAsInt = JsonConvert.DeserializeObject(startContentId); + startMediaIdsAsInt = JsonConvert.DeserializeObject(startMediaId); } + catch (Exception e) + { + throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the data is not formatted correctly - either content or media start Ids could not be parsed as JSON", e); + } var roles = identity.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToList(); var allowedApps = identity.FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToList(); @@ -67,8 +75,8 @@ namespace Umbraco.Core.Security Roles = roles.ToArray(), Username = username, RealName = realName, - StartContentNode = startContentIdAsInt, - StartMediaNode = startMediaIdAsInt + StartContentNodes = startContentIdsAsInt, + StartMediaNodes = startMediaIdsAsInt }; return new UmbracoBackOfficeIdentity(identity, userData); @@ -202,10 +210,10 @@ namespace Umbraco.Core.Security AddClaim(new Claim(ClaimTypes.GivenName, UserData.RealName, ClaimValueTypes.String, Issuer, Issuer, this)); if (HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false) - AddClaim(new Claim(Constants.Security.StartContentNodeIdClaimType, StartContentNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); + AddClaim(new Claim(Constants.Security.StartContentNodeIdClaimType, JsonConvert.SerializeObject(StartContentNodes), ClaimValueTypes.Integer32, Issuer, Issuer, this)); if (HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false) - AddClaim(new Claim(Constants.Security.StartMediaNodeIdClaimType, StartMediaNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); + AddClaim(new Claim(Constants.Security.StartMediaNodeIdClaimType, JsonConvert.SerializeObject(StartMediaNodes), ClaimValueTypes.Integer32, Issuer, Issuer, this)); if (HasClaim(x => x.Type == ClaimTypes.Locality) == false) AddClaim(new Claim(ClaimTypes.Locality, Culture, ClaimValueTypes.String, Issuer, Issuer, this)); @@ -259,14 +267,14 @@ namespace Umbraco.Core.Security get { return _currentIssuer; } } - public int StartContentNode + public int[] StartContentNodes { - get { return UserData.StartContentNode; } + get { return UserData.StartContentNodes; } } - public int StartMediaNode + public int[] StartMediaNodes { - get { return UserData.StartMediaNode; } + get { return UserData.StartMediaNodes; } } public string[] AllowedApplications diff --git a/src/Umbraco.Core/Security/UserData.cs b/src/Umbraco.Core/Security/UserData.cs index 407d2782dd..31a7d50f25 100644 --- a/src/Umbraco.Core/Security/UserData.cs +++ b/src/Umbraco.Core/Security/UserData.cs @@ -48,10 +48,10 @@ namespace Umbraco.Core.Security public string RealName { get; set; } [DataMember(Name = "startContent")] - public int StartContentNode { get; set; } + public int[] StartContentNodes { get; set; } [DataMember(Name = "startMedia")] - public int StartMediaNode { get; set; } + public int[] StartMediaNodes { get; set; } [DataMember(Name = "allowedApps")] public string[] AllowedApplications { get; set; } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 8072ecb94d..a801986b60 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -107,9 +107,7 @@ namespace Umbraco.Core.Services Language = GlobalSettings.DefaultUILanguage, Name = username, RawPasswordValue = passwordValue, - Username = username, - StartContentId = -1, - StartMediaId = -1, + Username = username, IsLockedOut = false, IsApproved = true }; @@ -604,8 +602,11 @@ namespace Umbraco.Core.Services /// public IProfile GetProfileById(int id) { - var user = GetUserById(id); - return user.ProfileData; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateUserRepository(uow); + return repository.GetProfile(id); + } } /// @@ -615,8 +616,11 @@ namespace Umbraco.Core.Services /// public IProfile GetProfileByUserName(string username) { - var user = GetByUsername(username); - return user.ProfileData; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateUserRepository(uow); + return repository.GetProfile(username); + } } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b6728affdf..52a367224b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -343,8 +343,10 @@ + + @@ -510,6 +512,7 @@ + diff --git a/src/Umbraco.Tests/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Controllers/UsersControllerTests.cs index b722bc789b..7af3d5e340 100644 --- a/src/Umbraco.Tests/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Controllers/UsersControllerTests.cs @@ -36,6 +36,52 @@ namespace Umbraco.Tests.Controllers public class UsersControllerTests : BaseDatabaseFactoryTest { + [Test] + public async void Save_User() + { + var runner = new TestRunner((message, helper) => + { + //setup some mocks + Umbraco.Core.Configuration.GlobalSettings.HasSmtpServer = true; + + var userServiceMock = Mock.Get(helper.UmbracoContext.Application.Services.UserService); + + userServiceMock.Setup(service => service.Save(It.IsAny(), It.IsAny())) + .Callback((IUser u, bool raiseEvents) => + { + u.Id = 1234; + }); + userServiceMock.Setup(service => service.GetAllUserGroups(It.IsAny())) + .Returns(Enumerable.Empty); + + //we need to manually apply automapper mappings with the mocked applicationcontext + InitializeMappers(helper.UmbracoContext.Application); + + return new UsersController(helper.UmbracoContext); + }); + + var invite = new UserInvite + { + Id = -1, + Email = "test@test.com", + Message = "Hello test!", + Name = "Test", + UserGroups = new[] { "writers" } + }; + var response = await runner.Execute("Users", "PostSaveUser", HttpMethod.Post, + new ObjectContent(invite, new JsonMediaTypeFormatter())); + + var obj = JsonConvert.DeserializeObject(response.Item2); + + Assert.AreEqual(invite.Name, obj.Name); + Assert.AreEqual(1234, obj.Id); + Assert.AreEqual(invite.Email, obj.Email); + foreach (var group in invite.UserGroups) + { + Assert.IsTrue(obj.UserGroups.Contains(group)); + } + } + [Test] public async void Invite_User() { diff --git a/src/Umbraco.Tests/Models/UserExtensionsTests.cs b/src/Umbraco.Tests/Models/UserExtensionsTests.cs index 84537987c9..c6f9c55ead 100644 --- a/src/Umbraco.Tests/Models/UserExtensionsTests.cs +++ b/src/Umbraco.Tests/Models/UserExtensionsTests.cs @@ -17,7 +17,7 @@ namespace Umbraco.Tests.Models public void Determines_Path_Based_Access_To_Content(int userId, string contentPath, bool outcome) { var userMock = new Mock(); - userMock.Setup(u => u.StartContentId).Returns(userId); + userMock.Setup(u => u.StartContentIds).Returns(new[]{ userId }); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns(contentPath); diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index 5bdc842cc1..104f435bf5 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -35,8 +35,8 @@ namespace Umbraco.Tests.Models PasswordQuestion = "question", //ProviderUserKey = "user key", SessionTimeout = 5, - StartContentId = 3, - StartMediaId = 8, + StartContentIds = new []{ 3 }, + StartMediaIds = new[]{ 8 }, Username = "username" }; @@ -82,8 +82,8 @@ namespace Umbraco.Tests.Models PasswordQuestion = "question", //ProviderUserKey = "user key", SessionTimeout = 5, - StartContentId = 3, - StartMediaId = 8, + StartContentIds = new[]{ 3 }, + StartMediaIds = new []{ 8 }, Username = "username" }; diff --git a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs index e9d9163b12..9f336d404e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repo = new NotificationsRepository(unitOfWork)) { - var userDto = new UserDto {ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; + var userDto = new UserDto {Email = "test", Login = "test", Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 10; i++) { - var userDto = new UserDto {ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; + var userDto = new UserDto {Email = "test" + i, Login = "test" + i, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); @@ -109,7 +109,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 10; i++) { - var userDto = new UserDto {ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; + var userDto = new UserDto {Email = "test" + i, Login = "test" + i, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now}; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); @@ -128,7 +128,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repo = new NotificationsRepository(unitOfWork)) { - var userDto = new UserDto {ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; + var userDto = new UserDto {Email = "test", Login = "test", Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; unitOfWork.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 0ad8fe1708..a4cf866cd2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -153,8 +153,8 @@ namespace Umbraco.Tests.Persistence.Repositories resolved.IsApproved = false; resolved.RawPasswordValue = "new"; resolved.IsLockedOut = true; - resolved.StartContentId = 10; - resolved.StartMediaId = 11; + resolved.StartContentIds = new []{ 10 }; + resolved.StartMediaIds = new []{ 11 }; resolved.Email = "new@new.com"; resolved.Username = "newName"; @@ -169,8 +169,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(updatedItem.IsApproved, Is.EqualTo(resolved.IsApproved)); Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(resolved.RawPasswordValue)); Assert.That(updatedItem.IsLockedOut, Is.EqualTo(resolved.IsLockedOut)); - Assert.That(updatedItem.StartContentId, Is.EqualTo(resolved.StartContentId)); - Assert.That(updatedItem.StartMediaId, Is.EqualTo(resolved.StartMediaId)); + Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(resolved.StartContentIds)); + Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(resolved.StartMediaIds)); Assert.That(updatedItem.Email, Is.EqualTo(resolved.Email)); Assert.That(updatedItem.Username, Is.EqualTo(resolved.Username)); Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); @@ -360,8 +360,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved)); Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue)); Assert.That(updatedItem.IsLockedOut, Is.EqualTo(originalUser.IsLockedOut)); - Assert.That(updatedItem.StartContentId, Is.EqualTo(originalUser.StartContentId)); - Assert.That(updatedItem.StartMediaId, Is.EqualTo(originalUser.StartMediaId)); + Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(originalUser.StartContentIds)); + Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(originalUser.StartMediaIds)); Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email)); Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username)); Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs b/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs index 813653fb7c..226b26990d 100644 --- a/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs +++ b/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs @@ -44,8 +44,8 @@ namespace Umbraco.Tests.Security Assert.AreEqual(sessionId, backofficeIdentity.SessionId); Assert.AreEqual("testing", backofficeIdentity.Username); Assert.AreEqual("hello world", backofficeIdentity.RealName); - Assert.AreEqual(-1, backofficeIdentity.StartContentNode); - Assert.AreEqual(5543, backofficeIdentity.StartMediaNode); + Assert.AreEqual(0, backofficeIdentity.StartContentNodes.Length); + Assert.IsTrue(backofficeIdentity.StartMediaNodes.UnsortedSequenceEqual(new []{ 5543 })); Assert.IsTrue(new[] {"content", "media"}.SequenceEqual(backofficeIdentity.AllowedApplications)); Assert.AreEqual("en-us", backofficeIdentity.Culture); Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles)); @@ -98,8 +98,7 @@ namespace Umbraco.Tests.Security Id = 1234, RealName = "hello world", Roles = new[] {"admin"}, - StartContentNode = -1, - StartMediaNode = 654, + StartMediaNodes = new []{ 654 }, Username = "testing" }; @@ -119,8 +118,7 @@ namespace Umbraco.Tests.Security Id = 1234, RealName = "hello world", Roles = new[] { "admin" }, - StartContentNode = -1, - StartMediaNode = 654, + StartMediaNodes =new []{ 654 } , Username = "testing" }; @@ -146,8 +144,7 @@ namespace Umbraco.Tests.Security Id = 1234, RealName = "hello world", Roles = new[] { "admin" }, - StartContentNode = -1, - StartMediaNode = 654, + StartMediaNodes = new []{ 654 }, Username = "testing" }; @@ -170,8 +167,7 @@ namespace Umbraco.Tests.Security Id = 1234, RealName = "hello world", Roles = new[] { "admin" }, - StartContentNode = -1, - StartMediaNode = 654, + StartMediaNodes = new []{ 654 }, Username = "testing" }; diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index d99f6e70d2..5a0ad30169 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using umbraco.BusinessLogic.Actions; +using Umbraco.Core; using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Services @@ -684,8 +685,8 @@ namespace Umbraco.Tests.Services Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved)); Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue)); Assert.That(updatedItem.IsLockedOut, Is.EqualTo(originalUser.IsLockedOut)); - Assert.That(updatedItem.StartContentId, Is.EqualTo(originalUser.StartContentId)); - Assert.That(updatedItem.StartMediaId, Is.EqualTo(originalUser.StartMediaId)); + Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(originalUser.StartContentIds)); + Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(originalUser.StartMediaIds)); Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email)); Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username)); Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs index 2dbfa757f2..0cfcb47ade 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs @@ -31,9 +31,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting Roles = new[] { "admin" }, AllowedApplications = new[] { "content", "media", "members" }, Culture = "en-US", - RealName = "Admin", - StartContentNode = -1, - StartMediaNode = -1, + RealName = "Admin", Username = "admin" }); diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 7299d9758e..127269a384 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -90,8 +90,8 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting && u.Id == (int) backofficeIdentity.Id && u.Language == "en" && u.Name == backofficeIdentity.RealName - && u.StartContentId == backofficeIdentity.StartContentNode - && u.StartMediaId == backofficeIdentity.StartMediaNode + && u.StartContentIds == backofficeIdentity.StartContentNodes + && u.StartMediaIds == backofficeIdentity.StartMediaNodes && u.Username == backofficeIdentity.Username)); //mock Validate diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs index 53b53e634f..ce2931e99e 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs @@ -15,8 +15,6 @@ namespace Umbraco.Tests.TestHelpers.Entities Name = "TestUser" + suffix, RawPasswordValue = "testing", IsLockedOut = false, - StartContentId = -1, - StartMediaId = -1, Email = "test" + suffix + "@test.com", Username = "TestUser" + suffix }; diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index c7922b9d58..2b832c098c 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -96,7 +96,7 @@ namespace Umbraco.Tests.UmbracoExamine } if (userService == null) { - userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); + userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == 0 && p.Name == "admin")); } if (mediaService == null) { diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs index 9634bb164a..7782612420 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/ContentControllerUnitTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -63,7 +63,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(9876); + userMock.Setup(u => u.StartContentIds).Returns(new[]{ 9876 }); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -89,7 +89,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -118,7 +118,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); @@ -147,7 +147,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; //act @@ -163,7 +163,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; //act @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(1234); + userMock.Setup(u => u.StartContentIds).Returns(new []{ 1234 }); var user = userMock.Object; //act @@ -195,7 +195,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(1234); + userMock.Setup(u => u.StartContentIds).Returns(new []{ 1234 }); var user = userMock.Object; //act @@ -211,7 +211,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); @@ -235,7 +235,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); @@ -259,7 +259,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); @@ -283,7 +283,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs index c776924af7..bc4046b39e 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/FilterAllowedOutgoingContentAttributeTests.cs @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(5); + userMock.Setup(u => u.StartContentIds).Returns(new []{ 5 }); var user = userMock.Object; att.FilterBasedOnStartNode(list, user); @@ -105,7 +105,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartContentId).Returns(-1); + userMock.Setup(u => u.StartContentIds).Returns(new int[0]); var user = userMock.Object; var userServiceMock = new Mock(); diff --git a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs index 2d5420409c..e65b1a1b19 100644 --- a/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/WebApiEditors/MediaControllerUnitTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartMediaId).Returns(-1); + userMock.Setup(u => u.StartMediaIds).Returns(new int[0]); var user = userMock.Object; var mediaMock = new Mock(); mediaMock.Setup(m => m.Path).Returns("-1,1234,5678"); @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartMediaId).Returns(-1); + userMock.Setup(u => u.StartMediaIds).Returns(new int[0]); var user = userMock.Object; var mediaMock = new Mock(); mediaMock.Setup(m => m.Path).Returns("-1,1234,5678"); @@ -59,7 +59,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(9); - userMock.Setup(u => u.StartMediaId).Returns(9876); + userMock.Setup(u => u.StartMediaIds).Returns(new[]{ 9876 }); var user = userMock.Object; var mediaMock = new Mock(); mediaMock.Setup(m => m.Path).Returns("-1,1234,5678"); @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartMediaId).Returns(-1); + userMock.Setup(u => u.StartMediaIds).Returns(new int[]{}); var user = userMock.Object; //act @@ -97,7 +97,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartMediaId).Returns(1234); + userMock.Setup(u => u.StartMediaIds).Returns(new[]{ 1234 }); var user = userMock.Object; //act @@ -113,7 +113,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartMediaId).Returns(-1); + userMock.Setup(u => u.StartMediaIds).Returns(new int[]{}); var user = userMock.Object; //act @@ -129,7 +129,7 @@ namespace Umbraco.Tests.Web.Controllers.WebApiEditors //arrange var userMock = new Mock(); userMock.Setup(u => u.Id).Returns(0); - userMock.Setup(u => u.StartMediaId).Returns(1234); + userMock.Setup(u => u.StartMediaIds).Returns(new[]{ 1234 }); var user = userMock.Object; //act diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index c95e1a7b7f..1a652a787c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -26,7 +26,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat enablelistviewexpand: '@' }, - compile: function(element, attrs) { + compile: function (element, attrs) { //config //var showheader = (attrs.showheader !== 'false'); var hideoptions = (attrs.hideoptions === 'true') ? "hide-options" : ""; @@ -44,7 +44,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat element.replaceWith(template); - return function(scope, elem, attr, controller) { + return function (scope, elem, attr, controller) { //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover @@ -84,16 +84,16 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat function setupExternalEvents() { if (scope.eventhandler) { - scope.eventhandler.clearCache = function(section) { + scope.eventhandler.clearCache = function (section) { treeService.clearCache({ section: section }); }; - scope.eventhandler.load = function(section) { + scope.eventhandler.load = function (section) { scope.section = section; loadTree(); }; - scope.eventhandler.reloadNode = function(node) { + scope.eventhandler.reloadNode = function (node) { if (!node) { node = scope.currentNode; @@ -108,7 +108,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat Used to do the tree syncing. If the args.tree is not specified we are assuming it has been specified previously using the _setActiveTreeType */ - scope.eventhandler.syncTree = function(args) { + scope.eventhandler.syncTree = function (args) { if (!args) { throw "args cannot be null"; } @@ -146,9 +146,16 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat // and previous so that the tree syncs properly. The tree syncs from the top down and if there are parts // of the tree's path in there that don't actually exist in the dom/model then syncing will not work. - userService.getCurrentUser().then(function(userData) { + userService.getCurrentUser().then(function (userData) { + + var startNodes = []; + for (var i = 0; i < userData.startContentIds; i++) { + startNodes.push(userData.startContentIds[i]); + } + for (var j = 0; j < userData.startMediaIds; j++) { + startNodes.push(userData.startMediaIds[j]); + } - var startNodes = [userData.startContentId, userData.startMediaId]; _.each(startNodes, function (i) { var found = _.find(args.path, function (p) { return String(p) === String(i); @@ -175,7 +182,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat node's children - this is synonymous with the legacy refreshTree method - again should not be used and should only be used for the legacy code to work. */ - scope.eventhandler._setActiveTreeType = function(treeAlias, loadChildren) { + scope.eventhandler._setActiveTreeType = function (treeAlias, loadChildren) { loadActiveTree(treeAlias, loadChildren); }; } @@ -210,7 +217,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat function doLoad(tree) { var childrenAndSelf = [tree].concat(tree.children); scope.activeTree = _.find(childrenAndSelf, function (node) { - if(node && node.metaData && node.metaData.treeAlias) { + if (node && node.metaData && node.metaData.treeAlias) { return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); } return false; @@ -223,7 +230,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //This is only used for the legacy tree method refreshTree! if (loadChildren) { scope.activeTree.expanded = true; - scope.loadChildren(scope.activeTree, false).then(function() { + scope.loadChildren(scope.activeTree, false).then(function () { emitEvent("activeTreeLoaded", { tree: scope.activeTree }); }); } @@ -236,7 +243,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat doLoad(scope.tree.root); } else { - scope.eventhandler.one("treeLoaded", function(e, args) { + scope.eventhandler.one("treeLoaded", function (e, args) { doLoad(args.tree.root); }); } @@ -261,7 +268,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat } treeService.getTree(args) - .then(function(data) { + .then(function (data) { //set the data once we have it scope.tree = data; @@ -274,7 +281,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat emitEvent("treeLoaded", { tree: scope.tree }); emitEvent("treeNodeExpanded", { tree: scope.tree, node: scope.tree.root, children: scope.tree.root.children }); - }, function(reason) { + }, function (reason) { scope.loading = false; notificationsService.error("Tree Error", reason); }); @@ -306,8 +313,8 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat scope.selectEnabledNodeClass = function (node) { return node ? node.selected ? - 'icon umb-tree-icon sprTree icon-check green temporary' : - '' : + 'icon umb-tree-icon sprTree icon-check green temporary' : + '' : ''; }; @@ -315,7 +322,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat * This changes dynamically based on if we are changing sections or just loading normal tree data. * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations. */ - scope.animation = function() { + scope.animation = function () { if (deleteAnimations && scope.tree && scope.tree.root && scope.tree.root.expanded) { return { leave: 'tree-node-delete-leave' }; } @@ -325,7 +332,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat }; /* helper to force reloading children of a tree node */ - scope.loadChildren = function(node, forceReload) { + scope.loadChildren = function (node, forceReload) { var deferred = $q.defer(); //emit treeNodeExpanding event, if a callback object is set on the tree @@ -339,7 +346,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat if (forceReload || (node.hasChildren && node.children.length === 0)) { //get the children from the tree service treeService.loadNodeChildren({ node: node, section: scope.section }) - .then(function(data) { + .then(function (data) { //emit expanded event emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data }); @@ -365,7 +372,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat The tree doesnt know about this, so it raises an event to tell the parent controller about it. */ - scope.options = function(n, ev) { + scope.options = function (n, ev) { emitEvent("treeOptionsClick", { element: elem, node: n, event: ev }); }; @@ -384,12 +391,12 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat emitEvent("treeNodeSelect", { element: elem, node: n, event: ev }); }; - scope.altSelect = function(n, ev) { + scope.altSelect = function (n, ev) { emitEvent("treeNodeAltSelect", { element: elem, tree: scope.tree, node: n, event: ev }); }; //watch for section changes - scope.$watch("section", function(newVal, oldVal) { + scope.$watch("section", function (newVal, oldVal) { if (!scope.tree) { loadTree(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 262b06dd50..7b7ea7e3b4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -235,16 +235,6 @@ angular.module('umbraco.services') var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; - //TODO: This is a mega backwards compatibility hack... These variables SHOULD NOT exist in the server variables - // since they are not supposed to be dynamic but I accidentally added them there in 7.1.5 IIRC so some people might - // now be relying on this :( - if (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables) { - Umbraco.Sys.ServerVariables["security"] = { - startContentId: data.startContentId, - startMediaId: data.startMediaId - }; - } - if (args && args.broadcastEvent) { //broadcast a global event, will inform listening controllers to load in the user specific data eventsService.emit("app.authenticated", result); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index 5ee103a32b..e76db90f47 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -108,15 +108,15 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", $scope.switchToMediaPicker = function () { userService.getCurrentUser().then(function (userData) { - dialogService.mediaPicker({ - startNodeId: userData.startMediaId, - callback: function (media) { - $scope.target.id = media.id; - $scope.target.isMedia = true; - $scope.target.name = media.name; - $scope.target.url = mediaHelper.resolveFile(media); - } - }); + dialogService.mediaPicker({ + startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], + callback: function(media) { + $scope.target.id = media.id; + $scope.target.isMedia = true; + $scope.target.name = media.name; + $scope.target.url = mediaHelper.resolveFile(media); + } + }); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index fde3e4cab3..9c5a2b888c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -89,23 +89,23 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $scope.switchToMediaPicker = function () { userService.getCurrentUser().then(function (userData) { - $scope.mediaPickerOverlay = { - view: "mediapicker", - startNodeId: userData.startMediaId, - show: true, - submit: function(model) { - var media = model.selectedImages[0]; + $scope.mediaPickerOverlay = { + view: "mediapicker", + startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], + show: true, + submit: function(model) { + var media = model.selectedImages[0]; - $scope.model.target.id = media.id; - $scope.model.target.udi = media.udi; - $scope.model.target.isMedia = true; - $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); + $scope.model.target.id = media.id; + $scope.model.target.udi = media.udi; + $scope.model.target.isMedia = true; + $scope.model.target.name = media.name; + $scope.model.target.url = mediaHelper.resolveFile(media); - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index 9b7c929367..b0b19a2716 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -279,7 +279,7 @@ function MediaFolderBrowserDashboardController($rootScope, $scope, $location, co currentUser = user; // check if the user start node is the dashboard - if(currentUser.startMediaId === -1) { + if (currentUser.startMediaIds.length === 0 || currentUser.startMediaIds.indexOf(-1) >= 0) { //get the system media listview contentTypeResource.getPropertyTypeScaffold(-96) @@ -305,7 +305,7 @@ function MediaFolderBrowserDashboardController($rootScope, $scope, $location, co } else { // redirect to start node - $location.path("/media/media/edit/" + currentUser.startMediaId); + $location.path("/media/media/edit/" + (currentUser.startMediaIds.length === 0 ? -1 : currentUser.startMediaIds[0])); } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index bb580a906d..57f7d06f80 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -4,7 +4,7 @@ angular.module("umbraco") if (!$scope.model.config.startNodeId) { userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaId; + $scope.model.config.startNodeId = userData.startMediaIds.length === 0 ? -1 : userData.startMediaIds[0]; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index e406f87aed..6ec9e74fa4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -28,7 +28,7 @@ currentTarget: currentTarget, onlyImages: true, showDetails: true, - startNodeId: userData.startMediaId, + startNodeId: userData.startMediaIds.length === 0 ? -1 : userData.startMediaIds[0], view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 6652c52cc0..d09c89c445 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -10,7 +10,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl if (!$scope.model.config.startNodeId) { userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaId; + $scope.model.config.startNodeId = userData.startMediaIds.length === 0 ? -1 : userData.startMediaIds[0]; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index bbb8024a53..45e8856132 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -250,7 +250,7 @@ angular.module("umbraco") onlyImages: true, showDetails: true, disableFolderSelect: true, - startNodeId: userData.startMediaId, + startNodeId: userData.startMediaIds.length === 0 ? -1 : userData.startMediaIds[0], view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index da5e0c3a3b..6682b22389 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -800,16 +800,16 @@ namespace Umbraco.Web.Editors } var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : (nodeId == Constants.System.RecycleBinContent) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinContent.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : user.HasPathAccess(contentItem); + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartContentIds, + Constants.System.RecycleBinContent) + : (nodeId == Constants.System.RecycleBinContent) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinContent.ToInvariantString(), + user.StartContentIds, + Constants.System.RecycleBinContent) + : user.HasPathAccess(contentItem); if (hasPathAccess == false) { diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 48da53e263..e16d6da67a 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -597,6 +597,36 @@ namespace Umbraco.Web.Editors return ExamineSearch(query, entityType, 200, 0, out total, searchFrom); } + private void AddExamineSearchFrom(string searchFrom, StringBuilder sb) + { + //if searchFrom is specified and it is greater than 0 + int mediaSearchFrom; + if (searchFrom != null && int.TryParse(searchFrom, out mediaSearchFrom) && mediaSearchFrom > 0) + { + sb.Append("+__Path: \\-1*\\,"); + sb.Append(mediaSearchFrom.ToString(CultureInfo.InvariantCulture)); + sb.Append("\\,* "); + } + } + + private void AddExamineUserStartNode(int[] startNodes, StringBuilder sb) + { + //make sure only what the user is configured to view is found + if (startNodes.Length > 0) + sb.Append("+("); + foreach (var startNode in startNodes) + { + if (startNode > 0) + { + sb.Append("__Path: \\-1*\\,"); + sb.Append(startNode.ToString(CultureInfo.InvariantCulture)); + sb.Append("\\,* "); + } + } + if (startNodes.Length > 0) + sb.Append(")"); + } + /// /// Searches for results based on the entity type /// @@ -636,34 +666,16 @@ namespace Umbraco.Web.Editors case UmbracoEntityTypes.Media: type = "media"; - var mediaSearchFrom = int.MinValue; - - if (Security.CurrentUser.StartMediaId > 0 || - //if searchFrom is specified and it is greater than 0 - (searchFrom != null && int.TryParse(searchFrom, out mediaSearchFrom) && mediaSearchFrom > 0)) - { - sb.Append("+__Path: \\-1*\\,"); - sb.Append(mediaSearchFrom > 0 - ? mediaSearchFrom.ToString(CultureInfo.InvariantCulture) - : Security.CurrentUser.StartMediaId.ToString(CultureInfo.InvariantCulture)); - sb.Append("\\,* "); - } + AddExamineSearchFrom(searchFrom, sb); + AddExamineUserStartNode(Security.CurrentUser.StartMediaIds, sb); + break; case UmbracoEntityTypes.Document: type = "content"; - var contentSearchFrom = int.MinValue; - - if (Security.CurrentUser.StartContentId > 0 || - //if searchFrom is specified and it is greater than 0 - (searchFrom != null && int.TryParse(searchFrom, out contentSearchFrom) && contentSearchFrom > 0)) - { - sb.Append("+__Path: \\-1*\\,"); - sb.Append(contentSearchFrom > 0 - ? contentSearchFrom.ToString(CultureInfo.InvariantCulture) - : Security.CurrentUser.StartContentId.ToString(CultureInfo.InvariantCulture)); - sb.Append("\\,* "); - } + AddExamineSearchFrom(searchFrom, sb); + AddExamineUserStartNode(Security.CurrentUser.StartContentIds, sb); + break; default: throw new NotSupportedException("The " + typeof(EntityController) + " currently does not support searching against object type " + entityType); diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 4980605374..4cac919b1b 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -884,16 +884,16 @@ namespace Umbraco.Web.Editors } var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : (nodeId == Constants.System.RecycleBinMedia) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinMedia.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : user.HasPathAccess(media); + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartMediaIds, + Constants.System.RecycleBinMedia) + : (nodeId == Constants.System.RecycleBinMedia) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinMedia.ToInvariantString(), + user.StartMediaIds, + Constants.System.RecycleBinMedia) + : user.HasPathAccess(media); return hasPathAccess; } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index c6ce2ecc81..73757f5427 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -98,6 +98,34 @@ namespace Umbraco.Web.Editors }; } + /// + /// Creates a new user + /// + /// + /// + public UserDisplay PostCreateUser(UserInvite userSave) + { + if (userSave == null) throw new ArgumentNullException("userSave"); + + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var existing = Services.UserService.GetByEmail(userSave.Email); + if (existing != null) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var user = Mapper.Map(userSave); + + Services.UserService.Save(user); + + return Mapper.Map(user); + } + /// /// Invites a user /// @@ -158,8 +186,31 @@ namespace Umbraco.Web.Editors if (found == null) throw new HttpResponseException(HttpStatusCode.NotFound); + var hasErrors = false; + + var existing = Services.UserService.GetByEmail(userSave.Email); + if (existing != null && existing.Id != (int)userSave.Id) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + hasErrors = true; + } + existing = Services.UserService.GetByUsername(userSave.Name); + if (existing != null && existing.Id != (int)userSave.Id) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + hasErrors = true; + } + + if (hasErrors) + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + //TODO: More validation, password changing logic, persisting - return Mapper.Map(found); + + var user = Mapper.Map(userSave); + + Services.UserService.Save(user); + + return Mapper.Map(user); } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs index 9b29e132af..3cdf1cc3b6 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs @@ -30,11 +30,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "remainingAuthSeconds")] public double SecondsUntilTimeout { get; set; } - [DataMember(Name = "startContentId")] - public int StartContentId { get; set; } + [DataMember(Name = "startContentIds")] + public int[] StartContentIds { get; set; } - [DataMember(Name = "startMediaId")] - public int StartMediaId { get; set; } + [DataMember(Name = "startMediaIds")] + public int[] StartMediaIds { get; set; } /// /// A list of sections the user is allowed to view. diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs index a0c2a8ef33..e4bbe7850b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs @@ -48,10 +48,9 @@ namespace Umbraco.Web.Models.ContentEditing /// /// Gets the available user groups (i.e. to populate a drop down) - /// The key is the Alias the value is the Name - the Alias is what is used in the UserGroup property and for persistence /// [DataMember(Name = "availableUserGroups")] - public IDictionary AvailableUserGroups { get; set; } + public IEnumerable AvailableUserGroups { get; set; } /// /// Gets the available cultures (i.e. to populate a drop down) @@ -60,26 +59,17 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "availableCultures")] public IDictionary AvailableCultures { get; set; } - //TODO: This will become StartContentIds as an array! - [DataMember(Name = "startContentId")] - public int StartContentId { get; set; } + [DataMember(Name = "startContentIds")] + public int[] StartContentIds { get; set; } - //TODO: This will become StartMediaIds as an array! - [DataMember(Name = "startMediaId")] - public int StartMediaId { get; set; } + [DataMember(Name = "startMediaIds")] + public int[] StartMediaIds { get; set; } - /// - /// A list of sections the user is allowed to view. - /// - [DataMember(Name = "allowedSections")] - public IEnumerable AllowedSections { get; set; } - - /// - /// Gets the available sections (i.e. to populate a drop down) - /// The key is the Alias the value is the Name - the Alias is what is used in the AllowedSections property and for persistence - /// - [DataMember(Name = "availableSections")] - public IDictionary AvailableSections { get; set; } + ///// + ///// A list of sections the user is allowed to view based on their current groups assigned + ///// + //[DataMember(Name = "allowedSections")] + //public IEnumerable AllowedSections { get; set; } /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs index 06e24b7d38..404b9925f9 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.Models.ContentEditing public UserGroupDisplay() { Notifications = new List(); - } + } /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. @@ -20,6 +20,13 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "sections")] public IEnumerable Sections { get; set; } + /// + /// Gets the available sections (i.e. to populate a drop down) + /// The key is the Alias the value is the Name - the Alias is what is used in the AllowedSections property and for persistence + /// + [DataMember(Name = "availableSections")] + public IEnumerable
AvailableSections { get; set; } + [DataMember(Name = "startNodeContent")] public int StartContentId { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs index 8bef19d62f..8f42dbf169 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs @@ -30,11 +30,11 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public IEnumerable UserGroups { get; set; } - [DataMember(Name = "startContentId")] - public int StartContentId { get; set; } + [DataMember(Name = "startContentIds")] + public int[] StartContentIds { get; set; } - [DataMember(Name = "startMediaId")] - public int StartMediaId { get; set; } + [DataMember(Name = "startMediaIds")] + public int[] StartMediaIds { get; set; } [DataMember(Name = "allowedSections")] public IEnumerable AllowedSections { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index aa4ad4b6e4..96d52f53fd 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -16,8 +16,29 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - config.CreateMap() + config.CreateMap() .ConstructUsing(invite => new User(invite.Name, invite.Email, invite.Email, Guid.NewGuid().ToString("N"))) + .ForMember(user => user.Id, expression => expression.Ignore()) + .ForMember(user => user.SessionTimeout, expression => expression.Ignore()) + .ForMember(user => user.StartContentIds, expression => expression.Ignore()) + .ForMember(user => user.StartMediaIds, expression => expression.Ignore()) + .ForMember(user => user.Language, expression => expression.Ignore()) + .ForMember(user => user.SecurityStamp, expression => expression.Ignore()) + .ForMember(user => user.ProviderUserKey, expression => expression.Ignore()) + .ForMember(user => user.Username, expression => expression.Ignore()) + .ForMember(user => user.RawPasswordValue, expression => expression.Ignore()) + .ForMember(user => user.PasswordQuestion, expression => expression.Ignore()) + .ForMember(user => user.RawPasswordAnswerValue, expression => expression.Ignore()) + .ForMember(user => user.Comments, expression => expression.Ignore()) + .ForMember(user => user.IsApproved, expression => expression.Ignore()) + .ForMember(user => user.IsLockedOut, expression => expression.Ignore()) + .ForMember(user => user.LastLoginDate, expression => expression.Ignore()) + .ForMember(user => user.LastPasswordChangeDate, expression => expression.Ignore()) + .ForMember(user => user.LastLockoutDate, expression => expression.Ignore()) + .ForMember(user => user.FailedPasswordAttempts, expression => expression.Ignore()) + .ForMember(user => user.DeletedDate, expression => expression.Ignore()) + .ForMember(user => user.CreateDate, expression => expression.Ignore()) + .ForMember(user => user.UpdateDate, expression => expression.Ignore()) .AfterMap((invite, user) => { foreach (var group in invite.UserGroups) @@ -27,8 +48,9 @@ namespace Umbraco.Web.Models.Mapping }); config.CreateMap() - .ForMember(detail => detail.Notifications, opt => opt.Ignore()) + .ForMember(detail => detail.AvailableSections, opt => opt.MapFrom(x => applicationContext.Services.SectionService.GetSections())) .ForMember(detail => detail.Sections, opt => opt.MapFrom(x => x.AllowedSections)) + .ForMember(detail => detail.Notifications, opt => opt.Ignore()) .ForMember(detail => detail.Udi, opt => opt.Ignore()) .ForMember(detail => detail.Trashed, opt => opt.Ignore()) .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) @@ -37,15 +59,12 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups)) - .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember( detail => detail.AvailableUserGroups, - opt => opt.MapFrom(user => applicationContext.Services.UserService.GetAllUserGroups().ToDictionary(x => x.Alias, x => x.Name))) - .ForMember( - detail => detail.AvailableSections, - opt => opt.MapFrom(user => applicationContext.Services.SectionService.GetSections().ToDictionary(x => x.Alias, x => x.Name))) + opt => opt.MapFrom(user => applicationContext.Services.UserService.GetAllUserGroups())) .ForMember( detail => detail.AvailableCultures, opt => opt.MapFrom(user => applicationContext.Services.TextService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName))) @@ -66,8 +85,8 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) - .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember( detail => detail.EmailHash, @@ -76,8 +95,8 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => user.Id)) - .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture)) .ForMember(detail => detail.AllowedSections, opt => opt.MapFrom(user => user.AllowedSections)) .ForMember( @@ -94,8 +113,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections)) .ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name)) .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Groups.ToArray())) - .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.StartContentIds)) + .ForMember(detail => detail.StartMediaNodes, opt => opt.MapFrom(user => user.StartMediaIds)) .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp)); diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 05407a93e5..d610ed6945 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -243,8 +243,6 @@ namespace Umbraco.Web.Security Name = membershipUser.UserName, RawPasswordValue = Guid.NewGuid().ToString("N"), //Need to set this to something - will not be used though Username = membershipUser.UserName, - StartContentId = -1, - StartMediaId = -1, IsLockedOut = false, IsApproved = true }; diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index a9f0bda805..076c7b093a 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -42,7 +42,7 @@ namespace Umbraco.Web.Trees { var node = base.CreateRootNode(queryStrings); //if the user's start node is not default, then ensure the root doesn't have a menu - if (Security.CurrentUser.StartContentId != Constants.System.Root) + if (Security.CurrentUser.StartContentIds.Length > 0 && Security.CurrentUser.StartContentIds.Contains(Constants.System.Root) == false) { node.MenuUrl = ""; } @@ -60,9 +60,9 @@ namespace Umbraco.Web.Trees get { return Services.ContentService.RecycleBinSmells(); } } - protected override int UserStartNode + protected override int[] UserStartNodes { - get { return Security.CurrentUser.StartContentId; } + get { return Security.CurrentUser.StartContentIds; } } /// @@ -121,7 +121,7 @@ namespace Umbraco.Web.Trees var menu = new MenuItemCollection(); //if the user's start node is not the root then ensure the root menu is empty/doesn't exist - if (Security.CurrentUser.StartContentId != Constants.System.Root) + if (Security.CurrentUser.StartContentIds.Length > 0 && Security.CurrentUser.StartContentIds.Contains(Constants.System.Root) == false) { return menu; } diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 97c5daed96..f8e8bec293 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -68,7 +68,7 @@ namespace Umbraco.Web.Trees /// /// Returns the user's start node for this tree /// - protected abstract int UserStartNode { get; } + protected abstract int[] UserStartNodes { get; } /// /// Gets the tree nodes for the given id @@ -105,7 +105,7 @@ namespace Umbraco.Web.Trees // Therefore, in the latter case, we want to change the id to -1 since we want to render the current user's root node // and the GetChildEntities method will take care of rendering the correct root node. // If it is in dialog mode, then we don't need to change anything and the children will just render as per normal. - if (IsDialog(queryStrings) == false && UserStartNode != Constants.System.Root) + if (IsDialog(queryStrings) == false && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) { id = Constants.System.Root.ToString(CultureInfo.InvariantCulture); } @@ -139,17 +139,11 @@ namespace Umbraco.Web.Trees //if a request is made for the root node data but the user's start node is not the default, then - // we need to return their start node data - if (iid == Constants.System.Root && UserStartNode != Constants.System.Root) + // we need to return their start nodes + if (iid == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) { - //just return their single start node, it will show up under the 'Content' label - var startNode = Services.EntityService.Get(UserStartNode, UmbracoObjectType); - if (startNode != null) - return new[] { startNode }; - else - { - throw new EntityNotFoundException(UserStartNode, "User's start content node could not be found"); - } + var nodes = Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes); + return nodes; } return Services.EntityService.GetChildren(iid, UmbracoObjectType).ToArray(); @@ -175,7 +169,7 @@ namespace Umbraco.Web.Trees protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { //check if we're rendering the root - if (id == Constants.System.Root.ToInvariantString() && UserStartNode == Constants.System.Root) + if (id == Constants.System.Root.ToInvariantString() && (UserStartNodes.Length == 0 || UserStartNodes.Contains(Constants.System.Root))) { var altStartId = string.Empty; diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index ed72857a40..068d6641b0 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -35,7 +35,7 @@ namespace Umbraco.Web.Trees { var node = base.CreateRootNode(queryStrings); //if the user's start node is not default, then ensure the root doesn't have a menu - if (Security.CurrentUser.StartMediaId != Constants.System.Root) + if (Security.CurrentUser.StartMediaIds.Length > 0 && Security.CurrentUser.StartMediaIds.Contains(Constants.System.Root) == false) { node.MenuUrl = ""; } @@ -53,9 +53,9 @@ namespace Umbraco.Web.Trees get { return Services.MediaService.RecycleBinSmells(); } } - protected override int UserStartNode + protected override int[] UserStartNodes { - get { return Security.CurrentUser.StartMediaId; } + get { return Security.CurrentUser.StartMediaIds; } } /// @@ -100,7 +100,7 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { //if the user's start node is not the root then ensure the root menu is empty/doesn't exist - if (Security.CurrentUser.StartMediaId != Constants.System.Root) + if (Security.CurrentUser.StartMediaIds.Length > 0 && Security.CurrentUser.StartMediaIds.Contains(Constants.System.Root) == false) { return menu; } diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs index 3feb3b682a..3ae70ac5e8 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs @@ -44,9 +44,9 @@ namespace Umbraco.Web.WebApi.Filters FilterBasedOnPermissions(items, user, ApplicationContext.Current.Services.UserService); } - protected override int GetUserStartNode(IUser user) + protected override int[] GetUserStartNodes(IUser user) { - return user.StartContentId; + return user.StartContentIds; } protected override int RecycleBinId diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 36170627d9..72cea5c2d0 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -39,9 +39,9 @@ namespace Umbraco.Web.WebApi.Filters get { return true; } } - protected virtual int GetUserStartNode(IUser user) + protected virtual int[] GetUserStartNodes(IUser user) { - return user.StartMediaId; + return user.StartMediaIds; } protected virtual int RecycleBinId @@ -85,8 +85,8 @@ namespace Umbraco.Web.WebApi.Filters var toRemove = new List(); foreach (dynamic item in items) { - var hasPathAccess = (item != null && UserExtensions.HasPathAccess(item.Path, GetUserStartNode(user), RecycleBinId)); - if (!hasPathAccess) + var hasPathAccess = (item != null && UserExtensions.HasPathAccess(item.Path, GetUserStartNodes(user), RecycleBinId)); + if (hasPathAccess == false) { toRemove.Add(item); } diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index a49c086be3..eb43857b45 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -294,8 +294,8 @@ namespace umbraco.BasePages RealName = u.Name, //currently we only have one user type! Roles = u.GetGroups(), - StartContentNode = u.StartNodeId, - StartMediaNode = u.StartMediaId, + StartContentNodes = u.UserEntity.StartContentIds, + StartMediaNodes = u.UserEntity.StartMediaIds, Username = u.LoginName, Culture = ui.Culture(u) diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 3a425ddef3..94d4802e48 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -707,39 +707,38 @@ namespace umbraco.BusinessLogic UserEntity.IsApproved = value == false; } } - - /// - /// - /// Gets or sets the start content node id. - /// - /// The start node id. + + [Obsolete("This should not be used, it will return invalid data because a user can have multiple start nodes, this will only return the first")] public int StartNodeId { get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return UserEntity.StartContentId; + return UserEntity.StartContentIds == null ? -1 : UserEntity.StartContentIds[0]; } set { - UserEntity.StartContentId = value; + if (UserEntity.StartContentIds == null) + UserEntity.StartContentIds = new int[] {value}; + else if (UserEntity.StartContentIds.Contains(value) == false) + UserEntity.StartContentIds = UserEntity.StartContentIds.Concat(new[] {value}).ToArray(); } } - /// - /// Gets or sets the start media id. - /// - /// The start media id. + [Obsolete("This should not be used, it will return invalid data because a user can have multiple start nodes, this will only return the first")] public int StartMediaId { get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return UserEntity.StartMediaId; + return UserEntity.StartMediaIds == null ? -1 : UserEntity.StartMediaIds[0]; } set { - UserEntity.StartMediaId = value; + if (UserEntity.StartMediaIds == null) + UserEntity.StartMediaIds = new int[] { value }; + else if (UserEntity.StartMediaIds.Contains(value) == false) + UserEntity.StartMediaIds = UserEntity.StartMediaIds.Concat(new[] { value }).ToArray(); } }