diff --git a/.gitignore b/.gitignore index 5390da67dd..36834898be 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,9 @@ src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.dev.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/canvasdesigner.*.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/navigation.controller.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.controller.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/utilities.js src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/ src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.js diff --git a/src/Umbraco.Configuration/Models/GlobalSettings.cs b/src/Umbraco.Configuration/Models/GlobalSettings.cs index ed1b4e58a2..68d293d104 100644 --- a/src/Umbraco.Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Configuration/Models/GlobalSettings.cs @@ -12,7 +12,7 @@ namespace Umbraco.Configuration.Models /// internal class GlobalSettings : IGlobalSettings { - public const string Prefix = Constants.Configuration.ConfigPrefix + "Global:"; + private const string Prefix = Constants.Configuration.ConfigGlobalPrefix; internal const string StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma! diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index 5124145a56..86a02affb6 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -12,6 +12,7 @@ /// public const string ConfigPrefix = "Umbraco:CMS:"; public const string ConfigSecurityPrefix = ConfigPrefix+"Security:"; + public const string ConfigGlobalPrefix = ConfigPrefix + "Global:"; public const string ConfigModelsBuilderPrefix = ConfigPrefix+"ModelsBuilder:"; public const string ConfigRuntimeMinification = ConfigPrefix+"RuntimeMinification"; public const string ConfigRuntimeMinificationVersion = ConfigRuntimeMinification+":Version"; diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index ae5741f0ef..b90a741f91 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -25,8 +25,10 @@ public const string UnknownUserName = "SYTEM"; public const string AdminGroupAlias = "admin"; + public const string EditorGroupAlias = "editor"; public const string SensitiveDataGroupAlias = "sensitiveData"; public const string TranslatorGroupAlias = "translator"; + public const string WriterGroupAlias = "writer"; public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs index 7c207c23c0..79c7f824fc 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs @@ -111,7 +111,7 @@ namespace Umbraco.Web.Models.PublishedContent var propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); noValueProperty = content.GetProperty(alias); } @@ -168,7 +168,7 @@ namespace Umbraco.Web.Models.PublishedContent { culture = null; segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); } property = content?.GetProperty(alias); diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index b83002fdce..424fc7c4a8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -23,5 +23,12 @@ /// Gets the segment. /// public string Segment { get; } + + /// + /// Gets the segment for the content item + /// + /// + /// + public virtual string GetSegment(int contentId) => Segment; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index 6710d79cc6..a387ea87b8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -3,13 +3,32 @@ public static class VariationContextAccessorExtensions { public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); + + public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); + + private static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, ref string culture, ref string segment) { if (culture != null && segment != null) return; // use context values var publishedVariationContext = variationContextAccessor?.VariationContext; if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; - if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : ""; + + if (segment == null) + { + if (variations.VariesBySegment()) + { + segment = contentId == null + ? publishedVariationContext?.Segment + : publishedVariationContext?.GetSegment(contentId.Value); + } + else + { + segment = ""; + } + } } } } diff --git a/src/Umbraco.Core/Services/IMembershipRoleService.cs b/src/Umbraco.Core/Services/IMembershipRoleService.cs index 30531b5031..7389bb9799 100644 --- a/src/Umbraco.Core/Services/IMembershipRoleService.cs +++ b/src/Umbraco.Core/Services/IMembershipRoleService.cs @@ -11,6 +11,9 @@ namespace Umbraco.Core.Services IEnumerable GetAllRoles(); IEnumerable GetAllRoles(int memberId); IEnumerable GetAllRoles(string username); + IEnumerable GetAllRolesIds(); + IEnumerable GetAllRolesIds(int memberId); + IEnumerable GetAllRolesIds(string username); IEnumerable GetMembersInRole(string roleName); IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); bool DeleteRole(string roleName, bool throwIfBeingUsed); diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index cf63950bab..67b0b49a45 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; -using Umbraco.Composing; using Umbraco.Core.IO; using Umbraco.Core.Strings; @@ -72,6 +71,23 @@ namespace Umbraco.Core } return fileName; + + + } + + /// + /// Determines the extension of the path or URL + /// + /// + /// Extension of the file + public static string GetFileExtension(this string file) + { + //Find any characters between the last . and the start of a query string or the end of the string + const string pattern = @"(?\.[^\.\?]+)(\?.*|$)"; + var match = Regex.Match(file, pattern); + return match.Success + ? match.Groups["extension"].Value + : string.Empty; } /// diff --git a/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index 9d36b60d83..dcd539c4a2 100644 --- a/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods public EmailNotificationMethod(ILocalizedTextService textService, IRuntimeState runtimeState, ILogger logger, IGlobalSettings globalSettings, IHealthChecksSettings healthChecksSettings, IContentSettings contentSettings) : base(healthChecksSettings) { - var recipientEmail = Settings["recipientEmail"]?.Value; + var recipientEmail = Settings?["recipientEmail"]?.Value; if (string.IsNullOrWhiteSpace(recipientEmail)) { Enabled = false; diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index b4328c973d..36f1a30b20 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -175,8 +175,8 @@ namespace Umbraco.Core.Migrations.Install private void CreateUserGroupData() { _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); - _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); - _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.WriterGroupAlias, Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.EditorGroupAlias, Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index b8a5755212..cfd67a5a02 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -169,6 +169,7 @@ namespace Umbraco.Core.Migrations.Upgrade .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); To("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}"); + To("{DF470D86-E5CA-42AC-9780-9D28070E25F9}"); // finish migrating from v7 - recreate all keys and indexes To("{3F9764F5-73D0-4D45-8804-1240A66E43A2}"); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs new file mode 100644 index 0000000000..d44e637a2c --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddPackagesSectionAccess : MigrationBase + { + public AddPackagesSectionAccess(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // Any user group which had access to the Developer section should have access to Packages + Database.Execute($@" + insert into {Constants.DatabaseSchema.Tables.UserGroup2App} + select userGroupId, '{Constants.Applications.Packages}' + from {Constants.DatabaseSchema.Tables.UserGroup2App} + where app='developer'"); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/StylesheetRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/StylesheetRepository.cs index c1fb5c3159..f432d6959e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/StylesheetRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/StylesheetRepository.cs @@ -32,8 +32,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement path = path.EnsureEndsWith(".css"); - if (FileSystem.FileExists(path) == false) + // if the css directory is changed, references to the old path can still exist (ie in RTE config) + // these old references will throw an error, which breaks the RTE + // try-catch here makes the request fail silently, and allows RTE to load correctly + try + { + if (FileSystem.FileExists(path) == false) + return null; + } catch + { return null; + } // content will be lazy-loaded when required var created = FileSystem.GetCreated(path).UtcDateTime; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs index b833a60c2d..b64c52648a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs @@ -58,7 +58,7 @@ namespace Umbraco.Web.PropertyEditors internal static bool IsValidFileExtension(string fileName, IContentSettings contentSettings) { if (fileName.IndexOf('.') <= 0) return false; - var extension = new FileInfo(fileName).Extension.TrimStart("."); + var extension = fileName.GetFileExtension().TrimStart("."); return contentSettings.IsFileAllowedForUpload(extension); } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs index fc0dd8a554..7ee6065210 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs @@ -958,6 +958,35 @@ namespace Umbraco.Core.Services.Implement } } + public IEnumerable GetAllRolesIds() + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.MemberTree); + return _memberGroupRepository.GetMany().Select(x => x.Id).Distinct(); + } + } + + public IEnumerable GetAllRolesIds(int memberId) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.MemberTree); + var result = _memberGroupRepository.GetMemberGroupsForMember(memberId); + return result.Select(x => x.Id).Distinct(); + } + } + + public IEnumerable GetAllRolesIds(string username) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.MemberTree); + var result = _memberGroupRepository.GetMemberGroupsForMember(username); + return result.Select(x => x.Id).Distinct(); + } + } + public IEnumerable GetMembersInRole(string roleName) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs index e23f7539c2..24f1af5843 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs @@ -368,7 +368,7 @@ namespace Umbraco.Core.Services.Implement /// public string GetDefaultMemberType() { - return "writer"; + return Constants.Security.WriterGroupAlias; } /// diff --git a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs index dd5a76837e..b4388b7b0b 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs @@ -355,6 +355,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private static IEnumerable GetByXPath(XPathNodeIterator iterator) { + iterator = iterator.Clone(); while (iterator.MoveNext()) { var xnav = iterator.Current as NavigableNavigator; diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 0254b815c1..86023bb302 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -90,7 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // determines whether a property has value public override bool HasValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); var value = GetSourceValue(culture, segment); var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); @@ -194,7 +194,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetSourceValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); if (culture == "" && segment == "") return _sourceValue; @@ -208,7 +208,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object value; lock (_locko) @@ -229,7 +229,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetXPathValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); lock (_locko) { diff --git a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs index e1436ac1fe..fcd9691e1f 100644 --- a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs +++ b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs @@ -9,7 +9,6 @@ namespace Umbraco.Tests.Common.Builders _parentBuilder = parentBuilder; } - public TParent Done() { return _parentBuilder; diff --git a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs index 7789090d16..3e0b9b96a9 100644 --- a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Common.Builders public override IConfigurationEditor Build() { - var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); return new ConfigurationEditor() { diff --git a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs index 20dc1bab81..de7b8d9a97 100644 --- a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs @@ -8,10 +8,16 @@ namespace Umbraco.Tests.Common.Builders : BuilderBase, IWithIdBuilder, IWithKeyBuilder, + IWithCreatorIdBuilder, IWithCreateDateBuilder, IWithUpdateDateBuilder, IWithDeleteDateBuilder, - IWithNameBuilder + IWithNameBuilder, + IWithParentIdBuilder, + IWithTrashedBuilder, + IWithLevelBuilder, + IWithPathBuilder, + IWithSortOrderBuilder { private readonly DataEditorBuilder _dataEditorBuilder; private int? _id; @@ -34,54 +40,18 @@ namespace Umbraco.Tests.Common.Builders _dataEditorBuilder = new DataEditorBuilder(this); } - public DataTypeBuilder WithParentId(int parentId) - { - _parentId = parentId; - return this; - } - - public DataTypeBuilder WithTrashed(bool trashed) - { - _trashed = trashed; - return this; - } - // public DataTypeBuilder WithConfiguration(object configuration) // { // _configuration = configuration; // return this; // } - public DataTypeBuilder WithLevel(int level) - { - _level = level; - return this; - } - - public DataTypeBuilder WithPath(string path) - { - _path = path; - return this; - } - - public DataTypeBuilder WithCreatorId(int creatorId) - { - _creatorId = creatorId; - return this; - } - public DataTypeBuilder WithDatabaseType(ValueStorageType databaseType) { _databaseType = databaseType; return this; } - public DataTypeBuilder WithSortOrder(int sortOrder) - { - _sortOrder = sortOrder; - return this; - } - public DataEditorBuilder AddEditor() { return _dataEditorBuilder; @@ -133,6 +103,12 @@ namespace Umbraco.Tests.Common.Builders set => _key = value; } + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + DateTime? IWithCreateDateBuilder.CreateDate { get => _createDate; @@ -156,5 +132,35 @@ namespace Umbraco.Tests.Common.Builders get => _name; set => _name = value; } + + int? IWithParentIdBuilder.ParentId + { + get => _parentId; + set => _parentId = value; + } + + bool? IWithTrashedBuilder.Trashed + { + get => _trashed; + set => _trashed = value; + } + + int? IWithLevelBuilder.Level + { + get => _level; + set => _level = value; + } + + string IWithPathBuilder.Path + { + get => _path; + set => _path = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } } } diff --git a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs index 37fb7c5b07..d2f9d4bf02 100644 --- a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs @@ -21,7 +21,6 @@ namespace Umbraco.Tests.Common.Builders private DateTime? _updateDate; private string _value; - public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) : base(parentBuilder) { _languageBuilder = new LanguageBuilder(this); diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs index c8f3f80bf1..cce3b88470 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs @@ -12,6 +12,13 @@ namespace Umbraco.Tests.Common.Builders.Extensions return builder; } + public static T WithCreatorId(this T builder, int creatorId) + where T : IWithCreatorIdBuilder + { + builder.CreatorId = creatorId; + return builder; + } + public static T WithCreateDate(this T builder, DateTime createDate) where T : IWithCreateDateBuilder { @@ -46,5 +53,61 @@ namespace Umbraco.Tests.Common.Builders.Extensions builder.Key = key; return builder; } + + public static T WithParentId(this T builder, int parentId) + where T : IWithParentIdBuilder + { + builder.ParentId = parentId; + return builder; + } + + public static T WithTrashed(this T builder, bool trashed) + where T : IWithTrashedBuilder + { + builder.Trashed = trashed; + return builder; + } + + public static T WithLevel(this T builder, int level) + where T : IWithLevelBuilder + { + builder.Level = level; + return builder; + } + + public static T WithPath(this T builder, string path) + where T : IWithPathBuilder + { + builder.Path = path; + return builder; + } + + public static T WithSortOrder(this T builder, int sortOrder) + where T : IWithSortOrderBuilder + { + builder.SortOrder = sortOrder; + return builder; + } + + public static T WithDescription(this T builder, string description) + where T : IWithDescriptionBuilder + { + builder.Description = description; + return builder; + } + + public static T WithIcon(this T builder, string icon) + where T : IWithIconBuilder + { + builder.Icon = icon; + return builder; + } + + public static T WithThumbnail(this T builder, string thumbnail) + where T : IWithThumbnailBuilder + { + builder.Thumbnail = thumbnail; + return builder; + } } } diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..b426cabaa6 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Tests.Common.Builders.Extensions +{ + public static class StringExtensions + { + public static string ToCamelCase(this string s) + { + if (string.IsNullOrWhiteSpace(s)) + { + return string.Empty; + } + + if (s.Length == 1) + { + return s.ToLowerInvariant(); + } + + return char.ToLowerInvariant(s[0]) + s.Substring(1); + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs new file mode 100644 index 0000000000..c7e176e9b0 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Umbraco.Tests.Common.Builders +{ + public class GenericCollectionBuilder + : ChildBuilderBase> + { + private readonly IList _collection; + + public GenericCollectionBuilder(TBuilder parentBuilder) : base(parentBuilder) + { + _collection = new List(); + } + + public override IEnumerable Build() + { + return _collection; + } + + public GenericCollectionBuilder WithValue(T value) + { + _collection.Add(value); + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs new file mode 100644 index 0000000000..8f6aedcf43 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Umbraco.Tests.Common.Builders +{ + public class GenericDictionaryBuilder + : ChildBuilderBase> + { + private readonly IDictionary _dictionary; + + public GenericDictionaryBuilder(TBuilder parentBuilder) : base(parentBuilder) + { + _dictionary = new Dictionary(); + } + + public override IDictionary Build() + { + return _dictionary; + } + + public GenericDictionaryBuilder WithKeyValue(TKey key, TValue value) + { + _dictionary.Add(key, value); + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs new file mode 100644 index 0000000000..48d41e7a84 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithApprovedBuilder + { + bool? Approved { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs new file mode 100644 index 0000000000..ae7712cf9e --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithCreatorIdBuilder + { + int? CreatorId { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs new file mode 100644 index 0000000000..1a155073b3 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithDescriptionBuilder + { + string Description { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs new file mode 100644 index 0000000000..5de5224e18 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithIconBuilder + { + string Icon { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs new file mode 100644 index 0000000000..dc6ee239ab --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithLevelBuilder + { + int? Level { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs new file mode 100644 index 0000000000..33d13b7ef1 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithParentIdBuilder + { + int? ParentId { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs new file mode 100644 index 0000000000..ed632c4e7d --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithPathBuilder + { + string Path { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs new file mode 100644 index 0000000000..3202c243fb --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithSortOrderBuilder + { + int? SortOrder { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs new file mode 100644 index 0000000000..ce5b10e274 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithThumbnailBuilder + { + string Thumbnail { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs new file mode 100644 index 0000000000..119e6a6e52 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithTrashedBuilder + { + bool? Trashed { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs new file mode 100644 index 0000000000..cef4a35524 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs @@ -0,0 +1,280 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class MemberBuilder + : BuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreatorIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithNameBuilder, + IWithTrashedBuilder, + IWithLevelBuilder, + IWithPathBuilder, + IWithSortOrderBuilder + { + private MemberTypeBuilder _memberTypeBuilder; + private GenericCollectionBuilder _memberGroupsBuilder; + private GenericDictionaryBuilder _additionalDataBuilder; + private GenericDictionaryBuilder _propertyDataBuilder; + + private int? _id; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private string _name; + private int? _creatorId; + private string _username; + private string _rawPasswordValue; + private string _email; + private int? _failedPasswordAttempts; + private int? _level; + private string _path; + private bool? _isApproved; + private bool? _isLockedOut; + private DateTime? _lastLockoutDate; + private DateTime? _lastLoginDate; + private DateTime? _lastPasswordChangeDate; + private int? _sortOrder; + private bool? _trashed; + private int? _propertyIdsIncrementingFrom; + + public MemberBuilder WithUserName(string username) + { + _username = username; + return this; + } + + public MemberBuilder WithEmail(string email) + { + _email = email; + return this; + } + + public MemberBuilder WithRawPasswordValue(string rawPasswordValue) + { + _rawPasswordValue = rawPasswordValue; + return this; + } + + public MemberBuilder WithFailedPasswordAttempts(int failedPasswordAttempts) + { + _failedPasswordAttempts = failedPasswordAttempts; + return this; + } + + public MemberBuilder WithIsApproved(bool isApproved) + { + _isApproved = isApproved; + return this; + } + + public MemberBuilder WithIsLockedOut(bool isLockedOut) + { + _isLockedOut = isLockedOut; + return this; + } + + public MemberBuilder WithLastLockoutDate(DateTime lastLockoutDate) + { + _lastLockoutDate = lastLockoutDate; + return this; + } + + public MemberBuilder WithLastLoginDate(DateTime lastLoginDate) + { + _lastLoginDate = lastLoginDate; + return this; + } + + public MemberBuilder WithLastPasswordChangeDate(DateTime lastPasswordChangeDate) + { + _lastPasswordChangeDate = lastPasswordChangeDate; + return this; + } + + public MemberBuilder WithPropertyIdsIncrementingFrom(int propertyIdsIncrementingFrom) + { + _propertyIdsIncrementingFrom = propertyIdsIncrementingFrom; + return this; + } + + public MemberTypeBuilder AddMemberType() + { + var builder = new MemberTypeBuilder(this); + _memberTypeBuilder = builder; + return builder; + } + + public GenericCollectionBuilder AddMemberGroups() + { + var builder = new GenericCollectionBuilder(this); + _memberGroupsBuilder = builder; + return builder; + } + + public GenericDictionaryBuilder AddAdditionalData() + { + var builder = new GenericDictionaryBuilder(this); + _additionalDataBuilder = builder; + return builder; + } + + public GenericDictionaryBuilder AddPropertyData() + { + var builder = new GenericDictionaryBuilder(this); + _propertyDataBuilder = builder; + return builder; + } + + public override Member Build() + { + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var name = _name ?? Guid.NewGuid().ToString(); + var creatorId = _creatorId ?? 1; + var username = _username ?? string.Empty; + var email = _email ?? string.Empty; + var rawPasswordValue = _rawPasswordValue ?? string.Empty; + var failedPasswordAttempts = _failedPasswordAttempts ?? 0; + var level = _level ?? 1; + var path = _path ?? "-1"; + var isApproved = _isApproved ?? false; + var isLockedOut = _isLockedOut ?? false; + var lastLockoutDate = _lastLockoutDate ?? DateTime.Now; + var lastLoginDate = _lastLoginDate ?? DateTime.Now; + var lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; + var sortOrder = _sortOrder ?? 0; + var trashed = _trashed ?? false; + + if (_memberTypeBuilder == null) + { + throw new InvalidOperationException("A member cannot be constructed without providing a member type (use AddMemberType)."); + } + + var memberType = _memberTypeBuilder.Build(); + + var member = new Member(name, email, username, rawPasswordValue, memberType) + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + CreatorId = creatorId, + Level = level, + Path = path, + SortOrder = sortOrder, + Trashed = trashed, + }; + + if (_propertyIdsIncrementingFrom.HasValue) + { + var i = _propertyIdsIncrementingFrom.Value; + foreach (var property in member.Properties) + { + property.Id = ++i; + } + } + + member.FailedPasswordAttempts = failedPasswordAttempts; + member.IsApproved = isApproved; + member.IsLockedOut = isLockedOut; + member.LastLockoutDate = lastLockoutDate; + member.LastLoginDate = lastLoginDate; + member.LastPasswordChangeDate = lastPasswordChangeDate; + + if (_memberGroupsBuilder != null) + { + member.Groups = _memberGroupsBuilder.Build(); + } + + if (_additionalDataBuilder != null) + { + var additionalData = _additionalDataBuilder.Build(); + foreach (var kvp in additionalData) + { + member.AdditionalData.Add(kvp.Key, kvp.Value); + } + } + + if (_propertyDataBuilder != null) + { + var propertyData = _propertyDataBuilder.Build(); + foreach (var kvp in propertyData) + { + member.SetValue(kvp.Key, kvp.Value); + } + + member.ResetDirtyProperties(false); + } + + return member; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + bool? IWithTrashedBuilder.Trashed + { + get => _trashed; + set => _trashed = value; + } + + int? IWithLevelBuilder.Level + { + get => _level; + set => _level = value; + } + + string IWithPathBuilder.Path + { + get => _path; + set => _path = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs new file mode 100644 index 0000000000..bfd7f30a14 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs @@ -0,0 +1,99 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class MemberGroupBuilder + : BuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreatorIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithNameBuilder + { + private GenericDictionaryBuilder _additionalDataBuilder; + + private int? _id; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private string _name; + private int? _creatorId; + + public GenericDictionaryBuilder AddAdditionalData() + { + var builder = new GenericDictionaryBuilder(this); + _additionalDataBuilder = builder; + return builder; + } + + public override MemberGroup Build() + { + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var name = _name ?? Guid.NewGuid().ToString(); + var creatorId = _creatorId ?? 1; + + var memberGroup = new MemberGroup + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + Name = name, + CreatorId = creatorId, + }; + + if (_additionalDataBuilder != null) + { + var additionalData = _additionalDataBuilder.Build(); + foreach (var kvp in additionalData) + { + memberGroup.AdditionalData.Add(kvp.Key, kvp.Value); + } + } + + return memberGroup; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs new file mode 100644 index 0000000000..b01b8a1680 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Strings; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class MemberTypeBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithAliasBuilder, + IWithNameBuilder, + IWithParentIdBuilder, + IWithSortOrderBuilder, + IWithCreatorIdBuilder, + IWithDescriptionBuilder, + IWithIconBuilder, + IWithThumbnailBuilder, + IWithTrashedBuilder + { + private readonly List _propertyGroupBuilders = new List(); + + private int? _id; + private string _alias; + private string _name; + private int? _parentId; + private int? _sortOrder; + private int? _creatorId; + private string _description; + private string _icon; + private string _thumbnail; + private bool? _trashed; + + public MemberTypeBuilder(MemberBuilder parentBuilder) : base(parentBuilder) + { + } + + public MemberTypeBuilder WithMembershipPropertyGroup() + { + var builder = new PropertyGroupBuilder(this) + .WithName(Constants.Conventions.Member.StandardPropertiesGroupName) + .WithSortOrder(1) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextArea) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias(Constants.Conventions.Member.Comments) + .WithName(Constants.Conventions.Member.CommentsLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Boolean) + .WithValueStorageType(ValueStorageType.Integer) + .WithAlias(Constants.Conventions.Member.IsApproved) + .WithName(Constants.Conventions.Member.IsApprovedLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Boolean) + .WithValueStorageType(ValueStorageType.Integer) + .WithAlias(Constants.Conventions.Member.IsLockedOut) + .WithName(Constants.Conventions.Member.IsLockedOutLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Date) + .WithAlias(Constants.Conventions.Member.LastLoginDate) + .WithName(Constants.Conventions.Member.LastLoginDateLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Date) + .WithAlias(Constants.Conventions.Member.LastPasswordChangeDate) + .WithName(Constants.Conventions.Member.LastPasswordChangeDateLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Date) + .WithAlias(Constants.Conventions.Member.LastLockoutDate) + .WithName(Constants.Conventions.Member.LastLockoutDateLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Integer) + .WithAlias(Constants.Conventions.Member.FailedPasswordAttempts) + .WithName(Constants.Conventions.Member.FailedPasswordAttemptsLabel) + .Done(); + _propertyGroupBuilders.Add(builder); + return this; + } + + public PropertyGroupBuilder AddPropertyGroup() + { + var builder = new PropertyGroupBuilder(this); + _propertyGroupBuilders.Add(builder); + return builder; + } + + public override MemberType Build() + { + var id = _id ?? 1; + var name = _name ?? Guid.NewGuid().ToString(); + var alias = _alias ?? name.ToCamelCase(); + var parentId = _parentId ?? -1; + var sortOrder = _sortOrder ?? 0; + var description = _description ?? string.Empty; + var icon = _icon ?? string.Empty; + var thumbnail = _thumbnail ?? string.Empty; + var creatorId = _creatorId ?? 0; + var trashed = _trashed ?? false; + + var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); + + var memberType = new MemberType(shortStringHelper, parentId) + { + Id = id, + Alias = alias, + Name = name, + SortOrder = sortOrder, + Description = description, + Icon = icon, + Thumbnail = thumbnail, + CreatorId = creatorId, + Trashed = trashed, + }; + + foreach (var propertyGroup in _propertyGroupBuilders.Select(x => x.Build())) + { + memberType.PropertyGroups.Add(propertyGroup); + } + + memberType.ResetDirtyProperties(false); + + return memberType; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + int? IWithParentIdBuilder.ParentId + { + get => _parentId; + set => _parentId = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + + string IWithDescriptionBuilder.Description + { + get => _description; + set => _description = value; + } + + string IWithIconBuilder.Icon + { + get => _icon; + set => _icon = value; + } + + string IWithThumbnailBuilder.Thumbnail + { + get => _thumbnail; + set => _thumbnail = value; + } + + bool? IWithTrashedBuilder.Trashed + { + get => _trashed; + set => _trashed = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs new file mode 100644 index 0000000000..5f6fe12dff --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class PropertyGroupBuilder + : ChildBuilderBase, // TODO: likely want to generalise this, so can use for document and media types too. + IWithNameBuilder, + IWithSortOrderBuilder + { + private readonly List _propertyTypeBuilders = new List(); + + private string _name; + private int? _sortOrder; + + public PropertyGroupBuilder(MemberTypeBuilder parentBuilder) : base(parentBuilder) + { + } + + public PropertyTypeBuilder AddPropertyType() + { + var builder = new PropertyTypeBuilder(this); + _propertyTypeBuilders.Add(builder); + return builder; + } + + public override PropertyGroup Build() + { + var name = _name ?? Guid.NewGuid().ToString(); + var sortOrder = _sortOrder ?? 0; + + var properties = new PropertyTypeCollection(false); + foreach (var propertyType in _propertyTypeBuilders.Select(x => x.Build())) + { + properties.Add(propertyType); + } + + return new PropertyGroup(properties) + { + Name = name, + SortOrder = sortOrder, + }; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs new file mode 100644 index 0000000000..955e2fca4c --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs @@ -0,0 +1,93 @@ +using System; +using Moq; +using Umbraco.Core.Models; +using Umbraco.Core.Strings; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class PropertyTypeBuilder + : ChildBuilderBase, + IWithAliasBuilder, + IWithNameBuilder, + IWithSortOrderBuilder, + IWithDescriptionBuilder + { + private string _propertyEditorAlias; + private ValueStorageType? _valueStorageType; + private string _alias; + private string _name; + private int? _sortOrder; + private string _description; + private int? _dataTypeId; + + public PropertyTypeBuilder(PropertyGroupBuilder parentBuilder) : base(parentBuilder) + { + } + + public PropertyTypeBuilder WithPropertyEditorAlias(string propertyEditorAlias) + { + _propertyEditorAlias = propertyEditorAlias; + return this; + } + + public PropertyTypeBuilder WithValueStorageType(ValueStorageType valueStorageType) + { + _valueStorageType = valueStorageType; + return this; + } + + public PropertyTypeBuilder WithDataTypeId(int dataTypeId) + { + _dataTypeId = dataTypeId; + return this; + } + + public override PropertyType Build() + { + var propertyEditorAlias = _propertyEditorAlias ?? Guid.NewGuid().ToString().ToCamelCase(); + var valueStorageType = _valueStorageType ?? ValueStorageType.Ntext; + var name = _name ?? Guid.NewGuid().ToString(); + var alias = _alias ?? name.ToCamelCase(); + var sortOrder = _sortOrder ?? 0; + var dataTypeId = _dataTypeId ?? 0; + var description = _description ?? string.Empty; + + var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); + + return new PropertyType(shortStringHelper, propertyEditorAlias, valueStorageType) + { + Alias = alias, + Name = name, + SortOrder = sortOrder, + DataTypeId = dataTypeId, + Description = description, + }; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + + string IWithDescriptionBuilder.Description + { + get => _description; + set => _description = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs index 07ab8ef3f7..cb24e0e8dc 100644 --- a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs @@ -1,16 +1,22 @@ -using System; -using System.Linq; -using Moq; -using Umbraco.Core; -using Umbraco.Core.Configuration; +using Umbraco.Configuration.Models; using Umbraco.Core.Models.Membership; using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { - public class UserBuilder - : BuilderBase, - IWithIdBuilder + + public class UserBuilder : UserBuilder + { + public UserBuilder() : base(null) + { + } + } + + public class UserBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithNameBuilder, + IWithApprovedBuilder { private int? _id; private string _language; @@ -20,63 +26,57 @@ namespace Umbraco.Tests.Common.Builders private bool? _isLockedOut; private string _email; private string _username; - private string _defaultLang; private string _suffix = string.Empty; + private string _defaultLang; - public UserBuilder WithDefaultUILanguage(string defaultLang) + + public UserBuilder(TParent parentBuilder) : base(parentBuilder) + { + + } + + public UserBuilder WithDefaultUILanguage(string defaultLang) { _defaultLang = defaultLang; return this; } - public UserBuilder WithLanguage(string language) + public UserBuilder WithLanguage(string language) { _language = language; return this; } - public UserBuilder WithApproved(bool approved) - { - _approved = approved; - return this; - } - - public UserBuilder WithRawPassword(string rawPassword) + public UserBuilder WithRawPassword(string rawPassword) { _rawPassword = rawPassword; return this; } - public UserBuilder WithEmail(string email) + public UserBuilder WithEmail(string email) { _email = email; return this; } - public UserBuilder WithUsername(string username) + public UserBuilder WithUsername(string username) { _username = username; return this; } - public UserBuilder WithLockedOut(bool isLockedOut) + public UserBuilder WithLockedOut(bool isLockedOut) { _isLockedOut = isLockedOut; return this; } - public UserBuilder WithName(string name) - { - _name = name; - return this; - } - /// /// Will suffix the name, email and username for testing /// /// /// - public UserBuilder WithSuffix(string suffix) + public UserBuilder WithSuffix(string suffix) { _suffix = suffix; return this; @@ -84,16 +84,25 @@ namespace Umbraco.Tests.Common.Builders public override User Build() { - var globalSettings = Mock.Of(x => x.DefaultUILanguage == (_defaultLang ?? "en-US")); - return new User(globalSettings, - _name ?? "TestUser" + _suffix, - _email ?? "test" + _suffix + "@test.com", - _username ?? "TestUser" + _suffix, - _rawPassword ?? "abcdefghijklmnopqrstuvwxyz") + var globalSettings = new GlobalSettingsBuilder().WithDefaultUiLanguage(_defaultLang).Build(); + var name = _name ?? "TestUser" + _suffix; + var email = _email ?? "test" + _suffix + "@test.com"; + var username = _username ?? "TestUser" + _suffix; + var rawPassword = _rawPassword ?? "abcdefghijklmnopqrstuvwxyz"; + var language = _language ?? globalSettings.DefaultUILanguage; + var isLockedOut = _isLockedOut ?? false; + var approved = _approved ?? true; + + return new User( + globalSettings, + name, + email, + username, + rawPassword) { - Language = _language ?? _defaultLang ?? "en-US", - IsLockedOut = _isLockedOut ?? false, - IsApproved = _approved ?? true + Language = language, + IsLockedOut = isLockedOut, + IsApproved = approved }; } @@ -102,5 +111,17 @@ namespace Umbraco.Tests.Common.Builders get => _id; set => _id = value; } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + bool? IWithApprovedBuilder.Approved + { + get => _approved; + set => _approved = value; + } } } diff --git a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs index d3ce5e71a8..4c6bb7a74c 100644 --- a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs @@ -6,9 +6,20 @@ using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { - public class UserGroupBuilder - : BuilderBase, - IWithIdBuilder + + public class UserGroupBuilder : UserGroupBuilder + { + public UserGroupBuilder() : base(null) + { + } + } + + public class UserGroupBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithIconBuilder, + IWithAliasBuilder, + IWithNameBuilder { private int? _startContentId; private int? _startMediaId; @@ -20,12 +31,16 @@ namespace Umbraco.Tests.Common.Builders private string _suffix; private int? _id; + public UserGroupBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + /// /// Will suffix the name and alias for testing /// /// /// - public UserGroupBuilder WithSuffix(string suffix) + public UserGroupBuilder WithSuffix(string suffix) { _suffix = suffix; return this; @@ -61,5 +76,24 @@ namespace Umbraco.Tests.Common.Builders get => _id; set => _id = value; } + + + string IWithIconBuilder.Icon + { + get => _icon; + set => _icon = value; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } } } diff --git a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs index 553aa4fe85..a162845200 100644 --- a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Integration.Extensions // dynamically change the config status var umbVersion = app.ApplicationServices.GetRequiredService(); var config = app.ApplicationServices.GetRequiredService(); - config[GlobalSettings.Prefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); + config[Constants.Configuration.ConfigGlobalPrefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); // re-run the runtime level check var profilingLogger = app.ApplicationServices.GetRequiredService(); diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 2974a01669..5968ee89fb 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -97,7 +97,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _); }); var host = await hostBuilder.StartAsync(); @@ -136,7 +136,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _); }); var host = await hostBuilder.StartAsync(); diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 43504e908a..d822d687d9 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -118,7 +118,7 @@ namespace Umbraco.Tests.Integration.Testing // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _); }); var host = await hostBuilder.StartAsync(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index a097661a93..ba1e500d5a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -4,18 +4,17 @@ using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; - namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { [TestFixture] public class DataTypeTests { - private readonly DataTypeBuilder _builder = new DataTypeBuilder(); + [Test] public void Can_Deep_Clone() { - var dtd = _builder + var dtd = _builder .WithId(3123) .Build(); diff --git a/src/Umbraco.Tests/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs similarity index 61% rename from src/Umbraco.Tests/Models/MemberGroupTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs index c3cf1c8c76..91ccd56b9e 100644 --- a/src/Umbraco.Tests/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs @@ -3,28 +3,21 @@ using System.Diagnostics; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; -using Umbraco.Core.Serialization; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; -namespace Umbraco.Tests.Models +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { [TestFixture] public class MemberGroupTests { + private readonly MemberGroupBuilder _builder = new MemberGroupBuilder(); + [Test] public void Can_Deep_Clone() { // Arrange - var group = new MemberGroup() - { - CreateDate = DateTime.Now, - CreatorId = 4, - Id = 6, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now, - Name = "asdf" - }; - group.AdditionalData.Add("test1", 123); - group.AdditionalData.Add("test2", "hello"); + var group = BuildMemberGroup(); // Act var clone = (MemberGroup)group.DeepClone(); @@ -44,29 +37,32 @@ namespace Umbraco.Tests.Models //This double verifies by reflection var allProps = clone.GetType().GetProperties(); foreach (var propertyInfo in allProps) - { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(group, null)); - } } [Test] public void Can_Serialize_Without_Error() { - var group = new MemberGroup() - { - CreateDate = DateTime.Now, - CreatorId = 4, - Id = 6, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now, - Name = "asdf" - }; - group.AdditionalData.Add("test1", 123); - group.AdditionalData.Add("test2", "hello"); + var group = BuildMemberGroup(); var json = JsonConvert.SerializeObject(group); Debug.Print(json); } + private MemberGroup BuildMemberGroup() + { + return _builder + .WithId(6) + .WithKey(Guid.NewGuid()) + .WithName("Test Group") + .WithCreatorId(4) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) + .AddAdditionalData() + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") + .Done() + .Build(); + } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs new file mode 100644 index 0000000000..1ac255db39 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs @@ -0,0 +1,151 @@ +using System; +using System.Diagnostics; +using System.Linq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models +{ + [TestFixture] + public class MemberTests + { + private readonly MemberBuilder _builder = new MemberBuilder(); + + [Test] + public void Can_Deep_Clone() + { + // Arrange + var member = BuildMember(); + + // Act + var clone = (Member)member.DeepClone(); + + // Assert + Assert.AreNotSame(clone, member); + Assert.AreEqual(clone, member); + Assert.AreEqual(clone.Id, member.Id); + Assert.AreEqual(clone.VersionId, member.VersionId); + Assert.AreEqual(clone.AdditionalData, member.AdditionalData); + Assert.AreEqual(clone.ContentType, member.ContentType); + Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId); + Assert.AreEqual(clone.CreateDate, member.CreateDate); + Assert.AreEqual(clone.CreatorId, member.CreatorId); + Assert.AreEqual(clone.Comments, member.Comments); + Assert.AreEqual(clone.Key, member.Key); + Assert.AreEqual(clone.FailedPasswordAttempts, member.FailedPasswordAttempts); + Assert.AreEqual(clone.Level, member.Level); + Assert.AreEqual(clone.Path, member.Path); + Assert.AreEqual(clone.Groups, member.Groups); + Assert.AreEqual(clone.Groups.Count(), member.Groups.Count()); + Assert.AreEqual(clone.IsApproved, member.IsApproved); + Assert.AreEqual(clone.IsLockedOut, member.IsLockedOut); + Assert.AreEqual(clone.SortOrder, member.SortOrder); + Assert.AreEqual(clone.LastLockoutDate, member.LastLockoutDate); + Assert.AreNotSame(clone.LastLoginDate, member.LastLoginDate); + Assert.AreEqual(clone.LastPasswordChangeDate, member.LastPasswordChangeDate); + Assert.AreEqual(clone.Trashed, member.Trashed); + Assert.AreEqual(clone.UpdateDate, member.UpdateDate); + Assert.AreEqual(clone.VersionId, member.VersionId); + Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); + Assert.AreNotSame(clone.Properties, member.Properties); + Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); + for (var index = 0; index < member.Properties.Count; index++) + { + Assert.AreNotSame(clone.Properties[index], member.Properties[index]); + Assert.AreEqual(clone.Properties[index], member.Properties[index]); + } + + // this can be the same, it is immutable + Assert.AreSame(clone.ContentType, member.ContentType); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(member, null)); + } + + [Test] + public void Can_Serialize_Without_Error() + { + var member = BuildMember(); + + var json = JsonConvert.SerializeObject(member); + Debug.Print(json); + } + + private Member BuildMember() + { + return _builder + .AddMemberType() + .WithId(99) + .WithAlias("memberType") + .WithName("Member Type") + .WithMembershipPropertyGroup() + .AddPropertyGroup() + .WithName("Content") + .WithSortOrder(1) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias("title") + .WithName("Title") + .WithSortOrder(1) + .WithDataTypeId(-88) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias("bodyText") + .WithName("Body text") + .WithSortOrder(2) + .WithDataTypeId(-87) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias("author") + .WithName("Author") + .WithDescription("Name of the author") + .WithSortOrder(3) + .WithDataTypeId(-88) + .Done() + .Done() + .Done() + .WithId(10) + .WithKey(Guid.NewGuid()) + .WithName("Fred") + .WithUserName("fred") + .WithRawPasswordValue("raw pass") + .WithEmail("email@email.com") + .WithCreatorId(22) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) + .WithFailedPasswordAttempts(22) + .WithLevel(3) + .WithPath("-1, 4, 10") + .WithIsApproved(true) + .WithIsLockedOut(true) + .WithSortOrder(5) + .WithTrashed(false) + .AddMemberGroups() + .WithValue("Group 1") + .WithValue("Group 2") + .Done() + .AddAdditionalData() + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") + .Done() + .WithPropertyIdsIncrementingFrom(200) + .AddPropertyData() + .WithKeyValue("title", "Name member") + .WithKeyValue("bodyText", "This is a subpage") + .WithKeyValue("author", "John Doe") + .Done() + .Build(); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs new file mode 100644 index 0000000000..ce47c69763 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs @@ -0,0 +1,27 @@ +using NUnit.Framework; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class DataTypeBuilderTests + { + [Test] + public void Is_Built_Correctly() + { + // Arrange + const int testId = 3123; + + var builder = new DataTypeBuilder(); + + // Act + var dtd = builder + .WithId(testId) + .Build(); + + // Assert + Assert.AreEqual(testId, dtd.Id); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs new file mode 100644 index 0000000000..e1df867642 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class MemberBuilderTests + { + private class PropertyTypeDetail + { + public string Alias { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } = string.Empty; + + public int SortOrder { get; set; } + + public int DataTypeId { get; set; } + } + + [Test] + public void Is_Built_Correctly() + { + // Arrange + const int testMemberTypeId = 99; + const string testMemberTypeAlias = "memberType"; + const string testMemberTypeName = "Member Type"; + const string testMemberTypePropertyGroupName = "Content"; + const int testId = 10; + const string testName = "Fred"; + const string testUsername = "fred"; + const string testRawPasswordValue = "raw pass"; + const string testEmail = "email@email.com"; + const int testCreatorId = 22; + const int testLevel = 3; + const string testPath = "-1, 4, 10"; + const bool testIsApproved = true; + const bool testIsLockedOut = true; + const int testSortOrder = 5; + const bool testTrashed = false; + var testKey = Guid.NewGuid(); + var testCreateDate = DateTime.Now.AddHours(-1); + var testUpdateDate = DateTime.Now; + var testLastLockoutDate = DateTime.Now.AddHours(-2); + var testLastLoginDate = DateTime.Now.AddHours(-3); + var testLastPasswordChangeDate = DateTime.Now.AddHours(-4); + var testPropertyType1 = new PropertyTypeDetail { Alias = "title", Name = "Title", SortOrder = 1, DataTypeId = -88 }; + var testPropertyType2 = new PropertyTypeDetail { Alias = "bodyText", Name = "Body Text", SortOrder = 2, DataTypeId = -87 }; + var testPropertyType3 = new PropertyTypeDetail { Alias = "author", Name = "Author", Description = "Writer of the article", SortOrder = 1, DataTypeId = -88 }; + var testGroups = new string[] { "group1", "group2" }; + var testPropertyData1 = new KeyValuePair("title", "Name member"); + var testPropertyData2 = new KeyValuePair("bodyText", "This is a subpage"); + var testPropertyData3 = new KeyValuePair("author", "John Doe"); + var testAdditionalData1 = new KeyValuePair("test1", 123); + var testAdditionalData2 = new KeyValuePair("test2", "hello"); + + var builder = new MemberBuilder(); + + // Act + var member = builder + .AddMemberType() + .WithId(testMemberTypeId) + .WithAlias(testMemberTypeAlias) + .WithName(testMemberTypeName) + .WithMembershipPropertyGroup() + .AddPropertyGroup() + .WithName(testMemberTypePropertyGroupName) + .WithSortOrder(1) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias(testPropertyType1.Alias) + .WithName(testPropertyType1.Name) + .WithSortOrder(testPropertyType1.SortOrder) + .WithDataTypeId(testPropertyType1.DataTypeId) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias(testPropertyType2.Alias) + .WithName(testPropertyType2.Name) + .WithSortOrder(testPropertyType2.SortOrder) + .WithDataTypeId(testPropertyType2.DataTypeId) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias(testPropertyType3.Alias) + .WithName(testPropertyType3.Name) + .WithDescription(testPropertyType3.Description) + .WithSortOrder(testPropertyType3.SortOrder) + .WithDataTypeId(testPropertyType3.DataTypeId) + .Done() + .Done() + .Done() + .WithId(testId) + .WithKey(testKey) + .WithName(testName) + .WithUserName(testUsername) + .WithRawPasswordValue(testRawPasswordValue) + .WithEmail(testEmail) + .WithCreatorId(testCreatorId) + .WithCreateDate(testCreateDate) + .WithUpdateDate(testUpdateDate) + .WithFailedPasswordAttempts(22) + .WithLevel(testLevel) + .WithPath(testPath) + .WithIsApproved(testIsApproved) + .WithIsLockedOut(testIsLockedOut) + .WithLastLockoutDate(testLastLockoutDate) + .WithLastLoginDate(testLastLoginDate) + .WithLastPasswordChangeDate(testLastPasswordChangeDate) + .WithSortOrder(testSortOrder) + .WithTrashed(testTrashed) + .AddMemberGroups() + .WithValue(testGroups[0]) + .WithValue(testGroups[1]) + .Done() + .AddAdditionalData() + .WithKeyValue(testAdditionalData1.Key, testAdditionalData1.Value) + .WithKeyValue(testAdditionalData2.Key, testAdditionalData2.Value) + .Done() + .WithPropertyIdsIncrementingFrom(200) + .AddPropertyData() + .WithKeyValue(testPropertyData1.Key, testPropertyData1.Value) + .WithKeyValue(testPropertyData2.Key, testPropertyData2.Value) + .WithKeyValue(testPropertyData3.Key, testPropertyData3.Value) + .Done() + .Build(); + + // Assert + Assert.AreEqual(testMemberTypeId, member.ContentTypeId); + Assert.AreEqual(testMemberTypeAlias, member.ContentType.Alias); + Assert.AreEqual(testMemberTypeName, member.ContentType.Name); + Assert.AreEqual(testId, member.Id); + Assert.AreEqual(testKey, member.Key); + Assert.AreEqual(testName, member.Name); + Assert.AreEqual(testCreateDate, member.CreateDate); + Assert.AreEqual(testUpdateDate, member.UpdateDate); + Assert.AreEqual(testCreatorId, member.CreatorId); + Assert.AreEqual(testGroups, member.Groups.ToArray()); + Assert.AreEqual(10, member.Properties.Count); // 7 from membership properties group, 3 custom + Assert.AreEqual(testPropertyData1.Value, member.GetValue(testPropertyData1.Key)); + Assert.AreEqual(testPropertyData2.Value, member.GetValue(testPropertyData2.Key)); + Assert.AreEqual(testPropertyData3.Value, member.GetValue(testPropertyData3.Key)); + Assert.AreEqual(2, member.AdditionalData.Count); + Assert.AreEqual(testAdditionalData1.Value, member.AdditionalData[testAdditionalData1.Key]); + Assert.AreEqual(testAdditionalData2.Value, member.AdditionalData[testAdditionalData2.Key]); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs new file mode 100644 index 0000000000..4cb47de052 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class MemberGroupBuilderTests + { + [Test] + public void Is_Built_Correctly() + { + // Arrange + const int testId = 6; + const string testName = "Test Group"; + const int testCreatorId = 4; + var testKey = Guid.NewGuid(); + var testCreateDate = DateTime.Now.AddHours(-1); + var testUpdateDate = DateTime.Now; + var testAdditionalData1 = new KeyValuePair("test1", 123); + var testAdditionalData2 = new KeyValuePair("test2", "hello"); + + var builder = new MemberGroupBuilder(); + + // Act + var group = builder + .WithId(testId) + .WithKey(testKey) + .WithName(testName) + .WithCreatorId(testCreatorId) + .WithCreateDate(testCreateDate) + .WithUpdateDate(testUpdateDate) + .AddAdditionalData() + .WithKeyValue(testAdditionalData1.Key, testAdditionalData1.Value) + .WithKeyValue(testAdditionalData2.Key, testAdditionalData2.Value) + .Done() + .Build(); + + // Assert + Assert.AreEqual(testId, group.Id); + Assert.AreEqual(testKey, group.Key); + Assert.AreEqual(testName, group.Name); + Assert.AreEqual(testCreateDate, group.CreateDate); + Assert.AreEqual(testUpdateDate, group.UpdateDate); + Assert.AreEqual(testCreatorId, group.CreatorId); + Assert.AreEqual(3, group.AdditionalData.Count); // previousName is added as part of the MemberGroup construction, plus the 2 we've added. + Assert.AreEqual(testAdditionalData1.Value, group.AdditionalData[testAdditionalData1.Key]); + Assert.AreEqual(testAdditionalData2.Value, group.AdditionalData[testAdditionalData2.Key]); + } + } +} diff --git a/src/Umbraco.Tests/Cache/AppCacheTests.cs b/src/Umbraco.Tests/Cache/AppCacheTests.cs index bb283064c7..6b1c14d320 100644 --- a/src/Umbraco.Tests/Cache/AppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/AppCacheTests.cs @@ -96,7 +96,7 @@ namespace Umbraco.Tests.Cache return ""; }); - Assert.AreEqual(counter, 1); + Assert.AreEqual(1, counter); } diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs index 9657091859..668d0b0297 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs @@ -27,17 +27,17 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public virtual void Can_Set_Multiple() { - Assert.AreEqual(ContentSettings.Error404Collection.Count(), 3); - Assert.AreEqual(ContentSettings.Error404Collection.ElementAt(0).Culture, "default"); - Assert.AreEqual(ContentSettings.Error404Collection.ElementAt(0).ContentId, 1047); + Assert.AreEqual(3, ContentSettings.Error404Collection.Count()); + Assert.AreEqual("default", ContentSettings.Error404Collection.ElementAt(0).Culture); + Assert.AreEqual(1047, ContentSettings.Error404Collection.ElementAt(0).ContentId); Assert.IsTrue(ContentSettings.Error404Collection.ElementAt(0).HasContentId); Assert.IsFalse(ContentSettings.Error404Collection.ElementAt(0).HasContentKey); - Assert.AreEqual(ContentSettings.Error404Collection.ElementAt(1).Culture, "en-US"); - Assert.AreEqual(ContentSettings.Error404Collection.ElementAt(1).ContentXPath, "$site/error [@name = 'error']"); + Assert.AreEqual("en-US", ContentSettings.Error404Collection.ElementAt(1).Culture); + Assert.AreEqual("$site/error [@name = 'error']", ContentSettings.Error404Collection.ElementAt(1).ContentXPath); Assert.IsFalse(ContentSettings.Error404Collection.ElementAt(1).HasContentId); Assert.IsFalse(ContentSettings.Error404Collection.ElementAt(1).HasContentKey); - Assert.AreEqual(ContentSettings.Error404Collection.ElementAt(2).Culture, "en-UK"); - Assert.AreEqual(ContentSettings.Error404Collection.ElementAt(2).ContentKey, new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC")); + Assert.AreEqual("en-UK", ContentSettings.Error404Collection.ElementAt(2).Culture); + Assert.AreEqual(new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC"), ContentSettings.Error404Collection.ElementAt(2).ContentKey); Assert.IsTrue(ContentSettings.Error404Collection.ElementAt(2).HasContentKey); Assert.IsFalse(ContentSettings.Error404Collection.ElementAt(2).HasContentId); } @@ -51,17 +51,17 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public virtual void ImageAutoFillProperties() { - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.Count(), 2); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(0).Alias, "umbracoFile"); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias, "umbracoWidth"); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias, "umbracoHeight"); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias, "umbracoBytes"); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias, "umbracoExtension"); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(1).Alias, "umbracoFile2"); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias, "umbracoWidth2"); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias, "umbracoHeight2"); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias, "umbracoBytes2"); - Assert.AreEqual(ContentSettings.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias, "umbracoExtension2"); + Assert.AreEqual(2, ContentSettings.ImageAutoFillProperties.Count()); + Assert.AreEqual("umbracoFile", ContentSettings.ImageAutoFillProperties.ElementAt(0).Alias); + Assert.AreEqual("umbracoWidth", ContentSettings.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias); + Assert.AreEqual("umbracoHeight", ContentSettings.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias); + Assert.AreEqual("umbracoBytes", ContentSettings.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias); + Assert.AreEqual("umbracoExtension", ContentSettings.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias); + Assert.AreEqual("umbracoFile2", ContentSettings.ImageAutoFillProperties.ElementAt(1).Alias); + Assert.AreEqual("umbracoWidth2", ContentSettings.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias); + Assert.AreEqual("umbracoHeight2", ContentSettings.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias); + Assert.AreEqual("umbracoBytes2", ContentSettings.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias); + Assert.AreEqual("umbracoExtension2", ContentSettings.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias); } [Test] @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings Debug.WriteLine("AllowedContainsExtension: {0}", allowedContainsExtension); Debug.WriteLine("DisallowedContainsExtension: {0}", disallowedContainsExtension); - Assert.AreEqual(ContentSettings.IsFileAllowedForUpload(extension), expected); + Assert.AreEqual(expected, ContentSettings.IsFileAllowedForUpload(extension)); } } } diff --git a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs index 42d8f92a5d..62e385beff 100644 --- a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -27,7 +27,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -41,7 +41,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); Assert.AreEqual(xml, xdoc.OuterXml); } } diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs deleted file mode 100644 index e0cd826536..0000000000 --- a/src/Umbraco.Tests/Models/MemberTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Core.Composing; -using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; - -namespace Umbraco.Tests.Models -{ - [TestFixture] - public class MemberTests - { - [Test] - public void Can_Deep_Clone() - { - // Arrange - var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); - memberType.Id = 99; - var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); - var i = 200; - foreach (var property in member.Properties) - { - property.Id = ++i; - } - member.Id = 10; - member.CreateDate = DateTime.Now; - member.CreatorId = 22; - member.Comments = "comments"; - member.Key = Guid.NewGuid(); - member.FailedPasswordAttempts = 22; - member.Level = 3; - member.Path = "-1,4,10"; - member.Groups = new[] {"group1", "group2"}; - member.IsApproved = true; - member.IsLockedOut = true; - member.LastLockoutDate = DateTime.Now; - member.LastLoginDate = DateTime.Now; - member.LastPasswordChangeDate = DateTime.Now; - member.RawPasswordValue = "raw pass"; - member.SortOrder = 5; - member.Trashed = false; - member.UpdateDate = DateTime.Now; - member.AdditionalData.Add("test1", 123); - member.AdditionalData.Add("test2", "hello"); - - // Act - var clone = (Member)member.DeepClone(); - - // Assert - Assert.AreNotSame(clone, member); - Assert.AreEqual(clone, member); - Assert.AreEqual(clone.Id, member.Id); - Assert.AreEqual(clone.VersionId, member.VersionId); - Assert.AreEqual(clone.AdditionalData, member.AdditionalData); - Assert.AreEqual(clone.ContentType, member.ContentType); - Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId); - Assert.AreEqual(clone.CreateDate, member.CreateDate); - Assert.AreEqual(clone.CreatorId, member.CreatorId); - Assert.AreEqual(clone.Comments, member.Comments); - Assert.AreEqual(clone.Key, member.Key); - Assert.AreEqual(clone.FailedPasswordAttempts, member.FailedPasswordAttempts); - Assert.AreEqual(clone.Level, member.Level); - Assert.AreEqual(clone.Path, member.Path); - Assert.AreEqual(clone.Groups, member.Groups); - Assert.AreEqual(clone.Groups.Count(), member.Groups.Count()); - Assert.AreEqual(clone.IsApproved, member.IsApproved); - Assert.AreEqual(clone.IsLockedOut, member.IsLockedOut); - Assert.AreEqual(clone.SortOrder, member.SortOrder); - Assert.AreEqual(clone.LastLockoutDate, member.LastLockoutDate); - Assert.AreNotSame(clone.LastLoginDate, member.LastLoginDate); - Assert.AreEqual(clone.LastPasswordChangeDate, member.LastPasswordChangeDate); - Assert.AreEqual(clone.Trashed, member.Trashed); - Assert.AreEqual(clone.UpdateDate, member.UpdateDate); - Assert.AreEqual(clone.VersionId, member.VersionId); - Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); - Assert.AreNotSame(clone.Properties, member.Properties); - Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); - for (var index = 0; index < member.Properties.Count; index++) - { - Assert.AreNotSame(clone.Properties[index], member.Properties[index]); - Assert.AreEqual(clone.Properties[index], member.Properties[index]); - } - - // this can be the same, it is immutable - Assert.AreSame(clone.ContentType, member.ContentType); - - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) - { - Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(member, null)); - } - } - - [Test] - public void Can_Serialize_Without_Error() - { - var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); - memberType.Id = 99; - var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); - var i = 200; - foreach (var property in member.Properties) - { - property.Id = ++i; - } - member.Id = 10; - member.CreateDate = DateTime.Now; - member.CreatorId = 22; - member.Comments = "comments"; - member.Key = Guid.NewGuid(); - member.FailedPasswordAttempts = 22; - member.Level = 3; - member.Path = "-1,4,10"; - member.Groups = new[] { "group1", "group2" }; - member.IsApproved = true; - member.IsLockedOut = true; - member.LastLockoutDate = DateTime.Now; - member.LastLoginDate = DateTime.Now; - member.LastPasswordChangeDate = DateTime.Now; - member.RawPasswordValue = "raw pass"; - member.SortOrder = 5; - member.Trashed = false; - member.UpdateDate = DateTime.Now; - member.AdditionalData.Add("test1", 123); - member.AdditionalData.Add("test2", "hello"); - - var json = JsonConvert.SerializeObject(member); - Debug.Print(json); - } - } -} diff --git a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs index 3ada9fcceb..c157d66b2c 100644 --- a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs +++ b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs @@ -1693,7 +1693,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.Depth, 0); + Assert.AreEqual(0, testReader.Depth); } } @@ -2067,7 +2067,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.RecordsAffected, -1); + Assert.AreEqual(-1, testReader.RecordsAffected); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index 70e7b664a1..3fbf87fedc 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -303,15 +303,13 @@ namespace Umbraco.Tests.Persistence.Repositories stylesheet = repository.Get("missing.css"); Assert.IsNull(stylesheet); - // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => - { - stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist - }); - Assert.Throws(() => - { - stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists - }); + // #7713 changes behaviour to return null when outside the filesystem + // to accomodate changing the CSS path and not flooding the backoffice with errors + stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist + Assert.IsNull(stylesheet); + + stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists + Assert.IsNull(stylesheet); } } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index e9b3c49e10..7ac2a0cba4 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -1325,6 +1325,18 @@ namespace Umbraco.Tests.PublishedContent AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1); } + [Test] + public void MultipleCacheIteration() + { + //see https://github.com/umbraco/Umbraco-CMS/issues/7798 + this.Init(this.GetInvariantKits()); + var snapshot = this._snapshotService.CreatePublishedSnapshot(previewToken: null); + this._snapshotAccessor.PublishedSnapshot = snapshot; + + var items = snapshot.Content.GetByXPath("/root/itype"); + Assert.AreEqual(items.Count(), items.Count()); + } + private void AssertLinkedNode(ContentNode node, int parent, int prevSibling, int nextSibling, int firstChild, int lastChild) { Assert.AreEqual(parent, node.ParentContentId); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 5319230de1..5f4694c420 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1811,7 +1811,7 @@ namespace Umbraco.Tests.Services ServiceContext.ContentService.Save(content2, Constants.Security.SuperUserId); Assert.IsTrue(ServiceContext.ContentService.SaveAndPublish(content2, userId: 0).Success); - var editorGroup = ServiceContext.UserService.GetUserGroupByAlias("editor"); + var editorGroup = ServiceContext.UserService.GetUserGroupByAlias(Constants.Security.EditorGroupAlias); editorGroup.StartContentId = content1.Id; ServiceContext.UserService.Save(editorGroup); diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 6138607e9d..e7adbdacbf 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -151,7 +151,17 @@ namespace Umbraco.Tests.Services Assert.AreEqual(3, found.Count()); } + [Test] + public void Can_Get_All_Roles_IDs() + { + ServiceContext.MemberService.AddRole("MyTestRole1"); + ServiceContext.MemberService.AddRole("MyTestRole2"); + ServiceContext.MemberService.AddRole("MyTestRole3"); + var found = ServiceContext.MemberService.GetAllRolesIds(); + + Assert.AreEqual(3, found.Count()); + } [Test] public void Can_Get_All_Roles_By_Member_Id() { @@ -170,7 +180,24 @@ namespace Umbraco.Tests.Services Assert.AreEqual(2, memberRoles.Count()); } + [Test] + public void Can_Get_All_Roles_Ids_By_Member_Id() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + ServiceContext.MemberService.AddRole("MyTestRole1"); + ServiceContext.MemberService.AddRole("MyTestRole2"); + ServiceContext.MemberService.AddRole("MyTestRole3"); + ServiceContext.MemberService.AssignRoles(new[] { member.Id }, new[] { "MyTestRole1", "MyTestRole2" }); + + var memberRoles = ServiceContext.MemberService.GetAllRolesIds(member.Id); + + Assert.AreEqual(2, memberRoles.Count()); + + } [Test] public void Can_Get_All_Roles_By_Member_Username() { diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index ef8ababf6e..cae6acb951 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -60,6 +60,24 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(stripped, result); } + [TestCase("../wtf.js?x=wtf", ".js")] + [TestCase(".htaccess", ".htaccess")] + [TestCase("path/to/file/image.png", ".png")] + [TestCase("c:\\abc\\def\\ghi.jkl", ".jkl")] + [TestCase("/root/folder.name/file.ext", ".ext")] + [TestCase("http://www.domain.com/folder/name/file.txt", ".txt")] + [TestCase("i/don't\\have\\an/extension", "")] + [TestCase("https://some.where/path/to/file.ext?query=this&more=that", ".ext")] + [TestCase("double_query.string/file.ext?query=abc?something.else", ".ext")] + [TestCase("test.tar.gz", ".gz")] + [TestCase("wierd.file,but._legal", "._legal")] + [TestCase("one_char.x", ".x")] + public void Get_File_Extension(string input, string result) + { + var extension = input.GetFileExtension(); + Assert.AreEqual(result, extension); + } + [TestCase("'+alert(1234)+'", "+alert1234+")] [TestCase("'+alert(56+78)+'", "+alert56+78+")] [TestCase("{{file}}", "file")] diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 3bfc25cf50..8854710fbf 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -297,8 +297,6 @@ - - diff --git a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs index 780a3e90fd..fd30f1b5ec 100644 --- a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs +++ b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index 141676e722..d8f8fc1aed 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -52,13 +52,25 @@ namespace Umbraco.Web.Common.Extensions /// /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment) + { + return services.AddUmbracoCore(webHostEnvironment,out _); + } + + /// + /// Adds the Umbraco Back Core requirements + /// + /// + /// + /// + /// + public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, out IFactory factory) { if (!UmbracoServiceProviderFactory.IsActive) throw new InvalidOperationException("Ensure to add UseUmbraco() in your Program.cs after ConfigureWebHostDefaults to enable Umbraco's service provider factory"); var umbContainer = UmbracoServiceProviderFactory.UmbracoContainer; - services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly()); + services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly(), out factory); return services; } @@ -70,8 +82,9 @@ namespace Umbraco.Web.Common.Extensions /// /// /// + /// /// - public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly) + public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly, out IFactory factory) { if (services is null) throw new ArgumentNullException(nameof(services)); var container = umbContainer; @@ -101,7 +114,7 @@ namespace Umbraco.Web.Common.Extensions backOfficeInfo, typeFinder); - var factory = coreRuntime.Configure(container); + factory = coreRuntime.Configure(container); return services; } @@ -159,8 +172,6 @@ namespace Umbraco.Web.Common.Extensions backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings); profiler = GetWebProfiler(hostingEnvironment, httpContextAccessor); - Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler); - return services; } diff --git a/src/Umbraco.Web.UI.Client/.eslintrc b/src/Umbraco.Web.UI.Client/.eslintrc index 1bfa081b9a..a4010917fb 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc +++ b/src/Umbraco.Web.UI.Client/.eslintrc @@ -19,10 +19,10 @@ "tinyMCE": false, "FileReader": false, "Umbraco": false, + "Utilities": false, "window": false, "LazyLoad": false, "ActiveXObject": false, "Bloodhound": false } - -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 3fa2a7628a..67a47a0a15 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -28,9 +28,9 @@ module.exports = { installer: { files: "./src/installer/**/*.js", out: "umbraco.installer.js" }, filters: { files: "./src/common/filters/**/*.js", out: "umbraco.filters.js" }, resources: { files: "./src/common/resources/**/*.js", out: "umbraco.resources.js" }, - services: { files: "./src/common/services/**/*.js", out: "umbraco.services.js" }, + services: { files: ["./src/common/services/**/*.js", "./src/utilities.js"], out: "umbraco.services.js" }, security: { files: "./src/common/interceptors/**/*.js", out: "umbraco.interceptors.js" }, - + //the controllers for views controllers: { files: [ diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 60e70ea170..1711163eb0 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -876,9 +876,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "source-map": { @@ -1035,9 +1035,9 @@ "integrity": "sha1-avwuQ6e17/3ETYQHQ2EShSVo6A0=" }, "acorn": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", - "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "dev": true }, "acorn-jsx": { @@ -2103,13 +2103,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2122,9 +2124,10 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -2416,7 +2419,7 @@ }, "callsites": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, @@ -2459,9 +2462,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001002", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001002.tgz", - "integrity": "sha512-pRuxPE8wdrWmVPKcDmJJiGBxr6lFJq4ivdSeo9FTmGj5Rb8NX3Mby2pARG57MXF15hYAhZ0nHV5XxT2ig4bz3g==", + "version": "1.0.30001038", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001038.tgz", + "integrity": "sha512-zii9quPo96XfOiRD4TrfYGs+QsGZpb2cGiMAzPjtf/hpFgB6zCPZgJb7I1+EATeMw/o+lG8FyRAnI+CWStHcaQ==", "dev": true }, "caseless": { @@ -6547,7 +6550,7 @@ }, "kind-of": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", "dev": true }, @@ -6712,9 +6715,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "source-map": { @@ -6759,7 +6762,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -12972,7 +12975,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -14071,7 +14074,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -14093,7 +14096,7 @@ }, "kind-of": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", "dev": true }, @@ -14678,7 +14681,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "optional": true, diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index c150af79de..438052367e 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -50,7 +50,7 @@ "@babel/core": "7.6.4", "@babel/preset-env": "7.6.3", "autoprefixer": "9.6.5", - "caniuse-lite": "^1.0.30001002", + "caniuse-lite": "^1.0.30001037", "cssnano": "4.1.10", "fs": "0.0.2", "gulp": "4.0.2", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index a33796ab6d..190da7da0d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -12,26 +12,25 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionItemsWidth = []; var evts = []; - var maxSections = 8; //setup scope vars - scope.maxSections = maxSections; - scope.overflowingSections = 0; scope.sections = []; + scope.visibleSections = 0; scope.currentSection = appState.getSectionState("currentSection"); - scope.showTray = false; //appState.getGlobalState("showTray"); + scope.showTray = false; scope.stickyNavigation = appState.getGlobalState("stickyNavigation"); - scope.needTray = false; function loadSections() { sectionService.getSectionsForUser() .then(function (result) { scope.sections = result; + scope.visibleSections = scope.sections.length; + // store the width of each section so we can hide/show them based on browser width // we store them because the sections get removed from the dom and then we // can't tell when to show them gain $timeout(function () { - $("#applications .sections li").each(function (index) { + $("#applications .sections li:not(:last)").each(function (index) { sectionItemsWidth.push($(this).outerWidth()); }); }); @@ -42,25 +41,22 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se function calculateWidth() { $timeout(function () { //total width minus room for avatar, search, and help icon - var windowWidth = $(window).width() - 150; + var containerWidth = $(".umb-app-header").outerWidth() - $(".umb-app-header__actions").outerWidth(); + var trayToggleWidth = $("#applications .sections li.expand").outerWidth(); var sectionsWidth = 0; - scope.totalSections = scope.sections.length; - scope.maxSections = maxSections; - scope.overflowingSections = scope.maxSections - scope.totalSections; - scope.needTray = scope.sections.length > scope.maxSections; - + // detect how many sections we can show on the screen for (var i = 0; i < sectionItemsWidth.length; i++) { var sectionItemWidth = sectionItemsWidth[i]; sectionsWidth += sectionItemWidth; - if (sectionsWidth > windowWidth) { - scope.needTray = true; - scope.maxSections = i - 1; - scope.overflowingSections = scope.maxSections - scope.totalSections; - break; + if (sectionsWidth + trayToggleWidth > containerWidth) { + scope.visibleSections = i; + return; } } + + scope.visibleSections = scope.sections.length; }); } @@ -134,14 +130,9 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se }; scope.currentSectionInOverflow = function () { - if (scope.overflowingSections === 0) { - return false; - } - var currentSection = scope.sections.filter(s => s.alias === scope.currentSection); - return (scope.sections.indexOf(currentSection[0]) >= scope.maxSections); - + return currentSection.length > 0 && scope.sections.indexOf(currentSection[0]) > scope.visibleSections - 1; }; loadSections(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js index 455857c1e1..8cba83328f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js @@ -178,11 +178,11 @@ function angularHelper($q) { $valid: true, $submitted: false, $pending: undefined, - $addControl: angular.noop, - $removeControl: angular.noop, - $setValidity: angular.noop, - $setDirty: angular.noop, - $setPristine: angular.noop, + $addControl: Utilities.noop, + $removeControl: Utilities.noop, + $setValidity: Utilities.noop, + $setDirty: Utilities.noop, + $setPristine: Utilities.noop, $name: formName }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 8d1caab850..ab9cfb63d2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -578,30 +578,12 @@ function navigationService($routeParams, $location, $q, $injector, eventsService if (args.action.metaData["actionView"]) { templateUrl = args.action.metaData["actionView"]; } - else { - - //by convention we will look into the /views/{treetype}/{action}.html - // for example: /views/content/create.html - - //we will also check for a 'packageName' for the current tree, if it exists then the convention will be: - // for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html - + else { var treeAlias = treeService.getTreeAlias(args.node); - var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); - if (!treeAlias) { throw "Could not get tree alias for node " + args.node.id; - } - - if (packageTreeFolder) { - templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + treeAlias + "/" + args.action.alias + ".html"; - } - else { - templateUrl = "views/" + treeAlias + "/" + args.action.alias + ".html"; - } - + } + templateUrl = this.getTreeTemplateUrl(treeAlias, args.action.alias); } setMode("dialog"); @@ -611,6 +593,31 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } }, + /** + * @ngdoc method + * @name umbraco.services.navigationService#getTreeTemplateUrl + * @methodOf umbraco.services.navigationService + * + * @param {string} treeAlias the alias of the tree to look up + * @param {string} action the view file name + * @description + * creates the templateUrl based on treeAlias and action + * by convention we will look into the /views/{treetype}/{action}.html + * for example: /views/content/create.html + * we will also check for a 'packageName' for the current tree, if it exists then the convention will be: + * for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html + */ + getTreeTemplateUrl: function(treeAlias, action) { + var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); + if (packageTreeFolder) { + return Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + + "/" + packageTreeFolder + + "/backoffice/" + treeAlias + "/" + action + ".html"; + } + else { + return "views/" + treeAlias + "/" + action + ".html"; + } + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 6c765ee135..0d766dc7d8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -365,12 +365,23 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe }, /** + * @ngdoc method + * @name umbraco.resources.contentResource#downloadFile + * @methodOf umbraco.resources.contentResource + * + * @description * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 + * + * @param {string} httpPath the path (url) to the resource being downloaded + * @returns {Promise} http promise object. */ downloadFile : function (httpPath) { + /** + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + // Use an arraybuffer return $http.get(httpPath, { responseType: 'arraybuffer' }) .then(function (response) { diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index f6f162f04f..05c391c3e7 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -2,17 +2,16 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco var _status = { index: 0, - current: undefined, - steps: undefined, + current: null, + steps: null, loading: true, progress: "100%" }; - var factTimer = undefined; + var factTimer; var _installerModel = { - installId: undefined, - instructions: { - } + installId: null, + instructions: {} }; //add to umbraco installer facts here @@ -304,7 +303,7 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco }, switchToFeedback : function(){ - service.status.current = undefined; + service.status.current = null; service.status.loading = true; service.status.configuring = false; @@ -320,11 +319,11 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco switchToConfiguration : function(){ service.status.loading = false; service.status.configuring = true; - service.status.feedback = undefined; - service.status.fact = undefined; + service.status.feedback = null; + service.status.fact = null; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } }, @@ -335,8 +334,8 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco service.status.feedback = "Redirecting you to Umbraco, please wait"; service.status.loading = false; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } $timeout(function(){ diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index e83c5114fc..687fce95ae 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -4,18 +4,19 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $scope.invalidDbDns = false; $scope.dbs = [ - { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, - { name: 'Microsoft SQL Server', id: 1}, + { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0 }, + { name: 'Microsoft SQL Server', id: 1 }, { name: 'Microsoft SQL Azure', id: 3 }, - { name: 'Custom connection string', id: -1} + { name: 'Custom connection string', id: -1 } ]; - if ( installerService.status.current.model.dbType === undefined ) { + if (angular.isUndefined(installerService.status.current.model.dbType) || installerService.status.current.model.dbType === null) { installerService.status.current.model.dbType = 0; } - $scope.validateAndForward = function(){ - if ( !$scope.checking && this.myForm.$valid ) { + $scope.validateAndForward = function() { + if (!$scope.checking && this.myForm.$valid) + { $scope.checking = true; $scope.invalidDbDns = false; @@ -23,9 +24,9 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $http.post( Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection", - model ).then( function( response ) { + model).then(function(response) { - if ( response.data === true ) { + if (response.data === true) { installerService.forward(); } else { diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js index bdcef63d95..fdd2c65c1c 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js @@ -1,5 +1,4 @@ angular.module("umbraco.install").controller("Umbraco.Installer.MachineKeyController", function ($scope, installerService) { - $scope.continue = function () { installerService.status.current.model = true; @@ -11,4 +10,4 @@ installerService.forward(); }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html index cfb08744b2..92f5cc8d9d 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html @@ -20,8 +20,9 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js index 10c0d596eb..9f0108853f 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js @@ -13,14 +13,14 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f $scope.passwordPattern = new RegExp(exp); } - $scope.validateAndInstall = function(){ - installerService.install(); + $scope.validateAndInstall = function() { + installerService.install(); }; $scope.validateAndForward = function(){ - if(this.myForm.$valid){ - installerService.forward(); - } + if (this.myForm.$valid) { + installerService.forward(); + } }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less index 0465881387..02b67460f6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less @@ -17,15 +17,17 @@ .umb-button-group { .umb-button__button { - border-radius: @baseBorderRadius; + border-radius: @baseBorderRadius 0 0 @baseBorderRadius; + + &:hover { + z-index: 2; + } } .umb-button-group__toggle { border-radius: 0 @baseBorderRadius @baseBorderRadius 0; - border-left: 1px solid rgba(0,0,0,0.09); - margin-left: -2px; + margin-left: -1px; padding-left: 10px; padding-right: 10px; } - } diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 883907d1dc..198ac132c1 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -1,19 +1,18 @@ - -/** +/** * @ngdoc controller - * @name Umbraco.MainController + * @name Umbraco.MainController * @function * - * @description + * @description * The main application controller * */ function MainController($scope, $location, appState, treeService, notificationsService, userService, historyService, updateChecker, navigationService, eventsService, tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) { - + //the null is important because we do an explicit bool check on this in the view - $scope.authenticated = null; + $scope.authenticated = null; $scope.touchDevice = appState.getGlobalState("touchDevice"); $scope.infiniteMode = false; $scope.overlay = {}; diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 556a4d6aef..93122792d3 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -154,7 +154,7 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; @@ -176,24 +176,7 @@ app.config(function ($routeProvider) { $scope.templateUrl = "views/users/overview.html"; return; } - - // Here we need to figure out if this route is for a user's package tree and if so then we need - // to change it's convention view path to: - // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html - - // otherwise if it is a core tree we use the core paths: - // views/{treetype}/{method}.html - - var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); - - if (packageTreeFolder) { - $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, resolve: canRoute(true) @@ -202,30 +185,13 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $route, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.tree || !$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; + return; } - - // Here we need to figure out if this route is for a package tree and if so then we need - // to change it's convention view path to: - // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html - - // otherwise if it is a core tree we use the core paths: - // views/{treetype}/{method}.html - - var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); - - if (packageTreeFolder) { - $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } - + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, reloadOnUrl: false, diff --git a/src/Umbraco.Web.UI.Client/src/utilities.js b/src/Umbraco.Web.UI.Client/src/utilities.js new file mode 100644 index 0000000000..bd12506358 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/utilities.js @@ -0,0 +1,86 @@ +/** + * A friendly utility collection to replace AngularJs' ng-functions + * If it doesn't exist here, it's probably available as vanilla JS + * + * Still carries a dependency on underscore, but if usages of underscore from + * elsewhere in the codebase can instead use these methods, the underscore + * dependency will be nicely abstracted and can be removed/swapped later + * + * This collection is open to extension... + */ +(function (window) { + + /** + * Equivalent to angular.noop + */ + const noop = () => { }; + + /** + * Facade to angular.copy + */ + const copy = val => angular.copy(val); + + /** + * Equivalent to angular.isArray + */ + const isArray = val => Array.isArray(val) || val instanceof Array; + + /** + * Facade to angular.equals + */ + const equals = (a, b) => angular.equals(a, b); + + /** + * Facade to angular.extend + * Use this with Angular objects, for vanilla JS objects, use Object.assign() + */ + const extend = (dst, src) => angular.extend(dst, src); + + /** + * Equivalent to angular.isFunction + */ + const isFunction = val => typeof val === 'function'; + + /** + * Equivalent to angular.isUndefined + */ + const isUndefined = val => typeof val === 'undefined'; + + /** + * Equivalent to angular.isDefined. Inverts result of const isUndefined + */ + const isDefined = val => !isUndefined(val); + + /** + * Equivalent to angular.isString + */ + const isString = val => typeof val === 'string'; + + /** + * Equivalent to angular.isNumber + */ + const isNumber = val => typeof val === 'number'; + + /** + * Equivalent to angular.isObject + */ + const isObject = val => val !== null && typeof val === 'object'; + + let _utilities = { + noop: noop, + copy: copy, + isArray: isArray, + equals: equals, + extend: extend, + isFunction: isFunction, + isUndefined: isUndefined, + isDefined: isDefined, + isString: isString, + isNumber: isNumber, + isObject: isObject + }; + + if (typeof (window.Utilities) === 'undefined') { + window.Utilities = _utilities; + } +})(window); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index 0dac7176ee..d435c6ead7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -2,7 +2,7 @@
- - - diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js index 41097f9e9a..47b0215dac 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js @@ -39,8 +39,14 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", stylesheetResource.getAll().then(function(stylesheets){ $scope.stylesheets = stylesheets; - - _.each($scope.stylesheets, function (stylesheet) { + + // if the CSS directory changes, previously assigned stylesheets are retained, but will not be visible + // and will throw a 404 when loading the RTE. Remove them here. Still needs to be saved... + let cssPath = Umbraco.Sys.ServerVariables.umbracoSettings.cssPath; + $scope.model.value.stylesheets = $scope.model.value.stylesheets + .filter(sheet => sheet.startsWith(cssPath)); + + $scope.stylesheets.forEach(stylesheet => { // support both current format (full stylesheet path) and legacy format (stylesheet name only) stylesheet.selected = $scope.model.value.stylesheets.indexOf(stylesheet.path) >= 0 ||$scope.model.value.stylesheets.indexOf(stylesheet.name) >= 0; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 0d012de068..f3c2e4e259 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -153,7 +153,7 @@ extendedSave(saved).then(function(result) { //if all is good, then reset the form formHelper.resetForm({ scope: $scope }); - }, angular.noop); + }, Utilities.noop); vm.user = _.omit(saved, "navigation"); //restore diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js index 984866dca1..31c52a344b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js @@ -112,7 +112,7 @@ userGroupsResource.deleteUserGroups(_.pluck(vm.selection, "id")).then(function (data) { clearSelection(); onInit(); - }, angular.noop); + }, Utilities.noop); overlayService.close(); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index f3c28fdb9d..4ac385921e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -386,7 +386,7 @@ vm.selectedBulkUserGroups = []; editorService.close(); clearSelection(); - }, angular.noop); + }, Utilities.noop); }, close: function () { vm.selectedBulkUserGroups = []; diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 55a2a93cb9..51ab151463 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -6,10 +6,15 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Umbraco.Composing; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Web.BackOffice.AspNetCore; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Extensions; using Umbraco.Web.Website.AspNetCore; +using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Web.UI.BackOffice @@ -39,7 +44,7 @@ namespace Umbraco.Web.UI.BackOffice { services.AddUmbracoConfiguration(_config); services.AddUmbracoRuntimeMinifier(_config); - services.AddUmbracoCore(_webHostEnvironment); + services.AddUmbracoCore(_webHostEnvironment, out var factory); services.AddUmbracoWebsite(); services.AddMvc(); @@ -47,6 +52,17 @@ namespace Umbraco.Web.UI.BackOffice { options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile }); + + //Finally initialize Current + Current.Initialize( + factory.GetInstance (), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance() + ); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml index 557529fb5e..8388c7a90d 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml @@ -2,7 +2,6 @@ @using Umbraco.Web @using Umbraco.Core @using Umbraco.Core.Media -@using Umbraco.Core.Models @using Umbraco.Web.Composing @inherits Umbraco.Web.Macros.PartialViewMacroPage diff --git a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js b/src/Umbraco.Web.UI/Umbraco/js/main.controller.js deleted file mode 100644 index 883907d1dc..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js +++ /dev/null @@ -1,220 +0,0 @@ - -/** - * @ngdoc controller - * @name Umbraco.MainController - * @function - * - * @description - * The main application controller - * - */ -function MainController($scope, $location, appState, treeService, notificationsService, - userService, historyService, updateChecker, navigationService, eventsService, - tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) { - - //the null is important because we do an explicit bool check on this in the view - $scope.authenticated = null; - $scope.touchDevice = appState.getGlobalState("touchDevice"); - $scope.infiniteMode = false; - $scope.overlay = {}; - $scope.drawer = {}; - $scope.search = {}; - $scope.login = {}; - $scope.tabbingActive = false; - - // Load TinyMCE assets ahead of time in the background for the user - // To help with first load of the RTE - tinyMceAssets.forEach(function (tinyJsAsset) { - assetsService.loadJs(tinyJsAsset, $scope); - }); - - // There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution. - // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 - function handleFirstTab(evt) { - if (evt.keyCode === 9) { - $scope.tabbingActive = true; - $scope.$digest(); - window.removeEventListener('keydown', handleFirstTab); - window.addEventListener('mousedown', disableTabbingActive); - } - } - - function disableTabbingActive(evt) { - $scope.tabbingActive = false; - $scope.$digest(); - window.removeEventListener('mousedown', disableTabbingActive); - window.addEventListener("keydown", handleFirstTab); - } - - window.addEventListener("keydown", handleFirstTab); - - - $scope.removeNotification = function (index) { - notificationsService.remove(index); - }; - - $scope.closeSearch = function() { - appState.setSearchState("show", false); - }; - - $scope.showLoginScreen = function(isTimedOut) { - $scope.login.isTimedOut = isTimedOut; - $scope.login.show = true; - }; - - $scope.hideLoginScreen = function() { - $scope.login.show = false; - }; - - var evts = []; - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function (evt, data) { - $scope.authenticated = null; - $scope.user = null; - const isTimedOut = data && data.isTimedOut ? true : false; - $scope.showLoginScreen(isTimedOut); - - // Remove the localstorage items for tours shown - // Means that when next logged in they can be re-shown if not already dismissed etc - localStorageService.remove("emailMarketingTourShown"); - localStorageService.remove("introTourShown"); - })); - - evts.push(eventsService.on("app.userRefresh", function(evt) { - userService.refreshCurrentUser().then(function(data) { - $scope.user = data; - - //Load locale file - if ($scope.user.locale) { - tmhDynamicLocale.set($scope.user.locale); - } - }); - })); - - //when the app is ready/user is logged in, setup the data - evts.push(eventsService.on("app.ready", function (evt, data) { - - $scope.authenticated = data.authenticated; - $scope.user = data.user; - - updateChecker.check().then(function (update) { - if (update && update !== "null") { - if (update.type !== "None") { - var notification = { - headline: "Update available", - message: "Click to download", - sticky: true, - type: "info", - url: update.url - }; - notificationsService.add(notification); - } - } - }); - - //if the user has changed we need to redirect to the root so they don't try to continue editing the - //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true) - if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) { - - var section = appState.getSectionState("currentSection"); - if (section) { - //if there's a section already assigned, reload it so the tree is cleared - navigationService.reloadSection(section); - } - - $location.path("/").search(""); - historyService.removeAll(); - treeService.clearCache(); - editorService.closeAll(); - overlayService.close(); - - //if the user changed, clearout local storage too - could contain sensitive data - localStorageService.clearAll(); - } - - //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data - if (data.loginType === "credentials") { - localStorageService.clearAll(); - } - - //Load locale file - if ($scope.user.locale) { - tmhDynamicLocale.set($scope.user.locale); - } - - })); - - // events for search - evts.push(eventsService.on("appState.searchState.changed", function (e, args) { - if (args.key === "show") { - $scope.search.show = args.value; - } - })); - - // events for drawer - // manage the help dialog by subscribing to the showHelp appState - evts.push(eventsService.on("appState.drawerState.changed", function (e, args) { - // set view - if (args.key === "view") { - $scope.drawer.view = args.value; - } - // set custom model - if (args.key === "model") { - $scope.drawer.model = args.value; - } - // show / hide drawer - if (args.key === "showDrawer") { - $scope.drawer.show = args.value; - } - })); - - // events for overlays - evts.push(eventsService.on("appState.overlay", function (name, args) { - $scope.overlay = args; - })); - - // events for tours - evts.push(eventsService.on("appState.tour.start", function (name, args) { - $scope.tour = args; - $scope.tour.show = true; - })); - - evts.push(eventsService.on("appState.tour.end", function () { - $scope.tour = null; - })); - - evts.push(eventsService.on("appState.tour.complete", function () { - $scope.tour = null; - })); - - // events for backdrop - evts.push(eventsService.on("appState.backdrop", function (name, args) { - $scope.backdrop = args; - })); - - // event for infinite editors - evts.push(eventsService.on("appState.editors.open", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("appState.editors.close", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - -} - - -//register it -angular.module('umbraco').controller("Umbraco.MainController", MainController). - config(function (tmhDynamicLocaleProvider) { - //Set url for locale files - tmhDynamicLocaleProvider.localeLocationPattern('lib/angular-i18n/angular-locale_{{locale}}.js'); - }); diff --git a/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js b/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js deleted file mode 100644 index 281be2d331..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js +++ /dev/null @@ -1,569 +0,0 @@ - -/** - * @ngdoc controller - * @name Umbraco.NavigationController - * @function - * - * @description - * Handles the section area of the app - * - * @param {navigationService} navigationService A reference to the navigationService - */ -function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource, editorState) { - - //this is used to trigger the tree to start loading once everything is ready - var treeInitPromise = $q.defer(); - - $scope.treeApi = {}; - - //Bind to the main tree events - $scope.onTreeInit = function () { - - $scope.treeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); - - //when a tree is loaded into a section, we need to put it into appState - $scope.treeApi.callbacks.treeLoaded(function (args) { - appState.setTreeState("currentRootNode", args.tree); - }); - - //when a tree node is synced this event will fire, this allows us to set the currentNode - $scope.treeApi.callbacks.treeSynced(function (args) { - - if (args.activate === undefined || args.activate === true) { - //set the current selected node - appState.setTreeState("selectedNode", args.node); - //when a node is activated, this is the same as clicking it and we need to set the - //current menu item to be this node as well. - //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. - } - }); - - //this reacts to the options item in the tree - $scope.treeApi.callbacks.treeOptionsClick(function (args) { - args.event.stopPropagation(); - args.event.preventDefault(); - - //Set the current action node (this is not the same as the current selected node!) - //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. - - if (args.event && args.event.altKey) { - args.skipDefault = true; - } - - navigationService.showMenu(args); - }); - - $scope.treeApi.callbacks.treeNodeAltSelect(function (args) { - args.event.stopPropagation(); - args.event.preventDefault(); - - args.skipDefault = true; - navigationService.showMenu(args); - }); - - //this reacts to tree items themselves being clicked - //the tree directive should not contain any handling, simply just bubble events - $scope.treeApi.callbacks.treeNodeSelect(function (args) { - var n = args.node; - args.event.stopPropagation(); - args.event.preventDefault(); - - if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") { - //this is a legacy tree node! - var jsPrefix = "javascript:"; - var js; - if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) { - js = n.metaData["jsClickCallback"].substr(jsPrefix.length); - } - else { - js = n.metaData["jsClickCallback"]; - } - try { - var func = eval(js); - //this is normally not necessary since the eval above should execute the method and will return nothing. - if (func != null && (typeof func === "function")) { - func.call(); - } - } - catch (ex) { - $log.error("Error evaluating js callback from legacy tree node: " + ex); - } - } - else if (n.routePath) { - //add action to the history service - historyService.add({ name: n.name, link: n.routePath, icon: n.icon }); - - //put this node into the tree state - appState.setTreeState("selectedNode", args.node); - //when a node is clicked we also need to set the active menu node to this node - //appState.setMenuState("currentNode", args.node); - - //not legacy, lets just set the route value and clear the query string if there is one. - $location.path(n.routePath); - navigationService.clearSearch(); - } - else if (n.section) { - $location.path(n.section); - navigationService.clearSearch(); - } - - navigationService.hideNavigation(); - }); - - return treeInitPromise.promise; - } - - //set up our scope vars - $scope.showContextMenuDialog = false; - $scope.showContextMenu = false; - $scope.showSearchResults = false; - $scope.menuDialogTitle = null; - $scope.menuActions = []; - $scope.menuNode = null; - $scope.languages = []; - $scope.selectedLanguage = {}; - $scope.page = {}; - $scope.page.languageSelectorIsOpen = false; - - $scope.currentSection = null; - $scope.customTreeParams = null; - $scope.treeCacheKey = "_"; - $scope.showNavigation = appState.getGlobalState("showNavigation"); - // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths - var expandedPaths = []; - - //trigger search with a hotkey: - keyboardService.bind("ctrl+shift+s", function () { - navigationService.showSearch(); - }); - - //// TODO: remove this it's not a thing - //$scope.selectedId = navigationService.currentId; - - var isInit = false; - var evts = []; - - //Listen for global state changes - evts.push(eventsService.on("appState.globalState.changed", function (e, args) { - if (args.key === "showNavigation") { - $scope.showNavigation = args.value; - } - })); - - //Listen for menu state changes - evts.push(eventsService.on("appState.menuState.changed", function (e, args) { - if (args.key === "showMenuDialog") { - $scope.showContextMenuDialog = args.value; - } - if (args.key === "dialogTemplateUrl") { - $scope.dialogTemplateUrl = args.value; - } - if (args.key === "showMenu") { - $scope.showContextMenu = args.value; - } - if (args.key === "dialogTitle") { - $scope.menuDialogTitle = args.value; - } - if (args.key === "menuActions") { - $scope.menuActions = args.value; - } - if (args.key === "currentNode") { - $scope.menuNode = args.value; - } - })); - - //Listen for tree state changes - evts.push(eventsService.on("appState.treeState.changed", function (e, args) { - if (args.key === "currentRootNode") { - - //if the changed state is the currentRootNode, determine if this is a full screen app - if (args.value.root && args.value.root.containsTrees === false) { - $rootScope.emptySection = true; - } - else { - $rootScope.emptySection = false; - } - } - - })); - - //Listen for section state changes - evts.push(eventsService.on("appState.sectionState.changed", function (e, args) { - - //section changed - if (args.key === "currentSection" && $scope.currentSection != args.value) { - //before loading the main tree we need to ensure that the nav is ready - navigationService.waitForNavReady().then(() => { - $scope.currentSection = args.value; - //load the tree - configureTreeAndLanguages(); - $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }); - }); - } - - //show/hide search results - if (args.key === "showSearchResults") { - $scope.showSearchResults = args.value; - } - - })); - - // Listen for language updates - evts.push(eventsService.on("editors.languages.languageDeleted", function (e, args) { - loadLanguages().then(function (languages) { - $scope.languages = languages; - const defaultCulture = $scope.languages[0].culture; - - if (args.language.culture === $scope.selectedLanguage.culture) { - $scope.selectedLanguage = defaultCulture; - - if ($scope.languages.length > 1) { - $location.search("mculture", defaultCulture); - } else { - $location.search("mculture", null); - } - - var currentEditorState = editorState.getCurrent(); - if (currentEditorState && currentEditorState.path) { - $scope.treeApi.syncTree({ path: currentEditorState.path, activate: true }); - } - } - }); - })); - - //Emitted when a language is created or an existing one saved/edited - evts.push(eventsService.on("editors.languages.languageSaved", function (e, args) { - if(args.isNew){ - //A new language has been created - reload languages for tree - loadLanguages().then(function (languages) { - $scope.languages = languages; - }); - } - else if(args.language.isDefault){ - //A language was saved and was set to be the new default (refresh the tree, so its at the top) - loadLanguages().then(function (languages) { - $scope.languages = languages; - }); - } - })); - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function () { - $scope.authenticated = false; - })); - - //when the application is ready and the user is authorized, setup the data - //this will occur anytime a new user logs in! - evts.push(eventsService.on("app.ready", function (evt, data) { - $scope.authenticated = true; - ensureInit(); - ensureMainCulture(); - })); - - // event for infinite editors - evts.push(eventsService.on("appState.editors.open", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("appState.editors.close", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("treeService.removeNode", function (e, args) { - //check to see if the current page has been removed - - var currentEditorState = editorState.getCurrent(); - if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) { - //current page is loaded, so navigate to root - var section = appState.getSectionState("currentSection"); - $location.path("/" + section); - } - })); - - /** - * For multi language sites, this ensures that mculture is set to either the last selected language or the default one - */ - function ensureMainCulture() { - if ($location.search().mculture) { - return; - } - var language = lastLanguageOrDefault(); - if (!language) { - return; - } - // trigger a language selection in the next digest cycle - $timeout(function () { - $scope.selectLanguage(language); - }); - } - - /** - * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down - */ - function configureTreeAndLanguages() { - - //create the custom query string param for this tree, this is currently only relevant for content - if ($scope.currentSection === "content") { - - //must use $location here because $routeParams isn't available until after the route change - var mainCulture = $location.search().mculture; - //select the current language if set in the query string - if (mainCulture && $scope.languages && $scope.languages.length > 1) { - var found = _.find($scope.languages, function (l) { - if (mainCulture === true) { - return false; - } - return l.culture.toLowerCase() === mainCulture.toLowerCase(); - }); - if (found) { - //set the route param - found.active = true; - $scope.selectedLanguage = found; - } - } - - var queryParams = {}; - if ($scope.selectedLanguage && $scope.selectedLanguage.culture) { - queryParams["culture"] = $scope.selectedLanguage.culture; - } - var queryString = $.param(queryParams); //create the query string from the params object - } - - if (queryString) { - $scope.customTreeParams = queryString; - $scope.treeCacheKey = queryString; // this tree uses caching but we need to change it's cache key per lang - } - else { - $scope.treeCacheKey = "_"; // this tree uses caching, there's no lang selected so use the default - } - - } - - /** - * Called when the app is ready and sets up the navigation (should only be called once) - */ - function ensureInit() { - - //only run once ever! - if (isInit) { - return; - } - - isInit = true; - - var navInit = false; - - //$routeParams will be populated after $routeChangeSuccess since this controller is used outside ng-view, - //* we listen for the first route change with a section to setup the navigation. - //* we listen for all route changes to track the current section. - $rootScope.$on('$routeChangeSuccess', function () { - - //only continue if there's a section available - if ($routeParams.section) { - - if (!navInit) { - navInit = true; - initNav(); - } - - //keep track of the current section when it changes - if ($scope.currentSection != $routeParams.section) { - appState.setSectionState("currentSection", $routeParams.section); - } - - } - }); - } - - /** - * This loads the language data, if the are no variant content types configured this will return no languages - */ - function loadLanguages() { - - return contentResource.allowsCultureVariation().then(function (b) { - if (b === true) { - return languageResource.getAll(); - } else { - return $q.when([]); //resolve an empty collection - } - }); - } - - /** - * Called once during init to initialize the navigation/tree/languages - */ - function initNav() { - // load languages - loadLanguages().then(function (languages) { - - $scope.languages = languages; - - if ($scope.languages.length > 1) { - //if there's already one set, check if it exists - var language = null; - var mainCulture = $location.search().mculture; - if (mainCulture) { - language = _.find($scope.languages, function (l) { - return l.culture.toLowerCase() === mainCulture.toLowerCase(); - }); - } - if (!language) { - language = lastLanguageOrDefault(); - - if (language) { - $location.search("mculture", language.culture); - } - } - } - - $scope.currentSection = $routeParams.section; - - configureTreeAndLanguages(); - - //resolve the tree promise, set it's property values for loading the tree which will make the tree load - treeInitPromise.resolve({ - section: $scope.currentSection, - customTreeParams: $scope.customTreeParams, - cacheKey: $scope.treeCacheKey, - - //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else - //like normal JS promises we could do resolve(...).then() - onLoaded: function () { - - //the nav is ready, let the app know - eventsService.emit("app.navigationReady", { treeApi: $scope.treeApi }); - - } - }); - }); - } - - function lastLanguageOrDefault() { - if (!$scope.languages || $scope.languages.length <= 1) { - return null; - } - // see if we can find a culture in the cookie set when changing language - var lastCulture = $cookies.get("UMB_MCULTURE"); - var language = lastCulture ? _.find($scope.languages, function (l) { - return l.culture.toLowerCase() === lastCulture.toLowerCase(); - }) : null; - if (!language) { - // no luck, look for the default language - language = _.find($scope.languages, function (l) { - return l.isDefault; - }); - } - return language; - } - - function nodeExpandedHandler(args) { - //store the reference to the expanded node path - if (args.node) { - treeService._trackExpandedPaths(args.node, expandedPaths); - } - } - - $scope.selectLanguage = function (language) { - - $location.search("mculture", language.culture); - // add the selected culture to a cookie so the user will log back into the same culture later on (cookie lifetime = one year) - var expireDate = new Date(); - expireDate.setDate(expireDate.getDate() + 365); - $cookies.put("UMB_MCULTURE", language.culture, {path: "/", expires: expireDate}); - - // close the language selector - $scope.page.languageSelectorIsOpen = false; - - configureTreeAndLanguages(); //re-bind language to the query string and update the tree params - - //reload the tree with it's updated querystring args - $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }).then(function () { - - //re-sync to currently edited node - var currNode = appState.getTreeState("selectedNode"); - //create the list of promises - var promises = []; - //starting with syncing to the currently selected node if there is one - if (currNode) { - var path = treeService.getPath(currNode); - promises.push($scope.treeApi.syncTree({ path: path, activate: true })); - } - // TODO: If we want to keep all paths expanded ... but we need more testing since we need to deal with unexpanding - //for (var i = 0; i < expandedPaths.length; i++) { - // promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); - //} - //execute them sequentially - - // set selected language to active - angular.forEach($scope.languages, function(language){ - language.active = false; - }); - language.active = true; - - angularHelper.executeSequentialPromises(promises); - }); - - }; - - //this reacts to the options item in the tree - // TODO: migrate to nav service - // TODO: is this used? - $scope.searchShowMenu = function (ev, args) { - //always skip default - args.skipDefault = true; - navigationService.showMenu(args); - }; - - // TODO: migrate to nav service - // TODO: is this used? - $scope.searchHide = function () { - navigationService.hideSearch(); - }; - - //the below assists with hiding/showing the tree - var treeActive = false; - - //Sets a service variable as soon as the user hovers the navigation with the mouse - //used by the leaveTree method to delay hiding - $scope.enterTree = function (event) { - treeActive = true; - }; - - // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again - $scope.leaveTree = function (event) { - //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down - if (!event) { - return; - } - closeTree(); - }; - - $scope.onOutsideClick = function() { - closeTree(); - }; - - function closeTree() { - if (!appState.getGlobalState("touchDevice")) { - treeActive = false; - $timeout(function () { - if (!treeActive) { - navigationService.hideTree(); - } - }, 300); - } - } - - $scope.toggleLanguageSelector = function () { - $scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen; - }; - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); -} - -//register it -angular.module('umbraco').controller("Umbraco.NavigationController", NavigationController); diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml index 9e5daaad5d..9d91abc34a 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml @@ -21,7 +21,7 @@ var altText = Model.value.altText ?? Model.value.caption ?? string.Empty; @altText - + if (Model.value.caption != null) {

@Model.value.caption

diff --git a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs index 8c43293ad7..f0ff6e3cad 100644 --- a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs @@ -5,7 +5,7 @@ using Umbraco.Net; namespace Umbraco.Web.AspNet { - public class AspNetUmbracoApplicationLifetime : IUmbracoApplicationLifetimeManager + public class AspNetUmbracoApplicationLifetime : IUmbracoApplicationLifetime, IUmbracoApplicationLifetimeManager { private readonly IHttpContextAccessor _httpContextAccessor; diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 8d5e8a9071..dfe1e59fc0 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -230,21 +230,27 @@ namespace Umbraco.Web } } - if (!lengthReached && currentTextLength >= length) + if (!lengthReached) { - // if the last character added was the first of a two character unicode pair, add the second character - if (Char.IsHighSurrogate((char)ic)) + if (currentTextLength == length) { - var lowSurrogate = tr.Read(); - outputtw.Write((char)lowSurrogate); - } + // if the last character added was the first of a two character unicode pair, add the second character + if (char.IsHighSurrogate((char)ic)) + { + var lowSurrogate = tr.Read(); + outputtw.Write((char)lowSurrogate); + } - // Reached truncate limit. - if (addElipsis) - { - outputtw.Write(hellip); } - lengthReached = true; + // only add elipsis if current length greater than original length + if (currentTextLength > length) + { + if (addElipsis) + { + outputtw.Write(hellip); + } + lengthReached = true; + } } } diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 2b7e5390f7..624395049e 100644 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -66,12 +66,18 @@ namespace Umbraco.Web.Macros #region MacroContent cache // gets this macro content cache identifier - private string GetContentCacheIdentifier(MacroModel model, int pageId) + private string GetContentCacheIdentifier(MacroModel model, int pageId, string cultureName) { var id = new StringBuilder(); var alias = model.Alias; id.AppendFormat("{0}-", alias); + //always add current culture to the key to allow variants to have different cache results + if (!string.IsNullOrEmpty(cultureName)) + { + // are there any unusual culture formats we'd need to handle? + id.AppendFormat("{0}-", cultureName); + } if (model.CacheByPage) id.AppendFormat("{0}-", pageId); @@ -220,7 +226,8 @@ namespace Umbraco.Web.Macros foreach (var prop in macro.Properties) prop.Value = ParseAttribute(prop.Value); - macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id); + var cultureName = System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id, cultureName); // get the macro from cache if it is there var macroContent = GetMacroContentFromCache(macro); diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index bcbcb8e08f..14544d6c60 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -289,7 +289,7 @@ namespace Umbraco.Web /// The specific culture to filter for. If null is used the current culture is used. (Default is null) /// /// - /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot + /// This can be useful in order to return all nodes in an entire site by a type when combined with or similar. /// public static IEnumerable DescendantsOrSelfOfType(this IEnumerable parentNodes, string docTypeAlias, string culture = null) { @@ -303,7 +303,7 @@ namespace Umbraco.Web /// The specific culture to filter for. If null is used the current culture is used. (Default is null) /// /// - /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot + /// This can be useful in order to return all nodes in an entire site by a type when combined with or similar. /// public static IEnumerable DescendantsOrSelf(this IEnumerable parentNodes, string culture = null) where T : class, IPublishedContent diff --git a/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs index 283f6b6b99..ecb11dc195 100644 --- a/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Security string[] defaultUserGroups = null, string defaultCulture = null) { - _defaultUserGroups = defaultUserGroups ?? new[] { "editor" }; + _defaultUserGroups = defaultUserGroups ?? new[] { Constants.Security.EditorGroupAlias }; _autoLinkExternalAccount = autoLinkExternalAccount; _defaultCulture = defaultCulture ?? Current.Configs.Global().DefaultUILanguage; }