diff --git a/.gitignore b/.gitignore
index 2ed26621df..7eef7463e5 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.Configuration/Models/HostingSettings.cs b/src/Umbraco.Configuration/Models/HostingSettings.cs
index 4a156cee6a..7cfc08d2d4 100644
--- a/src/Umbraco.Configuration/Models/HostingSettings.cs
+++ b/src/Umbraco.Configuration/Models/HostingSettings.cs
@@ -22,6 +22,6 @@ namespace Umbraco.Configuration.Models
/// Gets a value indicating whether umbraco is running in [debug mode].
///
/// true if [debug mode]; otherwise, false.
- public bool DebugMode => _configuration.GetValue(Prefix+":Debug", false);
+ public bool DebugMode => _configuration.GetValue(Prefix+"Debug", false);
}
}
diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs
index f7c81d6d7e..5ff83bdfd7 100644
--- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs
+++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs
@@ -23,6 +23,9 @@ namespace Umbraco.Core.Composing
"Umbraco.PublishedCache.NuCache",
"Umbraco.ModelsBuilder.Embedded",
"Umbraco.Examine.Lucene",
+ "Umbraco.Web.Common",
+ "Umbraco.Web.BackOffice",
+ "Umbraco.Web.Website",
};
public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly)
diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs
index c45229d918..a34ad8294f 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:";
}
}
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/IImageUrlGenerator.cs b/src/Umbraco.Core/Media/IImageUrlGenerator.cs
similarity index 66%
rename from src/Umbraco.Core/Models/IImageUrlGenerator.cs
rename to src/Umbraco.Core/Media/IImageUrlGenerator.cs
index 0dc2933fd9..f9e67cdd2b 100644
--- a/src/Umbraco.Core/Models/IImageUrlGenerator.cs
+++ b/src/Umbraco.Core/Media/IImageUrlGenerator.cs
@@ -1,4 +1,6 @@
-namespace Umbraco.Core.Models
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.Media
{
public interface IImageUrlGenerator
{
diff --git a/src/Umbraco.Core/Models/IKeyValue.cs b/src/Umbraco.Core/Models/IKeyValue.cs
new file mode 100644
index 0000000000..6025d4d37b
--- /dev/null
+++ b/src/Umbraco.Core/Models/IKeyValue.cs
@@ -0,0 +1,12 @@
+using System;
+using Umbraco.Core.Models.Entities;
+
+namespace Umbraco.Core.Models
+{
+ public interface IKeyValue : IEntity
+ {
+ string Identifier { get; set; }
+
+ string Value { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/KeyValue.cs b/src/Umbraco.Core/Models/KeyValue.cs
new file mode 100644
index 0000000000..2d47fcbfb3
--- /dev/null
+++ b/src/Umbraco.Core/Models/KeyValue.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Runtime.Serialization;
+using Umbraco.Core.Models.Entities;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Implements .
+ ///
+ [Serializable]
+ [DataContract(IsReference = true)]
+ public class KeyValue : EntityBase, IKeyValue, IEntity
+ {
+ private string _identifier;
+ private string _value;
+
+ ///
+ public string Identifier
+ {
+ get => _identifier;
+ set => SetPropertyValueAndDetectChanges(value, ref _identifier, nameof(Identifier));
+ }
+
+ ///
+ public string Value
+ {
+ get => _value;
+ set => SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value));
+ }
+
+ bool IEntity.HasIdentity => !string.IsNullOrEmpty(Identifier);
+ }
+}
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/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs
index 3e5361a1c1..a032720d46 100644
--- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs
+++ b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs
@@ -1,3 +1,5 @@
+using System;
+
namespace Umbraco.Net
{
// TODO: This shouldn't be in this namespace?
@@ -11,5 +13,13 @@ namespace Umbraco.Net
/// Terminates the current application. The application restarts the next time a request is received for it.
///
void Restart();
+
+ event EventHandler ApplicationInit;
+ }
+
+
+ public interface IUmbracoApplicationLifetimeManager
+ {
+ void InvokeApplicationInit();
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IKeyValueRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IKeyValueRepository.cs
new file mode 100644
index 0000000000..0eb2b27eb0
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/IKeyValueRepository.cs
@@ -0,0 +1,8 @@
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.Persistence.Repositories
+{
+ public interface IKeyValueRepository : IReadRepository, IWriteRepository
+ {
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs
index 29ae581459..0d1a8764ae 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs
@@ -20,8 +20,24 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
///
+ [Obsolete("This method will be removed in future versions. Please use ExistsByUserName instead.")]
bool Exists(string username);
+ ///
+ /// Checks if a user with the username exists
+ ///
+ ///
+ ///
+ bool ExistsByUserName(string username);
+
+
+ ///
+ /// Checks if a user with the login exists
+ ///
+ ///
+ ///
+ bool ExistsByLogin(string login);
+
///
/// Gets a list of objects associated with a given group
///
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/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs
index f3c95b07f9..c9bdc6d924 100644
--- a/src/Umbraco.Core/Services/ServiceContext.cs
+++ b/src/Umbraco.Core/Services/ServiceContext.cs
@@ -32,12 +32,13 @@ namespace Umbraco.Core.Services
private readonly Lazy _externalLoginService;
private readonly Lazy _redirectUrlService;
private readonly Lazy _consentService;
+ private readonly Lazy _keyValueService;
private readonly Lazy _contentTypeBaseServiceProvider;
///
/// Initializes a new instance of the class with lazy services.
///
- public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService, Lazy contentTypeBaseServiceProvider)
+ public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService, Lazy keyValueService, Lazy contentTypeBaseServiceProvider)
{
_publicAccessService = publicAccessService;
_domainService = domainService;
@@ -64,6 +65,7 @@ namespace Umbraco.Core.Services
_externalLoginService = externalLoginService;
_redirectUrlService = redirectUrlService;
_consentService = consentService;
+ _keyValueService = keyValueService;
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
}
@@ -99,6 +101,7 @@ namespace Umbraco.Core.Services
IServerRegistrationService serverRegistrationService = null,
IRedirectUrlService redirectUrlService = null,
IConsentService consentService = null,
+ IKeyValueService keyValueService = null,
IContentTypeBaseServiceProvider contentTypeBaseServiceProvider = null)
{
Lazy Lazy(T service) => service == null ? null : new Lazy(() => service);
@@ -129,6 +132,7 @@ namespace Umbraco.Core.Services
Lazy(externalLoginService),
Lazy(redirectUrlService),
Lazy(consentService),
+ Lazy(keyValueService),
Lazy(contentTypeBaseServiceProvider)
);
}
@@ -258,6 +262,11 @@ namespace Umbraco.Core.Services
///
public IConsentService ConsentService => _consentService.Value;
+ ///
+ /// Gets the KeyValueService.
+ ///
+ public IKeyValueService KeyValueService => _keyValueService.Value;
+
///
/// Gets the ContentTypeServiceBaseFactory.
///
diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs
index d61c0a87ca..c12b6799a4 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.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs
index 4321aefd7f..e7be0b6500 100644
--- a/src/Umbraco.Core/UriExtensions.cs
+++ b/src/Umbraco.Core/UriExtensions.cs
@@ -146,7 +146,7 @@ namespace Umbraco.Core
///
///
///
- internal static bool IsClientSideRequest(this Uri url)
+ public static bool IsClientSideRequest(this Uri url)
{
try
{
diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs
index 61ff4ecb6d..9a23255cc4 100644
--- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs
+++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs
@@ -47,6 +47,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
composition.RegisterUnique();
composition.RegisterUnique();
composition.RegisterUnique();
+ composition.RegisterUnique();
composition.RegisterUnique();
composition.RegisterUnique();
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/Models/ImageProcessorImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageProcessorImageUrlGenerator.cs
similarity index 67%
rename from src/Umbraco.Infrastructure/Models/ImageProcessorImageUrlGenerator.cs
rename to src/Umbraco.Infrastructure/Media/ImageProcessorImageUrlGenerator.cs
index f88309d7e3..f1a0121653 100644
--- a/src/Umbraco.Infrastructure/Models/ImageProcessorImageUrlGenerator.cs
+++ b/src/Umbraco.Infrastructure/Media/ImageProcessorImageUrlGenerator.cs
@@ -1,9 +1,10 @@
using System.Globalization;
using System.Text;
using Umbraco.Core;
+using Umbraco.Core.Media;
using Umbraco.Core.Models;
-namespace Umbraco.Web.Models
+namespace Umbraco.Infrastructure.Media
{
public class ImageProcessorImageUrlGenerator : IImageUrlGenerator
{
@@ -27,18 +28,18 @@ namespace Umbraco.Web.Models
//Only put quality here, if we don't have a format specified.
//Otherwise we need to put quality at the end to avoid it being overridden by the format.
- if (options.Quality != null && hasFormat == false) imageProcessorUrl.Append("&quality=").Append(options.Quality);
- if (options.HeightRatio != null) imageProcessorUrl.Append("&heightratio=").Append(options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture));
- if (options.WidthRatio != null) imageProcessorUrl.Append("&widthratio=").Append(options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture));
- if (options.Width != null) imageProcessorUrl.Append("&width=").Append(options.Width);
- if (options.Height != null) imageProcessorUrl.Append("&height=").Append(options.Height);
+ if (options.Quality.HasValue && hasFormat == false) imageProcessorUrl.Append("&quality=").Append(options.Quality);
+ if (options.HeightRatio.HasValue) imageProcessorUrl.Append("&heightratio=").Append(options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture));
+ if (options.WidthRatio.HasValue) imageProcessorUrl.Append("&widthratio=").Append(options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture));
+ if (options.Width.HasValue) imageProcessorUrl.Append("&width=").Append(options.Width);
+ if (options.Height.HasValue) imageProcessorUrl.Append("&height=").Append(options.Height);
if (options.UpScale == false) imageProcessorUrl.Append("&upscale=false");
- if (options.AnimationProcessMode != null) imageProcessorUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode);
- if (options.FurtherOptions != null) imageProcessorUrl.Append(options.FurtherOptions);
+ if (!string.IsNullOrWhiteSpace(options.AnimationProcessMode)) imageProcessorUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode);
+ if (!string.IsNullOrWhiteSpace(options.FurtherOptions)) imageProcessorUrl.Append(options.FurtherOptions);
//If furtherOptions contains a format, we need to put the quality after the format.
- if (options.Quality != null && hasFormat) imageProcessorUrl.Append("&quality=").Append(options.Quality);
- if (options.CacheBusterValue != null) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue);
+ if (options.Quality.HasValue && hasFormat) imageProcessorUrl.Append("&quality=").Append(options.Quality);
+ if (!string.IsNullOrWhiteSpace(options.CacheBusterValue)) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue);
return imageProcessorUrl.ToString();
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
index da6574670b..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" });
}
@@ -344,7 +344,7 @@ namespace Umbraco.Core.Migrations.Install
var stateValueKey = upgrader.StateValueKey;
var finalState = upgrader.Plan.FinalState;
- _database.Insert(Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, Updated = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, UpdateDate = DateTime.Now });
}
}
}
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/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Infrastructure/Models/Mapping/UserMapDefinition.cs
index 19a78bcbe4..80f3a8bde8 100644
--- a/src/Umbraco.Infrastructure/Models/Mapping/UserMapDefinition.cs
+++ b/src/Umbraco.Infrastructure/Models/Mapping/UserMapDefinition.cs
@@ -16,6 +16,7 @@ using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Actions;
using Umbraco.Web.Services;
+using Umbraco.Core.Media;
namespace Umbraco.Web.Models.Mapping
{
diff --git a/src/Umbraco.Infrastructure/Models/UserExtensions.cs b/src/Umbraco.Infrastructure/Models/UserExtensions.cs
index 0c0e4fbc48..4fefc89d11 100644
--- a/src/Umbraco.Infrastructure/Models/UserExtensions.cs
+++ b/src/Umbraco.Infrastructure/Models/UserExtensions.cs
@@ -9,6 +9,7 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Core.Security;
+using Umbraco.Core.Media;
namespace Umbraco.Core.Models
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs
index b74039c388..5ead6d0d26 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs
@@ -21,6 +21,6 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("updated")]
[Constraint(Default = SystemMethods.CurrentDateTime)]
- public DateTime Updated { get; set; }
+ public DateTime UpdateDate { get; set; }
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs
new file mode 100644
index 0000000000..3c2da4d5b7
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NPoco;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Persistence.Dtos;
+using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Scoping;
+
+namespace Umbraco.Core.Persistence.Repositories.Implement
+{
+ internal class KeyValueRepository : NPocoRepositoryBase, IKeyValueRepository
+ {
+ public KeyValueRepository(IScopeAccessor scopeAccessor, ILogger logger)
+ : base(scopeAccessor, AppCaches.NoCache, logger)
+ { }
+
+ #region Overrides of IReadWriteQueryRepository
+
+ public override void Save(IKeyValue entity)
+ {
+ if (Get(entity.Identifier) == null)
+ PersistNewItem(entity);
+ else
+ PersistUpdatedItem(entity);
+ }
+
+ #endregion
+
+ #region Overrides of NPocoRepositoryBase
+
+ protected override Guid NodeObjectTypeId => throw new NotSupportedException();
+
+ protected override Sql GetBaseQuery(bool isCount)
+ {
+ var sql = SqlContext.Sql();
+
+ sql = isCount
+ ? sql.SelectCount()
+ : sql.Select();
+
+ sql
+ .From();
+
+ return sql;
+ }
+
+ protected override string GetBaseWhereClause()
+ {
+ return Constants.DatabaseSchema.Tables.KeyValue + ".key = @id";
+ }
+
+ protected override IEnumerable GetDeleteClauses()
+ {
+ return Enumerable.Empty();
+ }
+
+ protected override IKeyValue PerformGet(string id)
+ {
+ var sql = GetBaseQuery(false).Where(x => x.Key == id);
+ var dto = Database.Fetch(sql).FirstOrDefault();
+ return dto == null ? null : Map(dto);
+ }
+
+ protected override IEnumerable PerformGetAll(params string[] ids)
+ {
+ var sql = GetBaseQuery(false).WhereIn(x => x.Key, ids);
+ var dtos = Database.Fetch(sql);
+ return dtos.WhereNotNull().Select(Map);
+ }
+
+ protected override IEnumerable PerformGetByQuery(IQuery query)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected override void PersistNewItem(IKeyValue entity)
+ {
+ var dto = Map(entity);
+ Database.Insert(dto);
+ }
+
+ protected override void PersistUpdatedItem(IKeyValue entity)
+ {
+ var dto = Map(entity);
+ Database.Update(dto);
+ }
+
+ private static KeyValueDto Map(IKeyValue keyValue)
+ {
+ if (keyValue == null) return null;
+
+ return new KeyValueDto
+ {
+ Key = keyValue.Identifier,
+ Value = keyValue.Value,
+ UpdateDate = keyValue.UpdateDate,
+ };
+ }
+
+ private static IKeyValue Map(KeyValueDto dto)
+ {
+ if (dto == null) return null;
+
+ return new KeyValue
+ {
+ Identifier = dto.Key,
+ Value = dto.Value,
+ UpdateDate = dto.UpdateDate,
+ };
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
index e8397ba22a..6985bf78da 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
@@ -132,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// This method is backed by an cache
///
- public void Save(TEntity entity)
+ public virtual void Save(TEntity entity)
{
if (entity.HasIdentity == false)
CachePolicy.Create(entity, PersistNewItem);
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/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
index 2f1b6c5814..c8de108db8 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
@@ -636,6 +636,11 @@ ORDER BY colName";
}
public bool Exists(string username)
+ {
+ return ExistsByUserName(username);
+ }
+
+ public bool ExistsByUserName(string username)
{
var sql = SqlContext.Sql()
.SelectCount()
@@ -645,6 +650,16 @@ ORDER BY colName";
return Database.ExecuteScalar(sql) > 0;
}
+ public bool ExistsByLogin(string login)
+ {
+ var sql = SqlContext.Sql()
+ .SelectCount()
+ .From()
+ .Where(x => x.Login == login);
+
+ return Database.ExecuteScalar(sql) > 0;
+ }
+
///
/// Gets a list of objects associated with a given group
///
diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs
index 249dd3dc73..4e5c3a113a 100644
--- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs
+++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Persistence
{
@@ -10,5 +11,20 @@ namespace Umbraco.Core.Persistence
if (asDatabase == null) throw new Exception("oops: database.");
return asDatabase;
}
+
+ ///
+ /// Gets a key/value directly from the database, no scope, nothing.
+ ///
+ /// Used by to determine the runtime state.
+ public static string GetFromKeyValueTable(this IUmbracoDatabase database, string key)
+ {
+ if (database is null) return null;
+
+ var sql = database.SqlContext.Sql()
+ .Select()
+ .From()
+ .Where(x => x.Key == key);
+ return database.FirstOrDefault(sql)?.Value;
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs
index 72e0843240..1a8e4524c7 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs
@@ -5,6 +5,7 @@ using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
+using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs
index eee45a636c..38b4611c1e 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs
@@ -6,6 +6,7 @@ using Umbraco.Core;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
+using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs
index dea9a5f002..2f283b2709 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
+using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
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/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs
index 1367c04cdb..5878e51dcd 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs
@@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json;
+using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.Serialization;
using Umbraco.Core.Strings;
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
index 1bf954e4c1..aa9ccb958f 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
@@ -1,5 +1,4 @@
using System;
-using System.IO;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Composing.CompositionExtensions;
@@ -7,9 +6,8 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Grid;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Dashboards;
-using Umbraco.Core.Hosting;
using Umbraco.Core.Dictionary;
-using Umbraco.Core.IO;
+using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Core.Migrations;
@@ -25,14 +23,14 @@ using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
-using Umbraco.Web.Models.PublishedContent;
-using Umbraco.Web.PublishedCache;
using Umbraco.Web;
-using Umbraco.Web.Migrations.PostMigrations;
using Umbraco.Web.Install;
-using Umbraco.Web.Trees;
+using Umbraco.Web.Migrations.PostMigrations;
+using Umbraco.Web.Models.PublishedContent;
using Umbraco.Web.PropertyEditors;
+using Umbraco.Web.PublishedCache;
using Umbraco.Web.Services;
+using Umbraco.Web.Trees;
using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator;
namespace Umbraco.Core.Runtime
diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
index 99f38f0486..85d915b433 100644
--- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
+++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
@@ -324,7 +324,7 @@ namespace Umbraco.Core.Runtime
{
Key = MainDomKey,
Value = id,
- Updated = DateTime.Now
+ UpdateDate = DateTime.Now
});
}
diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs
index e0099c5e7e..d6f674f028 100644
--- a/src/Umbraco.Infrastructure/RuntimeState.cs
+++ b/src/Umbraco.Infrastructure/RuntimeState.cs
@@ -9,7 +9,7 @@ using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Upgrade;
using Umbraco.Core.Persistence;
-using Umbraco.Core.Services.Implement;
+using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Sync;
namespace Umbraco.Core
@@ -225,7 +225,7 @@ namespace Umbraco.Core
// no scope, no service - just directly accessing the database
using (var database = databaseFactory.CreateDatabase())
{
- CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey);
+ CurrentMigrationState = database.GetFromKeyValueTable(stateValueKey);
FinalMigrationState = upgrader.Plan.FinalState;
}
diff --git a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs
index d5d8e66525..12ca34b020 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs
@@ -1,168 +1,55 @@
using System;
-using System.Linq;
-using Umbraco.Core.Composing;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Logging;
-using Umbraco.Core.Migrations;
+using Umbraco.Core.Models;
+using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Scoping;
-using Umbraco.Core.Persistence;
-using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Services.Implement
{
internal class KeyValueService : IKeyValueService
{
- private readonly object _initialock = new object();
private readonly IScopeProvider _scopeProvider;
- private readonly ILogger _logger;
- private readonly IUmbracoVersion _umbracoVersion;
- private bool _initialized;
+ private readonly IKeyValueRepository _repository;
- public KeyValueService(IScopeProvider scopeProvider, ILogger logger, IUmbracoVersion umbracoVersion)
+ public KeyValueService(IScopeProvider scopeProvider, IKeyValueRepository repository)
{
_scopeProvider = scopeProvider;
- _logger = logger;
- _umbracoVersion = umbracoVersion;
- }
-
- private void EnsureInitialized()
- {
- lock (_initialock)
- {
- if (_initialized) return;
- Initialize();
- }
- }
-
- private void Initialize()
- {
- // the key/value service is entirely self-managed, because it is used by the
- // upgrader and anything we might change need to happen before everything else
-
- // if already running 8, either following an upgrade or an install,
- // then everything should be ok (the table should exist, etc)
-
- if (_umbracoVersion.LocalVersion != null && _umbracoVersion.LocalVersion.Major >= 8)
- {
- _initialized = true;
- return;
- }
-
- // else we are upgrading from 7, we can assume that the locks table
- // exists, but we need to create everything for key/value
-
- using (var scope = _scopeProvider.CreateScope())
- {
- var context = new MigrationContext(scope.Database, _logger);
- var initMigration = new InitializeMigration(context);
- initMigration.Migrate();
- scope.Complete();
- }
-
- // but don't assume we are initializing
- // we are upgrading from v7 and if anything goes wrong,
- // the table and everything will be rolled back
- }
-
- ///
- /// A custom migration that executes standalone during the Initialize phase of this service.
- ///
- internal class InitializeMigration : MigrationBase
- {
- public InitializeMigration(IMigrationContext context)
- : base(context)
- { }
-
- public override void Migrate()
- {
- // as long as we are still running 7 this migration will be invoked,
- // but due to multiple restarts during upgrades, maybe the table
- // exists already
- if (TableExists(Constants.DatabaseSchema.Tables.KeyValue))
- return;
-
- Logger.Info("Creating KeyValue structure.");
-
- // the locks table was initially created with an identity (auto-increment) primary key,
- // but we don't want this, especially as we are about to insert a new row into the table,
- // so here we drop that identity
- DropLockTableIdentity();
-
- // insert the lock object for key/value
- Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do();
-
- // create the key-value table
- Create.Table().Do();
- }
-
- private void DropLockTableIdentity()
- {
- // one cannot simply drop an identity, that requires a bit of work
-
- // create a temp. id column and copy values
- Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do();
- Execute.Sql("update umbracoLock set nid = id").Do();
-
- // drop the id column entirely (cannot just drop identity)
- Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do();
- Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do();
-
- // recreate the id column without identity and copy values
- Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do();
- Execute.Sql("update umbracoLock set id = nid").Do();
-
- // drop the temp. id column
- Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do();
-
- // complete the primary key
- Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do();
- }
+ _repository = repository;
}
///
public string GetValue(string key)
{
- EnsureInitialized();
-
using (var scope = _scopeProvider.CreateScope())
{
- var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key);
- var dto = scope.Database.Fetch(sql).FirstOrDefault();
- scope.Complete();
- return dto?.Value;
+ return _repository.Get(key)?.Value;
}
}
///
public void SetValue(string key, string value)
{
- EnsureInitialized();
-
using (var scope = _scopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.KeyValues);
- var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key);
- var dto = scope.Database.Fetch(sql).FirstOrDefault();
-
- if (dto == null)
+ var keyValue = _repository.Get(key);
+ if (keyValue == null)
{
- dto = new KeyValueDto
+ keyValue = new KeyValue
{
- Key = key,
+ Identifier = key,
Value = value,
- Updated = DateTime.Now
+ UpdateDate = DateTime.Now,
};
-
- scope.Database.Insert(dto);
}
else
{
- dto.Value = value;
- dto.Updated = DateTime.Now;
- scope.Database.Update(dto);
+ keyValue.Value = value;
+ keyValue.UpdateDate = DateTime.Now;
}
+ _repository.Save(keyValue);
+
scope.Complete();
}
}
@@ -175,43 +62,26 @@ namespace Umbraco.Core.Services.Implement
}
///
- public bool TrySetValue(string key, string originValue, string newValue)
+ public bool TrySetValue(string key, string originalValue, string newValue)
{
- EnsureInitialized();
-
using (var scope = _scopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.KeyValues);
- var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key);
- var dto = scope.Database.Fetch(sql).FirstOrDefault();
-
- if (dto == null || dto.Value != originValue)
+ var keyValue = _repository.Get(key);
+ if (keyValue == null || keyValue.Value != originalValue)
+ {
return false;
+ }
- dto.Value = newValue;
- dto.Updated = DateTime.Now;
- scope.Database.Update(dto);
+ keyValue.Value = newValue;
+ keyValue.UpdateDate = DateTime.Now;
+ _repository.Save(keyValue);
scope.Complete();
}
return true;
}
-
- ///
- /// Gets a value directly from the database, no scope, nothing.
- ///
- /// Used by to determine the runtime state.
- internal static string GetValue(IUmbracoDatabase database, string key)
- {
- if (database is null) return null;
-
- var sql = database.SqlContext.Sql()
- .Select()
- .From()
- .Where(x => x.Key == key);
- return database.FirstOrDefault(sql)?.Value;
- }
}
}
diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs
index 4876772c86..7ee6065210 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs
@@ -756,7 +756,6 @@ namespace Umbraco.Core.Services.Implement
throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback
}
- // TODO: Since this is by property value, we need a GetByPropertyQuery on the repo!
// TODO: Since this is by property value, we need a GetByPropertyQuery on the repo!
return _memberRepository.Get(query);
}
@@ -959,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))
@@ -1196,7 +1224,6 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.MemberTree);
- // TODO: What about content that has the contenttype as part of its composition?
// TODO: What about content that has the contenttype as part of its composition?
var query = Query().Where(x => x.ContentTypeId == memberTypeId);
diff --git a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs
index ab9ea64292..f2af6cf424 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs
@@ -124,7 +124,6 @@ namespace Umbraco.Core.Services.Implement
}
else
{
- //If they are both the same already then there's nothing to update, exit
//If they are both the same already then there's nothing to update, exit
return OperationResult.Attempt.Succeed(evtMsgs, entry);
}
diff --git a/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs b/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs
index 97bf76e672..6a092b159f 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs
@@ -72,7 +72,6 @@ namespace Umbraco.Core.Services.Implement
_serverRegistrationRepository.Save(server);
_serverRegistrationRepository.DeactiveStaleServers(staleTimeout); // triggers a cache reload
- // reload - cheap, cached
// reload - cheap, cached
// default role is single server, but if registrations contain more
diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs
index f8982fc538..24f1af5843 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs
@@ -46,7 +46,7 @@ namespace Umbraco.Core.Services.Implement
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
- return _userRepository.Exists(username);
+ return _userRepository.ExistsByUserName(username);
}
}
@@ -109,9 +109,9 @@ namespace Umbraco.Core.Services.Implement
User user;
using (var scope = ScopeProvider.CreateScope())
{
- var loginExists = scope.Database.ExecuteScalar("SELECT COUNT(id) FROM umbracoUser WHERE userLogin = @Login", new { Login = username }) != 0;
+ var loginExists = _userRepository.ExistsByLogin(username);
if (loginExists)
- throw new ArgumentException("Login already exists"); // causes rollback // causes rollback
+ throw new ArgumentException("Login already exists"); // causes rollback
user = new User(_globalSettings)
{
@@ -340,7 +340,6 @@ namespace Umbraco.Core.Services.Implement
_userRepository.Save(user);
- //Now we have to check for backwards compat hacks
//Now we have to check for backwards compat hacks
var explicitUser = user as User;
if (explicitUser != null && explicitUser.GroupsToSave.Count > 0)
@@ -358,7 +357,6 @@ namespace Umbraco.Core.Services.Implement
scope.Events.Dispatch(SavedUser, this, saveEventArgs);
}
- //commit the whole lot in one go
//commit the whole lot in one go
scope.Complete();
}
@@ -370,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