diff --git a/.github/BUILD.md b/.github/BUILD.md index c6e870f396..ad33872423 100644 --- a/.github/BUILD.md +++ b/.github/BUILD.md @@ -43,6 +43,8 @@ If you only see a build.bat-file, you're probably on the wrong branch. If you sw You might run into [Powershell quirks](#powershell-quirks). +If it runs without errors; Hooray! Now you can continue with [the next step](CONTRIBUTING.md#how-do-i-begin) and open the solution and build it. + ### Build Infrastructure The Umbraco Build infrastructure relies on a PowerShell object. The object can be retrieved with: diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index cea5859486..0101ac9d16 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -28,7 +28,7 @@ This project and everyone participating in it, is governed by the [our Code of C [Working with the code](#working-with-the-code) * [Building Umbraco from source code](#building-umbraco-from-source-code) * [Working with the source code](#working-with-the-source-code) - * [Making changes after the PR was opened](#making-changes-after-the-pr-was-opened) + * [Making changes after the PR is open](#making-changes-after-the-pr-is-open) * [Which branch should I target for my contributions?](#which-branch-should-i-target-for-my-contributions) * [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository) @@ -65,7 +65,7 @@ Great question! The short version goes like this: * **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions) * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/contrib`, create a new branch first. * **Push** - great, now you can push the changes up to your fork on GitHub - * **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here] (https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go. + * **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here](https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go. ![Create a pull request](img/createpullrequest.png) diff --git a/.gitignore b/.gitignore index 12ad3299ad..5f2432313f 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 @@ -164,3 +167,12 @@ build/temp/ # eof /src/Umbraco.Web.UI.Client/TESTS-*.xml /src/ApiDocs/api/* +/src/Umbraco.Web.UI.NetCore/wwwroot/Media/* +/src/Umbraco.Web.UI.NetCore/wwwroot/is-cache/* +/src/Umbraco.Tests.Integration/App_Data/* +/src/Umbraco.Tests.Integration/TEMP/* +/src/Umbraco.Web.UI.NetCore/wwwroot/Umbraco/assets/* +/src/Umbraco.Web.UI.NetCore/wwwroot/Umbraco/js/* +/src/Umbraco.Web.UI.NetCore/wwwroot/Umbraco/lib/* +/src/Umbraco.Web.UI.NetCore/wwwroot/Umbraco/views/* +/src/Umbraco.Web.UI.NetCore/wwwroot/App_Data/TEMP/* diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 7630dad842..cfab130f0c 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,7 +25,7 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + diff --git a/build/build-bootstrap.ps1 b/build/build-bootstrap.ps1 index 71a25bfd7e..82c789ff22 100644 --- a/build/build-bootstrap.ps1 +++ b/build/build-bootstrap.ps1 @@ -22,6 +22,8 @@ # get NuGet $cache = 4 $nuget = "$scriptTemp\nuget.exe" + # ensure the correct NuGet-source is used. This one is used by Umbraco + $nugetsourceUmbraco = "https://www.myget.org/F/umbracocore/api/v3/index.json" if (-not $local) { $source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" @@ -61,7 +63,7 @@ # get the build system if (-not $local) { - $params = "-OutputDirectory", $scriptTemp, "-Verbosity", "quiet", "-PreRelease" + $params = "-OutputDirectory", $scriptTemp, "-Verbosity", "quiet", "-PreRelease", "-Source", $nugetsourceUmbraco &$nuget install Umbraco.Build @params if (-not $?) { throw "Failed to download Umbraco.Build." } } diff --git a/build/build.ps1 b/build/build.ps1 index ea07e4516f..6e124d1508 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -375,11 +375,14 @@ }) + $nugetsourceUmbraco = "https://api.nuget.org/v3/index.json" + $ubuild.DefineMethod("RestoreNuGet", { Write-Host "Restore NuGet" Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log" - &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log" + $params = "-Source", $nugetsourceUmbraco + &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log" @params if (-not $?) { throw "Failed to restore NuGet packages." } }) diff --git a/src/Umbraco.Configuration/AspNetCoreConfigsFactory.cs b/src/Umbraco.Configuration/AspNetCoreConfigsFactory.cs new file mode 100644 index 0000000000..0cacab9e1d --- /dev/null +++ b/src/Umbraco.Configuration/AspNetCoreConfigsFactory.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Configuration.Models; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.HealthChecks; +using Umbraco.Core.Configuration.UmbracoSettings; +using ConnectionStrings = Umbraco.Configuration.Models.ConnectionStrings; +using CoreDebugSettings = Umbraco.Configuration.Models.CoreDebugSettings; + +namespace Umbraco.Configuration +{ + public class AspNetCoreConfigsFactory : IConfigsFactory + { + private readonly IConfiguration _configuration; + + public AspNetCoreConfigsFactory(IConfiguration configuration) + { + _configuration = configuration ?? throw new System.ArgumentNullException(nameof(configuration)); + } + + public Configs Create() + { + var configs = new Configs(); + + configs.Add(() => new TourSettings(_configuration)); + configs.Add(() => new CoreDebugSettings(_configuration)); + configs.Add(() => new RequestHandlerSettings(_configuration)); + configs.Add(() => new SecuritySettings(_configuration)); + configs.Add(() => new UserPasswordConfigurationSettings(_configuration)); + configs.Add(() => new MemberPasswordConfigurationSettings(_configuration)); + configs.Add(() => new KeepAliveSettings(_configuration)); + configs.Add(() => new ContentSettings(_configuration)); + configs.Add(() => new HealthChecksSettings(_configuration)); + configs.Add(() => new LoggingSettings(_configuration)); + configs.Add(() => new ExceptionFilterSettings(_configuration)); + configs.Add(() => new ActiveDirectorySettings(_configuration)); + configs.Add(() => new RuntimeSettings(_configuration)); + configs.Add(() => new TypeFinderSettings(_configuration)); + configs.Add(() => new NuCacheSettings(_configuration)); + configs.Add(() => new WebRoutingSettings(_configuration)); + configs.Add(() => new IndexCreatorSettings(_configuration)); + configs.Add(() => new ModelsBuilderConfig(_configuration)); + configs.Add(() => new HostingSettings(_configuration)); + configs.Add(() => new GlobalSettings(_configuration)); + configs.Add(() => new ConnectionStrings(_configuration)); + configs.Add(() => new ImagingSettings(_configuration)); + + return configs; + } + } +} diff --git a/src/Umbraco.Configuration/CaseInsensitiveEnumConfigConverter.cs b/src/Umbraco.Configuration/CaseInsensitiveEnumConfigConverter.cs deleted file mode 100644 index f07e5133ef..0000000000 --- a/src/Umbraco.Configuration/CaseInsensitiveEnumConfigConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using System.Configuration; - -namespace Umbraco.Core.Configuration -{ - /// - /// A case-insensitive configuration converter for enumerations. - /// - /// The type of the enumeration. - public class CaseInsensitiveEnumConfigConverter : ConfigurationConverterBase - where T : struct - { - public override object ConvertFrom(ITypeDescriptorContext ctx, CultureInfo ci, object data) - { - if (data == null) - throw new ArgumentNullException("data"); - - //return Enum.Parse(typeof(T), (string)data, true); - - T value; - if (Enum.TryParse((string)data, true, out value)) - return value; - - throw new Exception(string.Format("\"{0}\" is not valid {1} value. Valid values are: {2}.", - data, typeof(T).Name, - string.Join(", ", Enum.GetValues(typeof(T)).Cast()))); - } - } -} diff --git a/src/Umbraco.Configuration/ConfigsFactory.cs b/src/Umbraco.Configuration/ConfigsFactory.cs index c09dc7b9f6..be6cee2d0c 100644 --- a/src/Umbraco.Configuration/ConfigsFactory.cs +++ b/src/Umbraco.Configuration/ConfigsFactory.cs @@ -1,34 +1,62 @@ -using System.Configuration; +using Umbraco.Configuration; +using Umbraco.Configuration.Implementations; +using Umbraco.Configuration.Legacy; using Umbraco.Core.Configuration.HealthChecks; +using Umbraco.Core.Configuration.Legacy; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; namespace Umbraco.Core.Configuration { public class ConfigsFactory : IConfigsFactory { public IHostingSettings HostingSettings { get; } = new HostingSettings(); + public ICoreDebugSettings CoreDebugSettings { get; } = new CoreDebugSettings(); + public IIndexCreatorSettings IndexCreatorSettings { get; } = new IndexCreatorSettings(); + public INuCacheSettings NuCacheSettings { get; } = new NuCacheSettings(); + public ITypeFinderSettings TypeFinderSettings { get; } = new TypeFinderSettings(); + public IRuntimeSettings RuntimeSettings { get; } = new RuntimeSettings(); + public IActiveDirectorySettings ActiveDirectorySettings { get; } = new ActiveDirectorySettings(); + public IExceptionFilterSettings ExceptionFilterSettings { get; } = new ExceptionFilterSettings(); + public ITourSettings TourSettings { get; } = new TourSettings(); + public ILoggingSettings LoggingSettings { get; } = new LoggingSettings(); + public IKeepAliveSettings KeepAliveSettings { get; } = new KeepAliveSettings(); + public IWebRoutingSettings WebRoutingSettings { get; } = new WebRoutingSettings(); + public IRequestHandlerSettings RequestHandlerSettings { get; } = new RequestHandlerSettings(); + public ISecuritySettings SecuritySettings { get; } = new SecuritySettings(); + public IUserPasswordConfiguration UserPasswordConfigurationSettings { get; } = new UserPasswordConfigurationSettings(); + public IMemberPasswordConfiguration MemberPasswordConfigurationSettings { get; } = new MemberPasswordConfigurationSettings(); + public IContentSettings ContentSettings { get; } = new ContentSettings(); + public IGlobalSettings GlobalSettings { get; } = new GlobalSettings(); + public IHealthChecksSettings HealthChecksSettings { get; } = new HealthChecksSettings(); + public IConnectionStrings ConnectionStrings { get; } = new ConnectionStrings(); + public IModelsBuilderConfig ModelsBuilderConfig { get; } = new ModelsBuilderConfig(); - public ICoreDebug CoreDebug { get; } = new CoreDebug(); - - public IUmbracoSettingsSection UmbracoSettings { get; } - - public Configs Create(IIOHelper ioHelper) + public Configs Create() { - var configs = new Configs(section => ConfigurationManager.GetSection(section)); - configs.Add(() => new GlobalSettings(ioHelper)); - configs.Add(() => HostingSettings); + var configs = new Configs(); - configs.Add("umbracoConfiguration/settings"); - configs.Add("umbracoConfiguration/HealthChecks"); + configs.Add(() => GlobalSettings); + configs.Add(() => HostingSettings); + configs.Add(() => HealthChecksSettings); + configs.Add(() => CoreDebugSettings); + configs.Add(() => ConnectionStrings); + configs.Add(() => ModelsBuilderConfig); + configs.Add(() => IndexCreatorSettings); + configs.Add(() => NuCacheSettings); + configs.Add(() => TypeFinderSettings); + configs.Add(() => RuntimeSettings); + configs.Add(() => ActiveDirectorySettings); + configs.Add(() => ExceptionFilterSettings); + configs.Add(() => TourSettings); + configs.Add(() => LoggingSettings); + configs.Add(() => KeepAliveSettings); + configs.Add(() => WebRoutingSettings); + configs.Add(() => RequestHandlerSettings); + configs.Add(() => SecuritySettings); + configs.Add(() => UserPasswordConfigurationSettings); + configs.Add(() => MemberPasswordConfigurationSettings); + configs.Add(() => ContentSettings); - // Password configuration is held within IUmbracoSettingsSection from umbracoConfiguration/settings but we'll add explicitly - // so it can be independently retrieved in classes that need it. - configs.AddPasswordConfigurations(); - - configs.Add(() => CoreDebug); - configs.Add(() => new ConnectionStrings(ioHelper)); - configs.AddCoreConfigs(ioHelper); return configs; } } diff --git a/src/Umbraco.Configuration/ConnectionStrings.cs b/src/Umbraco.Configuration/ConnectionStrings.cs deleted file mode 100644 index 6a00974831..0000000000 --- a/src/Umbraco.Configuration/ConnectionStrings.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Configuration; -using System.Linq; -using System.Xml.Linq; -using Umbraco.Core.IO; - -namespace Umbraco.Core.Configuration -{ - public class ConnectionStrings : IConnectionStrings - { - private readonly IIOHelper _ioHelper; - - public ConnectionStrings(IIOHelper ioHelper) - { - _ioHelper = ioHelper; - } - - public ConfigConnectionString this[string key] - { - get - { - var settings = ConfigurationManager.ConnectionStrings[key]; - if (settings == null) return null; - return new ConfigConnectionString(settings.ConnectionString, settings.ProviderName, settings.Name); - } - } - - public void RemoveConnectionString(string key) - { - var fileName = _ioHelper.MapPath(string.Format("{0}/web.config", _ioHelper.Root)); - var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); - - var appSettings = xml.Root.DescendantsAndSelf("appSettings").Single(); - var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key); - - if (setting != null) - { - setting.Remove(); - xml.Save(fileName, SaveOptions.DisableFormatting); - ConfigurationManager.RefreshSection("appSettings"); - } - var settings = ConfigurationManager.ConnectionStrings[key]; - } - } -} diff --git a/src/Umbraco.Configuration/HealthChecks/HealthChecksSection.cs b/src/Umbraco.Configuration/HealthChecks/HealthChecksSection.cs index 90a7d8c567..373d846567 100644 --- a/src/Umbraco.Configuration/HealthChecks/HealthChecksSection.cs +++ b/src/Umbraco.Configuration/HealthChecks/HealthChecksSection.cs @@ -1,10 +1,9 @@ using System; -using System.Collections.Generic; using System.Configuration; namespace Umbraco.Core.Configuration.HealthChecks { - public class HealthChecksSection : ConfigurationSection, IHealthChecks + public class HealthChecksSection : ConfigurationSection { private const string DisabledChecksKey = "disabledChecks"; private const string NotificationSettingsKey = "notificationSettings"; @@ -21,14 +20,5 @@ namespace Umbraco.Core.Configuration.HealthChecks get { return ((HealthCheckNotificationSettingsElement)(base[NotificationSettingsKey])); } } - IEnumerable IHealthChecks.DisabledChecks - { - get { return DisabledChecks; } - } - - IHealthCheckNotificationSettings IHealthChecks.NotificationSettings - { - get { return NotificationSettings; } - } } } diff --git a/src/Umbraco.Configuration/Legacy/ActiveDirectorySettings.cs b/src/Umbraco.Configuration/Legacy/ActiveDirectorySettings.cs new file mode 100644 index 0000000000..ef100afed6 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/ActiveDirectorySettings.cs @@ -0,0 +1,15 @@ +using System.Configuration; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Legacy +{ + public class ActiveDirectorySettings : IActiveDirectorySettings + { + public ActiveDirectorySettings() + { + ActiveDirectoryDomain = ConfigurationManager.AppSettings["ActiveDirectoryDomain"]; + } + + public string ActiveDirectoryDomain { get; } + } +} diff --git a/src/Umbraco.Configuration/CommaDelimitedConfigurationElement.cs b/src/Umbraco.Configuration/Legacy/CommaDelimitedConfigurationElement.cs similarity index 100% rename from src/Umbraco.Configuration/CommaDelimitedConfigurationElement.cs rename to src/Umbraco.Configuration/Legacy/CommaDelimitedConfigurationElement.cs diff --git a/src/Umbraco.Configuration/Legacy/ConfigurationManagerConfigBase.cs b/src/Umbraco.Configuration/Legacy/ConfigurationManagerConfigBase.cs new file mode 100644 index 0000000000..0302a7ea31 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/ConfigurationManagerConfigBase.cs @@ -0,0 +1,22 @@ +using System.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Implementations +{ + internal abstract class ConfigurationManagerConfigBase + { + private UmbracoSettingsSection _umbracoSettingsSection; + + protected UmbracoSettingsSection UmbracoSettingsSection + { + get + { + if (_umbracoSettingsSection is null) + { + _umbracoSettingsSection = ConfigurationManager.GetSection("umbracoConfiguration/settings") as UmbracoSettingsSection; + } + return _umbracoSettingsSection; + } + } + } +} diff --git a/src/Umbraco.Configuration/Legacy/ConnectionStrings.cs b/src/Umbraco.Configuration/Legacy/ConnectionStrings.cs new file mode 100644 index 0000000000..a02c351118 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/ConnectionStrings.cs @@ -0,0 +1,17 @@ +using System.Configuration; + +namespace Umbraco.Core.Configuration +{ + public class ConnectionStrings : IConnectionStrings + { + public ConfigConnectionString this[string key] + { + get + { + var settings = ConfigurationManager.ConnectionStrings[key]; + if (settings == null) return null; + return new ConfigConnectionString(settings.ConnectionString, settings.ProviderName, settings.Name); + } + } + } +} diff --git a/src/Umbraco.Configuration/Legacy/ContentSettings.cs b/src/Umbraco.Configuration/Legacy/ContentSettings.cs new file mode 100644 index 0000000000..1c3f543bfe --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/ContentSettings.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Macros; + +namespace Umbraco.Configuration.Implementations +{ + internal class ContentSettings : ConfigurationManagerConfigBase, IContentSettings + { + public string NotificationEmailAddress => UmbracoSettingsSection.Content.Notifications.NotificationEmailAddress; + public bool DisableHtmlEmail => UmbracoSettingsSection.Content.Notifications.DisableHtmlEmail; + public IEnumerable ImageFileTypes => UmbracoSettingsSection.Content.Imaging.ImageFileTypes; + public IEnumerable ImageAutoFillProperties => UmbracoSettingsSection.Content.Imaging.ImageAutoFillProperties; + public bool ResolveUrlsFromTextString => UmbracoSettingsSection.Content.ResolveUrlsFromTextString; + public IEnumerable Error404Collection => UmbracoSettingsSection.Content.Error404Collection; + public string PreviewBadge => UmbracoSettingsSection.Content.PreviewBadge; + public MacroErrorBehaviour MacroErrorBehaviour => UmbracoSettingsSection.Content.MacroErrors; + public IEnumerable DisallowedUploadFiles => UmbracoSettingsSection.Content.DisallowedUploadFiles; + public IEnumerable AllowedUploadFiles => UmbracoSettingsSection.Content.AllowedUploadFiles; + public bool ShowDeprecatedPropertyEditors => UmbracoSettingsSection.Content.ShowDeprecatedPropertyEditors; + public string LoginBackgroundImage => UmbracoSettingsSection.Content.LoginBackgroundImage; + } +} diff --git a/src/Umbraco.Configuration/CoreDebug.cs b/src/Umbraco.Configuration/Legacy/CoreDebugSettings.cs similarity index 87% rename from src/Umbraco.Configuration/CoreDebug.cs rename to src/Umbraco.Configuration/Legacy/CoreDebugSettings.cs index 0ff3274565..4902d4489f 100644 --- a/src/Umbraco.Configuration/CoreDebug.cs +++ b/src/Umbraco.Configuration/Legacy/CoreDebugSettings.cs @@ -3,9 +3,9 @@ using System.Configuration; namespace Umbraco.Core.Configuration { - public class CoreDebug : ICoreDebug + public class CoreDebugSettings : ICoreDebugSettings { - public CoreDebug() + public CoreDebugSettings() { var appSettings = ConfigurationManager.AppSettings; LogUncompletedScopes = string.Equals("true", appSettings[Constants.AppSettings.Debug.LogUncompletedScopes], StringComparison.OrdinalIgnoreCase); diff --git a/src/Umbraco.Configuration/Legacy/ExceptionFilterSettings.cs b/src/Umbraco.Configuration/Legacy/ExceptionFilterSettings.cs new file mode 100644 index 0000000000..50e2207485 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/ExceptionFilterSettings.cs @@ -0,0 +1,18 @@ +using System.Configuration; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Legacy +{ + public class ExceptionFilterSettings : IExceptionFilterSettings + { + public ExceptionFilterSettings() + { + if (bool.TryParse(ConfigurationManager.AppSettings["Umbraco.Web.DisableModelBindingExceptionFilter"], + out var disabled)) + { + Disabled = disabled; + } + } + public bool Disabled { get; } + } +} diff --git a/src/Umbraco.Configuration/GlobalSettings.cs b/src/Umbraco.Configuration/Legacy/GlobalSettings.cs similarity index 85% rename from src/Umbraco.Configuration/GlobalSettings.cs rename to src/Umbraco.Configuration/Legacy/GlobalSettings.cs index a44f7ae636..d93297ca90 100644 --- a/src/Umbraco.Configuration/GlobalSettings.cs +++ b/src/Umbraco.Configuration/Legacy/GlobalSettings.cs @@ -3,59 +3,12 @@ using System.Configuration; using System.Linq; using System.Net.Mail; using System.Xml.Linq; +using Umbraco.Composing; using Umbraco.Configuration; using Umbraco.Core.IO; -namespace Umbraco.Core.Configuration +namespace Umbraco.Core.Configuration.Legacy { - public class HostingSettings : IHostingSettings - { - private bool? _debugMode; - - /// - public LocalTempStorage LocalTempStorageLocation - { - get - { - var setting = ConfigurationManager.AppSettings[Constants.AppSettings.LocalTempStorage]; - if (!string.IsNullOrWhiteSpace(setting)) - return Enum.Parse(setting); - - return LocalTempStorage.Default; - } - } - - /// - /// Gets a value indicating whether umbraco is running in [debug mode]. - /// - /// true if [debug mode]; otherwise, false. - public bool DebugMode - { - get - { - if (!_debugMode.HasValue) - { - try - { - if (ConfigurationManager.GetSection("system.web/compilation") is ConfigurationSection compilation) - { - var debugElement = compilation.ElementInformation.Properties["debug"]; - - _debugMode = debugElement != null && (debugElement.Value is bool debug && debug); - - } - } - catch - { - _debugMode = false; - } - } - - return _debugMode.GetValueOrDefault(); - } - } - } - // TODO: Replace checking for if the app settings exist and returning an empty string, instead return the defaults! // TODO: need to massively cleanup these configuration classes @@ -64,7 +17,6 @@ namespace Umbraco.Core.Configuration /// public class GlobalSettings : IGlobalSettings { - private readonly IIOHelper _ioHelper; // TODO these should not be static private static string _reservedPaths; @@ -74,11 +26,6 @@ namespace Umbraco.Core.Configuration internal const string StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma! internal const string StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; //must end with a comma! - public GlobalSettings(IIOHelper ioHelper) - { - _ioHelper = ioHelper; - } - /// /// Used in unit testing to reset all config items that were set with property setters (i.e. did not come from config) /// @@ -184,8 +131,8 @@ namespace Umbraco.Core.Configuration if (_reservedPaths != null) return _reservedPaths; var reservedPaths = StaticReservedPaths; - var umbPath = ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.Path) && !ConfigurationManager.AppSettings[Constants.AppSettings.Path].IsNullOrWhiteSpace() - ? ConfigurationManager.AppSettings[Constants.AppSettings.Path] + var umbPath = ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.UmbracoPath) && !ConfigurationManager.AppSettings[Constants.AppSettings.UmbracoPath].IsNullOrWhiteSpace() + ? ConfigurationManager.AppSettings[Constants.AppSettings.UmbracoPath] : "~/umbraco"; //always add the umbraco path to the list reservedPaths += umbPath.EnsureEndsWith(','); @@ -199,20 +146,6 @@ namespace Umbraco.Core.Configuration } } - /// - /// Gets the path to umbraco's root directory (/umbraco by default). - /// - /// The path. - public string Path - { - get - { - return ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.Path) - ? _ioHelper.ResolveUrl(ConfigurationManager.AppSettings[Constants.AppSettings.Path]) - : string.Empty; - } - } - /// /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. /// @@ -227,7 +160,7 @@ namespace Umbraco.Core.Configuration } set { - SaveSetting(Constants.AppSettings.ConfigurationStatus, value, _ioHelper); + SaveSetting(Constants.AppSettings.ConfigurationStatus, value, Current.IOHelper); //TODO remove } } @@ -238,7 +171,7 @@ namespace Umbraco.Core.Configuration /// Value of the setting to be saved. internal static void SaveSetting(string key, string value, IIOHelper ioHelper) { - var fileName = ioHelper.MapPath(string.Format("{0}/web.config", ioHelper.Root)); + var fileName = ioHelper.MapPath("~/web.config"); var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); var appSettings = xml.Root.DescendantsAndSelf("appSettings").Single(); @@ -254,7 +187,7 @@ namespace Umbraco.Core.Configuration ConfigurationManager.RefreshSection("appSettings"); } - + /// /// Gets the time out in minutes. /// @@ -382,6 +315,10 @@ namespace Umbraco.Core.Configuration private string _databaseFactoryServerVersion; public string DatabaseFactoryServerVersion => GetterWithDefaultValue(Constants.AppSettings.Debug.DatabaseFactoryServerVersion, string.Empty, ref _databaseFactoryServerVersion); + private string _mainDomLock; + + public string MainDomLock => GetterWithDefaultValue(Constants.AppSettings.MainDomLock, string.Empty, ref _mainDomLock); + private T GetterWithDefaultValue(string appSettingKey, T defaultValue, ref T backingField) { if (backingField != null) return backingField; @@ -407,5 +344,22 @@ namespace Umbraco.Core.Configuration return backingField; } + + /// + /// Gets the path to the razor file used when no published content is available. + /// + public string NoNodesViewPath + { + get + { + var configuredValue = ConfigurationManager.AppSettings[Constants.AppSettings.NoNodesViewPath]; + if (!string.IsNullOrWhiteSpace(configuredValue)) + { + return configuredValue; + } + + return "~/config/splashes/NoNodes.cshtml"; + } + } } } diff --git a/src/Umbraco.Configuration/Legacy/HealthChecksSettings.cs b/src/Umbraco.Configuration/Legacy/HealthChecksSettings.cs new file mode 100644 index 0000000000..23385d1378 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/HealthChecksSettings.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Configuration; +using Umbraco.Core.Configuration.HealthChecks; + +namespace Umbraco.Core.Configuration.Legacy +{ + public class HealthChecksSettings : IHealthChecksSettings + { + private HealthChecksSection _healthChecksSection; + + private HealthChecksSection HealthChecksSection + { + get + { + if (_healthChecksSection is null) + { + _healthChecksSection = ConfigurationManager.GetSection("umbracoConfiguration/HealthChecks") as HealthChecksSection; + } + return _healthChecksSection; + } + } + + public IEnumerable DisabledChecks => HealthChecksSection.DisabledChecks; + public IHealthCheckNotificationSettings NotificationSettings => HealthChecksSection.NotificationSettings; + } +} diff --git a/src/Umbraco.Configuration/Legacy/HostingSettings.cs b/src/Umbraco.Configuration/Legacy/HostingSettings.cs new file mode 100644 index 0000000000..1858e8a4a4 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/HostingSettings.cs @@ -0,0 +1,54 @@ +using System.Configuration; + +namespace Umbraco.Core.Configuration.Legacy +{ + public class HostingSettings : IHostingSettings + { + private bool? _debugMode; + + /// + public LocalTempStorage LocalTempStorageLocation + { + get + { + var setting = ConfigurationManager.AppSettings[Constants.AppSettings.LocalTempStorage]; + if (!string.IsNullOrWhiteSpace(setting)) + return Enum.Parse(setting); + + return LocalTempStorage.Default; + } + } + + public string ApplicationVirtualPath => null; + + /// + /// Gets a value indicating whether umbraco is running in [debug mode]. + /// + /// true if [debug mode]; otherwise, false. + public bool DebugMode + { + get + { + if (!_debugMode.HasValue) + { + try + { + if (ConfigurationManager.GetSection("system.web/compilation") is ConfigurationSection compilation) + { + var debugElement = compilation.ElementInformation.Properties["debug"]; + + _debugMode = debugElement != null && (debugElement.Value is bool debug && debug); + + } + } + catch + { + _debugMode = false; + } + } + + return _debugMode.GetValueOrDefault(); + } + } + } +} diff --git a/src/Umbraco.Configuration/Legacy/IndexCreatorSettings.cs b/src/Umbraco.Configuration/Legacy/IndexCreatorSettings.cs new file mode 100644 index 0000000000..d023d46246 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/IndexCreatorSettings.cs @@ -0,0 +1,15 @@ +using System.Configuration; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Legacy +{ + public class IndexCreatorSettings : IIndexCreatorSettings + { + public IndexCreatorSettings() + { + LuceneDirectoryFactory = ConfigurationManager.AppSettings["Umbraco.Examine.LuceneDirectoryFactory"]; + } + + public string LuceneDirectoryFactory { get; } + } +} diff --git a/src/Umbraco.Configuration/InnerTextConfigurationElement.cs b/src/Umbraco.Configuration/Legacy/InnerTextConfigurationElement.cs similarity index 100% rename from src/Umbraco.Configuration/InnerTextConfigurationElement.cs rename to src/Umbraco.Configuration/Legacy/InnerTextConfigurationElement.cs diff --git a/src/Umbraco.Configuration/Legacy/KeepAliveSettings.cs b/src/Umbraco.Configuration/Legacy/KeepAliveSettings.cs new file mode 100644 index 0000000000..0b8315d447 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/KeepAliveSettings.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Implementations +{ + internal class KeepAliveSettings : ConfigurationManagerConfigBase, IKeepAliveSettings + { + public bool DisableKeepAliveTask => UmbracoSettingsSection.KeepAlive.DisableKeepAliveTask; + public string KeepAlivePingUrl => UmbracoSettingsSection.KeepAlive.KeepAlivePingUrl; + } +} diff --git a/src/Umbraco.Configuration/Legacy/LoggingSettings.cs b/src/Umbraco.Configuration/Legacy/LoggingSettings.cs new file mode 100644 index 0000000000..020b0c0e64 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/LoggingSettings.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Implementations +{ + internal class LoggingSettings : ConfigurationManagerConfigBase, ILoggingSettings + { + public int MaxLogAge => UmbracoSettingsSection.Logging.MaxLogAge; + } +} diff --git a/src/Umbraco.Configuration/Legacy/MemberPasswordConfigurationSettings.cs b/src/Umbraco.Configuration/Legacy/MemberPasswordConfigurationSettings.cs new file mode 100644 index 0000000000..e42b02de73 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/MemberPasswordConfigurationSettings.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Implementations +{ + internal class MemberPasswordConfigurationSettings : ConfigurationManagerConfigBase, IMemberPasswordConfiguration + { + public int RequiredLength => UmbracoSettingsSection.Security.UserPasswordConfiguration.RequiredLength; + public bool RequireNonLetterOrDigit => UmbracoSettingsSection.Security.UserPasswordConfiguration.RequireNonLetterOrDigit; + public bool RequireDigit => UmbracoSettingsSection.Security.UserPasswordConfiguration.RequireDigit; + public bool RequireLowercase=> UmbracoSettingsSection.Security.UserPasswordConfiguration.RequireLowercase; + public bool RequireUppercase=> UmbracoSettingsSection.Security.UserPasswordConfiguration.RequireUppercase; + public bool UseLegacyEncoding=> UmbracoSettingsSection.Security.UserPasswordConfiguration.UseLegacyEncoding; + public string HashAlgorithmType=> UmbracoSettingsSection.Security.UserPasswordConfiguration.HashAlgorithmType; + public int MaxFailedAccessAttemptsBeforeLockout => UmbracoSettingsSection.Security.UserPasswordConfiguration.MaxFailedAccessAttemptsBeforeLockout; + } +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.Configuration/Legacy/ModelsBuilderConfig.cs similarity index 76% rename from src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs rename to src/Umbraco.Configuration/Legacy/ModelsBuilderConfig.cs index d0137ed2b2..f6395b23b4 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.Configuration/Legacy/ModelsBuilderConfig.cs @@ -2,18 +2,18 @@ using System.Configuration; using System.IO; using System.Threading; -using System.Web.Configuration; +using Umbraco.Core.Configuration; using Umbraco.Core; using Umbraco.Core.IO; -namespace Umbraco.ModelsBuilder.Embedded.Configuration +namespace Umbraco.Configuration.Legacy { /// /// Represents the models builder configuration. /// public class ModelsBuilderConfig : IModelsBuilderConfig { - private readonly IIOHelper _ioHelper; + private const string Prefix = "Umbraco.ModelsBuilder."; private object _modelsModelLock; private bool _modelsModelConfigured; @@ -21,23 +21,21 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration private object _flagOutOfDateModelsLock; private bool _flagOutOfDateModelsConfigured; private bool _flagOutOfDateModels; - public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; - public string DefaultModelsDirectory => _ioHelper.MapPath("~/App_Data/Models"); + + public string DefaultModelsDirectory => "~/App_Data/Models"; /// /// Initializes a new instance of the class. /// - public ModelsBuilderConfig(IIOHelper ioHelper) + public ModelsBuilderConfig() { - _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); - // giant kill switch, default: false // must be explicitely set to true for anything else to happen Enable = ConfigurationManager.AppSettings[Prefix + "Enable"] == "true"; // ensure defaults are initialized for tests - ModelsNamespace = DefaultModelsNamespace; + ModelsNamespace = Constants.ModelsBuilder.DefaultModelsNamespace; ModelsDirectory = DefaultModelsDirectory; DebugLevel = 0; @@ -59,12 +57,8 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration value = ConfigurationManager.AppSettings[Prefix + "ModelsDirectory"]; if (!string.IsNullOrWhiteSpace(value)) { - var root = _ioHelper.MapPath("~/"); - if (root == null) - throw new ConfigurationErrorsException("Could not determine root directory."); - // GetModelsDirectory will ensure that the path is safe - ModelsDirectory = GetModelsDirectory(root, value, AcceptUnsafeModelsDirectory); + ModelsDirectory = value; } // default: 0 @@ -81,7 +75,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// /// Initializes a new instance of the class. /// - public ModelsBuilderConfig(IIOHelper ioHelper, + public ModelsBuilderConfig( bool enable = false, ModelsMode modelsMode = ModelsMode.Nothing, string modelsNamespace = null, @@ -91,11 +85,10 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration bool acceptUnsafeModelsDirectory = false, int debugLevel = 0) { - _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); Enable = enable; _modelsMode = modelsMode; - ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; + ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? Constants.ModelsBuilder.DefaultModelsNamespace : modelsNamespace; EnableFactory = enableFactory; _flagOutOfDateModels = flagOutOfDateModels; ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory; @@ -103,36 +96,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration DebugLevel = debugLevel; } - // internal for tests - internal static string GetModelsDirectory(string root, string config, bool acceptUnsafe) - { - // making sure it is safe, ie under the website root, - // unless AcceptUnsafeModelsDirectory and then everything is OK. - if (!Path.IsPathRooted(root)) - throw new ConfigurationErrorsException($"Root is not rooted \"{root}\"."); - - if (config.StartsWith("~/")) - { - var dir = Path.Combine(root, config.TrimStart("~/")); - - // sanitize - GetFullPath will take care of any relative - // segments in path, eg '../../foo.tmp' - it may throw a SecurityException - // if the combined path reaches illegal parts of the filesystem - dir = Path.GetFullPath(dir); - root = Path.GetFullPath(root); - - if (!dir.StartsWith(root) && !acceptUnsafe) - throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); - - return dir; - } - - if (acceptUnsafe) - return Path.GetFullPath(config); - - throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); - } /// /// Gets a value indicating whether the whole models experience is enabled. @@ -174,8 +138,13 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration { get { - var section = (CompilationSection)ConfigurationManager.GetSection("system.web/compilation"); - return section != null && section.Debug; + if (ConfigurationManager.GetSection("system.web/compilation") is ConfigurationSection section && + bool.TryParse(section.ElementInformation.Properties["debug"].Value.ToString(), out var isDebug)) + { + return isDebug; + } + + return false; } } diff --git a/src/Umbraco.Configuration/Legacy/NuCacheSettings.cs b/src/Umbraco.Configuration/Legacy/NuCacheSettings.cs new file mode 100644 index 0000000000..25f52a5c7d --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/NuCacheSettings.cs @@ -0,0 +1,14 @@ +using System.Configuration; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Legacy +{ + public class NuCacheSettings : INuCacheSettings + { + public NuCacheSettings() + { + BTreeBlockSize = ConfigurationManager.AppSettings["Umbraco.Web.PublishedCache.NuCache.BTree.BlockSize"]; + } + public string BTreeBlockSize { get; } + } +} diff --git a/src/Umbraco.Configuration/OptionalCommaDelimitedConfigurationElement.cs b/src/Umbraco.Configuration/Legacy/OptionalCommaDelimitedConfigurationElement.cs similarity index 100% rename from src/Umbraco.Configuration/OptionalCommaDelimitedConfigurationElement.cs rename to src/Umbraco.Configuration/Legacy/OptionalCommaDelimitedConfigurationElement.cs diff --git a/src/Umbraco.Configuration/OptionalInnerTextConfigurationElement.cs b/src/Umbraco.Configuration/Legacy/OptionalInnerTextConfigurationElement.cs similarity index 100% rename from src/Umbraco.Configuration/OptionalInnerTextConfigurationElement.cs rename to src/Umbraco.Configuration/Legacy/OptionalInnerTextConfigurationElement.cs diff --git a/src/Umbraco.Configuration/RawXmlConfigurationElement.cs b/src/Umbraco.Configuration/Legacy/RawXmlConfigurationElement.cs similarity index 100% rename from src/Umbraco.Configuration/RawXmlConfigurationElement.cs rename to src/Umbraco.Configuration/Legacy/RawXmlConfigurationElement.cs diff --git a/src/Umbraco.Configuration/Legacy/RequestHandlerSettings.cs b/src/Umbraco.Configuration/Legacy/RequestHandlerSettings.cs new file mode 100644 index 0000000000..1c54f4d475 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/RequestHandlerSettings.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Implementations +{ + internal class RequestHandlerSettings : ConfigurationManagerConfigBase, IRequestHandlerSettings + { + public bool AddTrailingSlash => UmbracoSettingsSection?.RequestHandler?.AddTrailingSlash ?? true; + public bool ConvertUrlsToAscii => UmbracoSettingsSection?.RequestHandler?.UrlReplacing?.ConvertUrlsToAscii.InvariantEquals("true") ?? false; + public bool TryConvertUrlsToAscii => UmbracoSettingsSection?.RequestHandler?.UrlReplacing?.ConvertUrlsToAscii.InvariantEquals("try") ?? false; + public IEnumerable CharCollection => UmbracoSettingsSection?.RequestHandler?.UrlReplacing?.CharCollection ?? Enumerable.Empty(); + } +} diff --git a/src/Umbraco.Configuration/Legacy/RuntimeSettings.cs b/src/Umbraco.Configuration/Legacy/RuntimeSettings.cs new file mode 100644 index 0000000000..200642a819 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/RuntimeSettings.cs @@ -0,0 +1,29 @@ +using System.Configuration; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Legacy +{ + public class RuntimeSettings : IRuntimeSettings + { + public RuntimeSettings() + { + if (ConfigurationManager.GetSection("system.web/httpRuntime") is ConfigurationSection section) + { + var maxRequestLengthProperty = section.ElementInformation.Properties["maxRequestLength"]; + if (maxRequestLengthProperty != null && maxRequestLengthProperty.Value is int requestLength) + { + MaxRequestLength = requestLength; + } + + var maxQueryStringProperty = section.ElementInformation.Properties["maxQueryStringLength"]; + if (maxQueryStringProperty != null && maxQueryStringProperty.Value is int maxQueryStringLength) + { + MaxQueryStringLength = maxQueryStringLength; + } + } + } + public int? MaxQueryStringLength { get; } + public int? MaxRequestLength { get; } + + } +} diff --git a/src/Umbraco.Configuration/Legacy/SecuritySettings.cs b/src/Umbraco.Configuration/Legacy/SecuritySettings.cs new file mode 100644 index 0000000000..b7e39b0608 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/SecuritySettings.cs @@ -0,0 +1,14 @@ +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Implementations +{ + internal class SecuritySettings : ConfigurationManagerConfigBase, ISecuritySettings + { + public bool KeepUserLoggedIn => UmbracoSettingsSection.Security.KeepUserLoggedIn; + public bool HideDisabledUsersInBackoffice => UmbracoSettingsSection.Security.HideDisabledUsersInBackoffice; + public bool AllowPasswordReset => UmbracoSettingsSection.Security.AllowPasswordReset; + public string AuthCookieName => UmbracoSettingsSection.Security.AuthCookieName; + public string AuthCookieDomain => UmbracoSettingsSection.Security.AuthCookieDomain; + public bool UsernameIsEmail => UmbracoSettingsSection.Security.UsernameIsEmail; + } +} diff --git a/src/Umbraco.Configuration/SmtpSettings.cs b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs similarity index 100% rename from src/Umbraco.Configuration/SmtpSettings.cs rename to src/Umbraco.Configuration/Legacy/SmtpSettings.cs diff --git a/src/Umbraco.Configuration/Legacy/TourSettings.cs b/src/Umbraco.Configuration/Legacy/TourSettings.cs new file mode 100644 index 0000000000..134c3c48d5 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/TourSettings.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Implementations +{ + internal class TourSettings : ConfigurationManagerConfigBase, ITourSettings + { + public bool EnableTours => UmbracoSettingsSection.BackOffice.Tours.EnableTours; + } +} diff --git a/src/Umbraco.Configuration/Legacy/TypeFinderSettings.cs b/src/Umbraco.Configuration/Legacy/TypeFinderSettings.cs new file mode 100644 index 0000000000..b1009f754b --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/TypeFinderSettings.cs @@ -0,0 +1,17 @@ +using System.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Legacy +{ + public class TypeFinderSettings : ITypeFinderSettings + { + public TypeFinderSettings() + { + AssembliesAcceptingLoadExceptions = ConfigurationManager.AppSettings[ + Constants.AppSettings.AssembliesAcceptingLoadExceptions]; + } + + public string AssembliesAcceptingLoadExceptions { get; } + } +} diff --git a/src/Umbraco.Configuration/UmbracoConfigurationSection.cs b/src/Umbraco.Configuration/Legacy/UmbracoConfigurationSection.cs similarity index 100% rename from src/Umbraco.Configuration/UmbracoConfigurationSection.cs rename to src/Umbraco.Configuration/Legacy/UmbracoConfigurationSection.cs diff --git a/src/Umbraco.Configuration/Legacy/UserPasswordConfigurationSettings.cs b/src/Umbraco.Configuration/Legacy/UserPasswordConfigurationSettings.cs new file mode 100644 index 0000000000..51dd645c42 --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/UserPasswordConfigurationSettings.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Configuration; +namespace Umbraco.Configuration.Implementations +{ + internal class UserPasswordConfigurationSettings : ConfigurationManagerConfigBase, IUserPasswordConfiguration + { + public int RequiredLength => UmbracoSettingsSection.Security.UserPasswordConfiguration.RequiredLength; + public bool RequireNonLetterOrDigit => UmbracoSettingsSection.Security.UserPasswordConfiguration.RequireNonLetterOrDigit; + public bool RequireDigit => UmbracoSettingsSection.Security.UserPasswordConfiguration.RequireDigit; + public bool RequireLowercase=> UmbracoSettingsSection.Security.UserPasswordConfiguration.RequireLowercase; + public bool RequireUppercase=> UmbracoSettingsSection.Security.UserPasswordConfiguration.RequireUppercase; + public bool UseLegacyEncoding=> UmbracoSettingsSection.Security.UserPasswordConfiguration.UseLegacyEncoding; + public string HashAlgorithmType=> UmbracoSettingsSection.Security.UserPasswordConfiguration.HashAlgorithmType; + public int MaxFailedAccessAttemptsBeforeLockout => UmbracoSettingsSection.Security.UserPasswordConfiguration.MaxFailedAccessAttemptsBeforeLockout; + } +} diff --git a/src/Umbraco.Configuration/Legacy/WebRoutingSettings.cs b/src/Umbraco.Configuration/Legacy/WebRoutingSettings.cs new file mode 100644 index 0000000000..cfca66822b --- /dev/null +++ b/src/Umbraco.Configuration/Legacy/WebRoutingSettings.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Implementations +{ + internal class WebRoutingSettings : ConfigurationManagerConfigBase, IWebRoutingSettings + { + public bool TrySkipIisCustomErrors => UmbracoSettingsSection?.WebRouting?.TrySkipIisCustomErrors ?? false; + public bool InternalRedirectPreservesTemplate => UmbracoSettingsSection?.WebRouting?.InternalRedirectPreservesTemplate ?? false; + public bool DisableAlternativeTemplates => UmbracoSettingsSection?.WebRouting?.DisableAlternativeTemplates ?? false; + public bool ValidateAlternativeTemplates => UmbracoSettingsSection?.WebRouting?.ValidateAlternativeTemplates ?? false; + public bool DisableFindContentByIdPath => UmbracoSettingsSection?.WebRouting?.DisableFindContentByIdPath ?? false; + public bool DisableRedirectUrlTracking => UmbracoSettingsSection?.WebRouting?.DisableRedirectUrlTracking ?? false; + public string UrlProviderMode => UmbracoSettingsSection?.WebRouting?.UrlProviderMode ?? "Auto"; + public string UmbracoApplicationUrl => UmbracoSettingsSection?.WebRouting?.UmbracoApplicationUrl; + } +} diff --git a/src/Umbraco.Configuration/Models/ActiveDirectorySettings.cs b/src/Umbraco.Configuration/Models/ActiveDirectorySettings.cs new file mode 100644 index 0000000000..015fb17a8e --- /dev/null +++ b/src/Umbraco.Configuration/Models/ActiveDirectorySettings.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class ActiveDirectorySettings : IActiveDirectorySettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "ActiveDirectory:"; + private readonly IConfiguration _configuration; + + public ActiveDirectorySettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public string ActiveDirectoryDomain => _configuration.GetValue(Prefix+"Domain"); + } +} diff --git a/src/Umbraco.Configuration/Models/ConnectionStrings.cs b/src/Umbraco.Configuration/Models/ConnectionStrings.cs new file mode 100644 index 0000000000..22a0bde571 --- /dev/null +++ b/src/Umbraco.Configuration/Models/ConnectionStrings.cs @@ -0,0 +1,59 @@ +using System; +using System.Data.Common; +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + public class ConnectionStrings : IConnectionStrings + { + private readonly IConfiguration _configuration; + + public ConnectionStrings(IConfiguration configuration) + { + _configuration = configuration; + } + + public ConfigConnectionString this[string key] + { + get + { + var connectionString = _configuration.GetConnectionString(key); + var provider = ParseProvider(connectionString); + return new ConfigConnectionString(connectionString, provider, key); + } + set => throw new NotImplementedException(); + } + + private string ParseProvider(string connectionString) + { + if (string.IsNullOrEmpty(connectionString)) + { + return null; + } + + var builder = new DbConnectionStringBuilder(); + + builder.ConnectionString = connectionString; + + if (builder.TryGetValue("Data Source", out var ds) && ds is string dataSource) + { + if (dataSource.EndsWith(".sdf")) + { + return Constants.DbProviderNames.SqlCe; + } + } + + if (builder.TryGetValue("Server", out var s) && s is string server && builder.TryGetValue("Database", out var db) && db is string database) + { + if (!string.IsNullOrEmpty(server) && !string.IsNullOrEmpty(database)) + { + return Constants.DbProviderNames.SqlServer; + } + } + + throw new ArgumentException("Cannot determine provider name from connection string", nameof(connectionString)); + } + } +} diff --git a/src/Umbraco.Configuration/Models/ContentSettings.cs b/src/Umbraco.Configuration/Models/ContentSettings.cs new file mode 100644 index 0000000000..5bc31814b7 --- /dev/null +++ b/src/Umbraco.Configuration/Models/ContentSettings.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Macros; + +namespace Umbraco.Configuration.Models +{ + internal class ContentSettings : IContentSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "Content:"; + private const string NotificationsPrefix = Prefix + "Notifications:"; + private const string ImagingPrefix = Prefix + "Imaging:"; + private const string DefaultPreviewBadge = + @"
Preview modeClick to end
"; + + private static readonly ImagingAutoFillUploadField[] DefaultImagingAutoFillUploadField = + { + new ImagingAutoFillUploadField + { + Alias = "umbracoFile" + } + }; + + private readonly IConfiguration _configuration; + + public ContentSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public string NotificationEmailAddress => + _configuration.GetValue(NotificationsPrefix+"Email"); + + public bool DisableHtmlEmail => + _configuration.GetValue(NotificationsPrefix+"DisableHtmlEmail", false); + + public IEnumerable ImageFileTypes => _configuration.GetValue( + ImagingPrefix+"ImageFileTypes", new[] { "jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif" }); + + public IEnumerable ImageAutoFillProperties => + _configuration.GetValue(ImagingPrefix+"AutoFillImageProperties", + DefaultImagingAutoFillUploadField); + + + public bool ResolveUrlsFromTextString => + _configuration.GetValue(Prefix+"ResolveUrlsFromTextString", false); + + public IEnumerable Error404Collection => _configuration + .GetSection(Prefix+"Errors:Error404") + .GetChildren() + .Select(x => new ContentErrorPage(x)); + + public string PreviewBadge => _configuration.GetValue(Prefix+"PreviewBadge", DefaultPreviewBadge); + + public MacroErrorBehaviour MacroErrorBehaviour => + _configuration.GetValue(Prefix+"MacroErrors", MacroErrorBehaviour.Inline); + + public IEnumerable DisallowedUploadFiles => _configuration.GetValue( + Prefix+"DisallowedUploadFiles", + new[] { "ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd" }); + + public IEnumerable AllowedUploadFiles => + _configuration.GetValue(Prefix+"AllowedUploadFiles", Array.Empty()); + + public bool ShowDeprecatedPropertyEditors => + _configuration.GetValue(Prefix+"ShowDeprecatedPropertyEditors", false); + + public string LoginBackgroundImage => + _configuration.GetValue(Prefix+"LoginBackgroundImage", string.Empty); + + private class ContentErrorPage : IContentErrorPage + { + public ContentErrorPage(IConfigurationSection configurationSection) + { + Culture = configurationSection.Key; + + var value = configurationSection.Value; + + if (int.TryParse(value, out var contentId)) + { + HasContentId = true; + ContentId = contentId; + } + else if (Guid.TryParse(value, out var contentKey)) + { + HasContentKey = true; + ContentKey = contentKey; + } + else + { + ContentXPath = value; + } + } + + public int ContentId { get; } + public Guid ContentKey { get; } + public string ContentXPath { get; } + public bool HasContentId { get; } + public bool HasContentKey { get; } + public string Culture { get; set; } + } + + private class ImagingAutoFillUploadField : IImagingAutoFillUploadField + { + public string Alias { get; set; } + public string WidthFieldAlias { get; set; } + public string HeightFieldAlias { get; set; } + public string LengthFieldAlias { get; set; } + public string ExtensionFieldAlias { get; set; } + } + } +} diff --git a/src/Umbraco.Configuration/Models/CoreDebugSettings.cs b/src/Umbraco.Configuration/Models/CoreDebugSettings.cs new file mode 100644 index 0000000000..6d6c0eaf0d --- /dev/null +++ b/src/Umbraco.Configuration/Models/CoreDebugSettings.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class CoreDebugSettings : ICoreDebugSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "Core:Debug:"; + private readonly IConfiguration _configuration; + + public CoreDebugSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public bool LogUncompletedScopes => + _configuration.GetValue(Prefix+"LogUncompletedScopes", false); + + public bool DumpOnTimeoutThreadAbort => + _configuration.GetValue(Prefix+"DumpOnTimeoutThreadAbort", false); + } +} diff --git a/src/Umbraco.Configuration/Models/ExceptionFilterSettings.cs b/src/Umbraco.Configuration/Models/ExceptionFilterSettings.cs new file mode 100644 index 0000000000..581daf9f40 --- /dev/null +++ b/src/Umbraco.Configuration/Models/ExceptionFilterSettings.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class ExceptionFilterSettings : IExceptionFilterSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "ExceptionFilter:"; + private readonly IConfiguration _configuration; + + public ExceptionFilterSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public bool Disabled => _configuration.GetValue(Prefix+"Disabled", false); + } +} diff --git a/src/Umbraco.Configuration/Models/GlobalSettings.cs b/src/Umbraco.Configuration/Models/GlobalSettings.cs new file mode 100644 index 0000000000..4b30813bd5 --- /dev/null +++ b/src/Umbraco.Configuration/Models/GlobalSettings.cs @@ -0,0 +1,95 @@ +using System; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + /// + /// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information + /// from web.config appsettings + /// + internal class GlobalSettings : IGlobalSettings + { + private const string Prefix = Constants.Configuration.ConfigGlobalPrefix; + + internal const string + StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma! + + internal const string + StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; //must end with a comma! + + private readonly IConfiguration _configuration; + + public GlobalSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public string ReservedUrls => _configuration.GetValue(Prefix + "ReservedUrls", StaticReservedUrls); + public string ReservedPaths => _configuration.GetValue(Prefix + "ReservedPaths", StaticReservedPaths); + + // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4238 - stop having version in web.config appSettings + public string ConfigurationStatus + { + get => _configuration.GetValue(Prefix + "ConfigurationStatus"); + set => throw new NotImplementedException("We should remove this and only use the value from database"); + } + + public int TimeOutInMinutes => _configuration.GetValue(Prefix + "TimeOutInMinutes", 20); + public string DefaultUILanguage => _configuration.GetValue(Prefix + "TimeOutInMinutes", "en-US"); + + public bool HideTopLevelNodeFromPath => + _configuration.GetValue(Prefix + "HideTopLevelNodeFromPath", false); + + public bool UseHttps => _configuration.GetValue(Prefix + "UseHttps", false); + public int VersionCheckPeriod => _configuration.GetValue(Prefix + "VersionCheckPeriod", 7); + public string UmbracoPath => _configuration.GetValue(Prefix + "UmbracoPath", "~/umbraco"); + public string UmbracoCssPath => _configuration.GetValue(Prefix + "UmbracoCssPath", "~/css"); + + public string UmbracoScriptsPath => + _configuration.GetValue(Prefix + "UmbracoScriptsPath", "~/scripts"); + + public string UmbracoMediaPath => _configuration.GetValue(Prefix + "UmbracoMediaPath", "~/media"); + + public bool InstallMissingDatabase => + _configuration.GetValue(Prefix + "InstallMissingDatabase", false); + + public bool InstallEmptyDatabase => _configuration.GetValue(Prefix + "InstallEmptyDatabase", false); + + public bool DisableElectionForSingleServer => + _configuration.GetValue(Prefix + "DisableElectionForSingleServer", false); + + public string RegisterType => _configuration.GetValue(Prefix + "RegisterType", string.Empty); + + public string DatabaseFactoryServerVersion => + _configuration.GetValue(Prefix + "DatabaseFactoryServerVersion", string.Empty); + + public string MainDomLock => _configuration.GetValue(Prefix + "MainDomLock", string.Empty); + + public string NoNodesViewPath => + _configuration.GetValue(Prefix + "NoNodesViewPath", "~/config/splashes/NoNodes.cshtml"); + + public bool IsSmtpServerConfigured => + _configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp")?.GetChildren().Any() ?? false; + + public ISmtpSettings SmtpSettings => + new SmtpSettingsImpl(_configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp")); + + private class SmtpSettingsImpl : ISmtpSettings + { + private readonly IConfigurationSection _configurationSection; + + public SmtpSettingsImpl(IConfigurationSection configurationSection) + { + _configurationSection = configurationSection; + } + + public string From => _configurationSection.GetValue("From"); + public string Host => _configurationSection.GetValue("Host"); + public int Port => _configurationSection.GetValue("Port"); + public string PickupDirectoryLocation => _configurationSection.GetValue("PickupDirectoryLocation"); + } + } +} diff --git a/src/Umbraco.Configuration/Models/HealthChecksSettingsSettings.cs b/src/Umbraco.Configuration/Models/HealthChecksSettingsSettings.cs new file mode 100644 index 0000000000..1d73051ec8 --- /dev/null +++ b/src/Umbraco.Configuration/Models/HealthChecksSettingsSettings.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration.HealthChecks; + +namespace Umbraco.Configuration.Models +{ + internal class HealthChecksSettings : IHealthChecksSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "HealthChecks:"; + private readonly IConfiguration _configuration; + + public HealthChecksSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public IEnumerable DisabledChecks => _configuration + .GetSection(Prefix+"DisabledChecks") + .GetChildren() + .Select( + x => new DisabledHealthCheck + { + Id = x.GetValue("Id"), + DisabledOn = x.GetValue("DisabledOn"), + DisabledBy = x.GetValue("DisabledBy") + }); + + public IHealthCheckNotificationSettings NotificationSettings => + new HealthCheckNotificationSettings( + _configuration.GetSection(Prefix+"NotificationSettings")); + + private class DisabledHealthCheck : IDisabledHealthCheck + { + public Guid Id { get; set; } + public DateTime DisabledOn { get; set; } + public int DisabledBy { get; set; } + } + + private class HealthCheckNotificationSettings : IHealthCheckNotificationSettings + { + private readonly IConfigurationSection _configurationSection; + + public HealthCheckNotificationSettings(IConfigurationSection configurationSection) + { + _configurationSection = configurationSection; + } + + public bool Enabled => _configurationSection.GetValue("Enabled", false); + public string FirstRunTime => _configurationSection.GetValue("FirstRunTime"); + public int PeriodInHours => _configurationSection.GetValue("PeriodInHours", 24); + + public IReadOnlyDictionary NotificationMethods => _configurationSection + .GetSection("NotificationMethods") + .GetChildren() + .ToDictionary(x => x.Key, x => (INotificationMethod) new NotificationMethod(x.Key, x)); + + public IEnumerable DisabledChecks => _configurationSection + .GetSection("DisabledChecks").GetChildren().Select( + x => new DisabledHealthCheck + { + Id = x.GetValue("Id"), + DisabledOn = x.GetValue("DisabledOn"), + DisabledBy = x.GetValue("DisabledBy") + }); + } + + private class NotificationMethod : INotificationMethod + { + private readonly IConfigurationSection _configurationSection; + + public NotificationMethod(string alias, IConfigurationSection configurationSection) + { + Alias = alias; + _configurationSection = configurationSection; + } + + public string Alias { get; } + public bool Enabled => _configurationSection.GetValue("Enabled", false); + + public HealthCheckNotificationVerbosity Verbosity => + _configurationSection.GetValue("Verbosity", HealthCheckNotificationVerbosity.Summary); + + public bool FailureOnly => _configurationSection.GetValue("FailureOnly", true); + + public IReadOnlyDictionary Settings => _configurationSection + .GetSection("Settings").GetChildren().ToDictionary(x => x.Key, + x => (INotificationMethodSettings) new NotificationMethodSettings(x.Key, x.Value)); + } + + private class NotificationMethodSettings : INotificationMethodSettings + { + public NotificationMethodSettings(string key, string value) + { + Key = key; + Value = value; + } + + public string Key { get; } + public string Value { get; } + } + } +} diff --git a/src/Umbraco.Configuration/Models/HostingSettings.cs b/src/Umbraco.Configuration/Models/HostingSettings.cs new file mode 100644 index 0000000000..f0fbcf4cab --- /dev/null +++ b/src/Umbraco.Configuration/Models/HostingSettings.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class HostingSettings : IHostingSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "Hosting:"; + private readonly IConfiguration _configuration; + + public HostingSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + public LocalTempStorage LocalTempStorageLocation => + _configuration.GetValue(Prefix+"LocalTempStorage", LocalTempStorage.Default); + + public string ApplicationVirtualPath => null; + + /// + /// 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); + } +} diff --git a/src/Umbraco.Configuration/Models/ImagingSettings.cs b/src/Umbraco.Configuration/Models/ImagingSettings.cs new file mode 100644 index 0000000000..4a9501b2ba --- /dev/null +++ b/src/Umbraco.Configuration/Models/ImagingSettings.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class ImagingSettings : IImagingSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "Imaging:"; + private const string CachePrefix = Prefix + "Cache:"; + private const string ResizePrefix = Prefix + "Resize:"; + private readonly IConfiguration _configuration; + + public ImagingSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public int MaxBrowserCacheDays => _configuration.GetValue(CachePrefix + "MaxBrowserCacheDays", 7); + public int MaxCacheDays => _configuration.GetValue(CachePrefix + "MaxCacheDays", 365); + public uint CachedNameLength => _configuration.GetValue(CachePrefix + "CachedNameLength", (uint) 8); + public string CacheFolder => _configuration.GetValue(CachePrefix + "Folder", "../App_Data/Cache"); + public int MaxResizeWidth => _configuration.GetValue(ResizePrefix + "MaxWidth", 5000); + public int MaxResizeHeight => _configuration.GetValue(ResizePrefix + "MaxHeight", 5000); + } +} diff --git a/src/Umbraco.Configuration/Models/IndexCreatorSettings.cs b/src/Umbraco.Configuration/Models/IndexCreatorSettings.cs new file mode 100644 index 0000000000..b4bb000552 --- /dev/null +++ b/src/Umbraco.Configuration/Models/IndexCreatorSettings.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class IndexCreatorSettings : IIndexCreatorSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "Examine:"; + private readonly IConfiguration _configuration; + + public IndexCreatorSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public string LuceneDirectoryFactory => + _configuration.GetValue(Prefix + "LuceneDirectoryFactory"); + } +} diff --git a/src/Umbraco.Configuration/Models/KeepAliveSettings.cs b/src/Umbraco.Configuration/Models/KeepAliveSettings.cs new file mode 100644 index 0000000000..04194e1a3c --- /dev/null +++ b/src/Umbraco.Configuration/Models/KeepAliveSettings.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Models +{ + internal class KeepAliveSettings : IKeepAliveSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "KeepAlive:"; + private readonly IConfiguration _configuration; + + public KeepAliveSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public bool DisableKeepAliveTask => + _configuration.GetValue(Prefix + "DisableKeepAliveTask", false); + + public string KeepAlivePingUrl => _configuration.GetValue(Prefix + "KeepAlivePingUrl", + "{umbracoApplicationUrl}/api/keepalive/ping"); + } +} diff --git a/src/Umbraco.Configuration/Models/LoggingSettings.cs b/src/Umbraco.Configuration/Models/LoggingSettings.cs new file mode 100644 index 0000000000..b05fe03875 --- /dev/null +++ b/src/Umbraco.Configuration/Models/LoggingSettings.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Models +{ + internal class LoggingSettings : ILoggingSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "Logging:"; + private readonly IConfiguration _configuration; + + public LoggingSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public int MaxLogAge => _configuration.GetValue(Prefix + "MaxLogAge", -1); + } +} diff --git a/src/Umbraco.Configuration/Models/MemberPasswordConfigurationSettings.cs b/src/Umbraco.Configuration/Models/MemberPasswordConfigurationSettings.cs new file mode 100644 index 0000000000..c7b147349e --- /dev/null +++ b/src/Umbraco.Configuration/Models/MemberPasswordConfigurationSettings.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class MemberPasswordConfigurationSettings : IMemberPasswordConfiguration + { + private const string Prefix = Constants.Configuration.ConfigSecurityPrefix + "MemberPassword:"; + private readonly IConfiguration _configuration; + + public MemberPasswordConfigurationSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public int RequiredLength => + _configuration.GetValue(Prefix + "RequiredLength", 10); + + public bool RequireNonLetterOrDigit => + _configuration.GetValue(Prefix + "RequireNonLetterOrDigit", false); + + public bool RequireDigit => + _configuration.GetValue(Prefix + "RequireDigit", false); + + public bool RequireLowercase => + _configuration.GetValue(Prefix + "RequireLowercase", false); + + public bool RequireUppercase => + _configuration.GetValue(Prefix + "RequireUppercase", false); + + public string HashAlgorithmType => + _configuration.GetValue(Prefix + "HashAlgorithmType", "HMACSHA256"); + + public int MaxFailedAccessAttemptsBeforeLockout => + _configuration.GetValue(Prefix + "MaxFailedAccessAttemptsBeforeLockout", 5); + } +} diff --git a/src/Umbraco.Configuration/Models/ModelsBuilderConfig.cs b/src/Umbraco.Configuration/Models/ModelsBuilderConfig.cs new file mode 100644 index 0000000000..d111dbba70 --- /dev/null +++ b/src/Umbraco.Configuration/Models/ModelsBuilderConfig.cs @@ -0,0 +1,86 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + /// + /// Represents the models builder configuration. + /// + internal class ModelsBuilderConfig : IModelsBuilderConfig + { + private const string Prefix = Constants.Configuration.ConfigModelsBuilderPrefix; + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + public ModelsBuilderConfig(IConfiguration configuration) + { + _configuration = configuration; + } + + public string DefaultModelsDirectory => "~/App_Data/Models"; + + /// + /// Gets a value indicating whether the whole models experience is enabled. + /// + /// + /// If this is false then absolutely nothing happens. + /// Default value is false which means that unless we have this setting, nothing happens. + /// + public bool Enable => _configuration.GetValue(Prefix+"Enable", false); + + /// + /// Gets the models mode. + /// + public ModelsMode ModelsMode => + _configuration.GetValue(Prefix+"ModelsMode", ModelsMode.Nothing); + + /// + /// Gets the models namespace. + /// + /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. + public string ModelsNamespace => _configuration.GetValue(Prefix+"ModelsNamespace"); + + /// + /// Gets a value indicating whether we should enable the models factory. + /// + /// Default value is true because no factory is enabled by default in Umbraco. + public bool EnableFactory => _configuration.GetValue(Prefix+"EnableFactory", true); + + /// + /// Gets a value indicating whether we should flag out-of-date models. + /// + /// + /// Models become out-of-date when data types or content types are updated. When this + /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are + /// generated through the dashboard, the files is cleared. Default value is false. + /// + public bool FlagOutOfDateModels => + _configuration.GetValue(Prefix+"FlagOutOfDateModels", false) && !ModelsMode.IsLive(); + + /// + /// Gets the models directory. + /// + /// Default is ~/App_Data/Models but that can be changed. + public string ModelsDirectory => + _configuration.GetValue(Prefix+"ModelsDirectory", "~/App_Data/Models"); + + /// + /// Gets a value indicating whether to accept an unsafe value for ModelsDirectory. + /// + /// + /// An unsafe value is an absolute path, or a relative path pointing outside + /// of the website root. + /// + public bool AcceptUnsafeModelsDirectory => + _configuration.GetValue(Prefix+"AcceptUnsafeModelsDirectory", false); + + /// + /// Gets a value indicating the debug log level. + /// + /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). + public int DebugLevel => _configuration.GetValue(Prefix+"DebugLevel", 0); + } +} diff --git a/src/Umbraco.Configuration/Models/NuCacheSettings.cs b/src/Umbraco.Configuration/Models/NuCacheSettings.cs new file mode 100644 index 0000000000..51b8b1fe08 --- /dev/null +++ b/src/Umbraco.Configuration/Models/NuCacheSettings.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class NuCacheSettings : INuCacheSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "NuCache:"; + private readonly IConfiguration _configuration; + + public NuCacheSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public string BTreeBlockSize => _configuration.GetValue(Prefix+"BTreeBlockSize"); + } +} diff --git a/src/Umbraco.Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Configuration/Models/RequestHandlerSettings.cs new file mode 100644 index 0000000000..ce5cd65c20 --- /dev/null +++ b/src/Umbraco.Configuration/Models/RequestHandlerSettings.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Models +{ + internal class RequestHandlerSettings : IRequestHandlerSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "RequestHandler:"; + private static readonly CharItem[] DefaultCharCollection = + { + new CharItem { Char = " ", Replacement = "-" }, + new CharItem { Char = "\"", Replacement = "" }, + new CharItem { Char = "'", Replacement = "" }, + new CharItem { Char = "%", Replacement = "" }, + new CharItem { Char = ".", Replacement = "" }, + new CharItem { Char = ";", Replacement = "" }, + new CharItem { Char = "/", Replacement = "" }, + new CharItem { Char = "\\", Replacement = "" }, + new CharItem { Char = ":", Replacement = "" }, + new CharItem { Char = "#", Replacement = "" }, + new CharItem { Char = "+", Replacement = "plus" }, + new CharItem { Char = "*", Replacement = "star" }, + new CharItem { Char = "&", Replacement = "" }, + new CharItem { Char = "?", Replacement = "" }, + new CharItem { Char = "æ", Replacement = "ae" }, + new CharItem { Char = "ä", Replacement = "ae" }, + new CharItem { Char = "ø", Replacement = "oe" }, + new CharItem { Char = "ö", Replacement = "oe" }, + new CharItem { Char = "å", Replacement = "aa" }, + new CharItem { Char = "ü", Replacement = "ue" }, + new CharItem { Char = "ß", Replacement = "ss" }, + new CharItem { Char = "|", Replacement = "-" }, + new CharItem { Char = "<", Replacement = "" }, + new CharItem { Char = ">", Replacement = "" } + }; + + private readonly IConfiguration _configuration; + + public RequestHandlerSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public bool AddTrailingSlash => + _configuration.GetValue(Prefix+"AddTrailingSlash", true); + + public bool ConvertUrlsToAscii => _configuration + .GetValue(Prefix+"ConvertUrlsToAscii").InvariantEquals("true"); + + public bool TryConvertUrlsToAscii => _configuration + .GetValue(Prefix+"ConvertUrlsToAscii").InvariantEquals("try"); + + + //We need to special handle ":", as this character is special in keys + public IEnumerable CharCollection + { + get + { + var collection = _configuration.GetSection(Prefix + "CharCollection").GetChildren() + .Select(x => new CharItem() + { + Char = x.GetValue("Char"), + Replacement = x.GetValue("Replacement"), + }).ToArray(); + + if (collection.Any() || _configuration.GetSection("Prefix").GetChildren().Any(x => + x.Key.Equals("CharCollection", StringComparison.OrdinalIgnoreCase))) + { + return collection; + } + + return DefaultCharCollection; + } + } + + + public class CharItem : IChar + { + public string Char { get; set; } + public string Replacement { get; set; } + } + } +} diff --git a/src/Umbraco.Configuration/Models/RuntimeSettings.cs b/src/Umbraco.Configuration/Models/RuntimeSettings.cs new file mode 100644 index 0000000000..ef129030b6 --- /dev/null +++ b/src/Umbraco.Configuration/Models/RuntimeSettings.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class RuntimeSettings : IRuntimeSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "Runtime:"; + private readonly IConfiguration _configuration; + public RuntimeSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public int? MaxQueryStringLength => _configuration.GetValue(Prefix+"MaxRequestLength"); + public int? MaxRequestLength => _configuration.GetValue(Prefix+"MaxRequestLength"); + } +} diff --git a/src/Umbraco.Configuration/Models/SecuritySettings.cs b/src/Umbraco.Configuration/Models/SecuritySettings.cs new file mode 100644 index 0000000000..9244eace96 --- /dev/null +++ b/src/Umbraco.Configuration/Models/SecuritySettings.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Models +{ + internal class SecuritySettings : ISecuritySettings + { + private const string Prefix = Constants.Configuration.ConfigSecurityPrefix; + private readonly IConfiguration _configuration; + + public SecuritySettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public bool KeepUserLoggedIn => _configuration.GetValue(Prefix + "KeepUserLoggedIn", true); + + public bool HideDisabledUsersInBackoffice => + _configuration.GetValue(Prefix + "HideDisabledUsersInBackoffice", false); + + public bool AllowPasswordReset => + _configuration.GetValue(Prefix + "AllowPasswordResetAllowPasswordReset", true); + + public string AuthCookieName => + _configuration.GetValue(Prefix + "AuthCookieName", "UMB_UCONTEXT"); + + public string AuthCookieDomain => + _configuration.GetValue(Prefix + "AuthCookieDomain"); + + public bool UsernameIsEmail => _configuration.GetValue(Prefix + "UsernameIsEmail", true); + } +} diff --git a/src/Umbraco.Configuration/Models/TourSettings.cs b/src/Umbraco.Configuration/Models/TourSettings.cs new file mode 100644 index 0000000000..9fe1814ff5 --- /dev/null +++ b/src/Umbraco.Configuration/Models/TourSettings.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Configuration.Models +{ + internal class TourSettings : ITourSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "Tours:"; + private readonly IConfiguration _configuration; + + public TourSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public string Type { get; set; } + + public bool EnableTours => _configuration.GetValue(Prefix+"EnableTours", true); + } +} diff --git a/src/Umbraco.Configuration/Models/TypeFinderSettings.cs b/src/Umbraco.Configuration/Models/TypeFinderSettings.cs new file mode 100644 index 0000000000..8a1f7ac9e0 --- /dev/null +++ b/src/Umbraco.Configuration/Models/TypeFinderSettings.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class TypeFinderSettings : ITypeFinderSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "TypeFinder:"; + private readonly IConfiguration _configuration; + + public TypeFinderSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public string AssembliesAcceptingLoadExceptions => + _configuration.GetValue(Prefix+"AssembliesAcceptingLoadExceptions"); + } +} diff --git a/src/Umbraco.Configuration/Models/UserPasswordConfigurationSettings.cs b/src/Umbraco.Configuration/Models/UserPasswordConfigurationSettings.cs new file mode 100644 index 0000000000..5e68b16203 --- /dev/null +++ b/src/Umbraco.Configuration/Models/UserPasswordConfigurationSettings.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration.Models +{ + internal class UserPasswordConfigurationSettings : IUserPasswordConfiguration + { + private const string Prefix = Constants.Configuration.ConfigSecurityPrefix + "UserPassword:"; + private readonly IConfiguration _configuration; + + public UserPasswordConfigurationSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public int RequiredLength => _configuration.GetValue(Prefix + "RequiredLength", 10); + + public bool RequireNonLetterOrDigit => + _configuration.GetValue(Prefix + "RequireNonLetterOrDigit", false); + + public bool RequireDigit => _configuration.GetValue(Prefix + "RequireDigit", false); + + public bool RequireLowercase => + _configuration.GetValue(Prefix + "RequireLowercase", false); + + public bool RequireUppercase => + _configuration.GetValue(Prefix + "RequireUppercase", false); + + public string HashAlgorithmType => + _configuration.GetValue(Prefix + "HashAlgorithmType", "HMACSHA256"); + + public int MaxFailedAccessAttemptsBeforeLockout => + _configuration.GetValue(Prefix + "MaxFailedAccessAttemptsBeforeLockout", 5); + } +} diff --git a/src/Umbraco.Configuration/Models/WebRoutingSettings.cs b/src/Umbraco.Configuration/Models/WebRoutingSettings.cs new file mode 100644 index 0000000000..9ac856ca9f --- /dev/null +++ b/src/Umbraco.Configuration/Models/WebRoutingSettings.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Configuration.Models +{ + internal class WebRoutingSettings : IWebRoutingSettings + { + private const string Prefix = Constants.Configuration.ConfigPrefix + "WebRouting:"; + private readonly IConfiguration _configuration; + + public WebRoutingSettings(IConfiguration configuration) + { + _configuration = configuration; + } + + public bool TrySkipIisCustomErrors => + _configuration.GetValue(Prefix + "TrySkipIisCustomErrors", false); + + public bool InternalRedirectPreservesTemplate => + _configuration.GetValue(Prefix + "InternalRedirectPreservesTemplate", false); + + public bool DisableAlternativeTemplates => + _configuration.GetValue(Prefix + "DisableAlternativeTemplates", false); + + public bool ValidateAlternativeTemplates => + _configuration.GetValue(Prefix + "ValidateAlternativeTemplates", false); + + public bool DisableFindContentByIdPath => + _configuration.GetValue(Prefix + "DisableFindContentByIdPath", false); + + public bool DisableRedirectUrlTracking => + _configuration.GetValue(Prefix + "DisableRedirectUrlTracking", false); + + public string UrlProviderMode => + _configuration.GetValue(Prefix + "UrlProviderMode", UrlMode.Auto.ToString()); + + public string UmbracoApplicationUrl => + _configuration.GetValue(Prefix + "UmbracoApplicationUrl"); + } +} diff --git a/src/Umbraco.Configuration/Properties/AssemblyInfo.cs b/src/Umbraco.Configuration/Properties/AssemblyInfo.cs index d10dd929da..b77c087e22 100644 --- a/src/Umbraco.Configuration/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Configuration/Properties/AssemblyInfo.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; // Umbraco Cms [assembly: InternalsVisibleTo("Umbraco.Tests")] +[assembly: InternalsVisibleTo("Umbraco.Tests.Common")] [assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] // Allow this to be mocked in our unit tests diff --git a/src/Umbraco.Configuration/Umbraco.Configuration.csproj b/src/Umbraco.Configuration/Umbraco.Configuration.csproj index 57fca1dfd6..2337ea24f8 100644 --- a/src/Umbraco.Configuration/Umbraco.Configuration.csproj +++ b/src/Umbraco.Configuration/Umbraco.Configuration.csproj @@ -22,6 +22,9 @@ + + + @@ -29,4 +32,10 @@ + + + <_Parameter1>Umbraco.Tests.Integration + + + diff --git a/src/Umbraco.Configuration/UmbracoSettings/BackOfficeElement.cs b/src/Umbraco.Configuration/UmbracoSettings/BackOfficeElement.cs index 79bff51d05..46b9bf32a9 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/BackOfficeElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/BackOfficeElement.cs @@ -7,6 +7,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("tours")] internal TourConfigElement Tours => (TourConfigElement)this["tours"]; - ITourSection IBackOfficeSection.Tours => Tours; + ITourSettings IBackOfficeSection.Tours => Tours; } } diff --git a/src/Umbraco.Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Configuration/UmbracoSettings/ContentElement.cs index 8e4f0edd8f..28b4314c9a 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/ContentElement.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Macros; namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class ContentElement : UmbracoConfigurationElement, IContentSection + internal class ContentElement : UmbracoConfigurationElement, IContentSettings { private const string DefaultPreviewBadge = @"
Preview modeClick to end
"; @@ -40,26 +40,26 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("loginBackgroundImage")] internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty); - string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress; + string IContentSettings.NotificationEmailAddress => Notifications.NotificationEmailAddress; - bool IContentSection.DisableHtmlEmail => Notifications.DisableHtmlEmail; + bool IContentSettings.DisableHtmlEmail => Notifications.DisableHtmlEmail; - IEnumerable IContentSection.ImageFileTypes => Imaging.ImageFileTypes; + IEnumerable IContentSettings.ImageFileTypes => Imaging.ImageFileTypes; - IEnumerable IContentSection.ImageAutoFillProperties => Imaging.ImageAutoFillProperties; + IEnumerable IContentSettings.ImageAutoFillProperties => Imaging.ImageAutoFillProperties; - bool IContentSection.ResolveUrlsFromTextString => ResolveUrlsFromTextString; + bool IContentSettings.ResolveUrlsFromTextString => ResolveUrlsFromTextString; - string IContentSection.PreviewBadge => PreviewBadge; + string IContentSettings.PreviewBadge => PreviewBadge; - MacroErrorBehaviour IContentSection.MacroErrorBehaviour => MacroErrors; + MacroErrorBehaviour IContentSettings.MacroErrorBehaviour => MacroErrors; - IEnumerable IContentSection.DisallowedUploadFiles => DisallowedUploadFiles; + IEnumerable IContentSettings.DisallowedUploadFiles => DisallowedUploadFiles; - IEnumerable IContentSection.AllowedUploadFiles => AllowedUploadFiles; + IEnumerable IContentSettings.AllowedUploadFiles => AllowedUploadFiles; - bool IContentSection.ShowDeprecatedPropertyEditors => ShowDeprecatedPropertyEditors; + bool IContentSettings.ShowDeprecatedPropertyEditors => ShowDeprecatedPropertyEditors; - string IContentSection.LoginBackgroundImage => LoginBackgroundImage; + string IContentSettings.LoginBackgroundImage => LoginBackgroundImage; } } diff --git a/src/Umbraco.Configuration/UmbracoSettings/KeepAliveElement.cs b/src/Umbraco.Configuration/UmbracoSettings/KeepAliveElement.cs index 89ba9be54d..2297fb4e20 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/KeepAliveElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/KeepAliveElement.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class KeepAliveElement : ConfigurationElement, IKeepAliveSection + internal class KeepAliveElement : ConfigurationElement, IKeepAliveSettings { [ConfigurationProperty("disableKeepAliveTask", DefaultValue = "false")] public bool DisableKeepAliveTask => (bool)base["disableKeepAliveTask"]; diff --git a/src/Umbraco.Configuration/UmbracoSettings/LoggingElement.cs b/src/Umbraco.Configuration/UmbracoSettings/LoggingElement.cs index 106b6cc134..2fdd61e169 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/LoggingElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/LoggingElement.cs @@ -3,12 +3,12 @@ using System.Configuration; namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class LoggingElement : UmbracoConfigurationElement, ILoggingSection + internal class LoggingElement : UmbracoConfigurationElement, ILoggingSettings { [ConfigurationProperty("maxLogAge")] internal InnerTextConfigurationElement MaxLogAge => GetOptionalTextElement("maxLogAge", -1); - int ILoggingSection.MaxLogAge => MaxLogAge; + int ILoggingSettings.MaxLogAge => MaxLogAge; } } diff --git a/src/Umbraco.Configuration/UmbracoSettings/MemberPasswordConfigurationElement.cs b/src/Umbraco.Configuration/UmbracoSettings/MemberPasswordConfigurationElement.cs index 93c7c20159..92cd112630 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/MemberPasswordConfigurationElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/MemberPasswordConfigurationElement.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class MemberPasswordConfigurationElement : PasswordConfigurationElement, IMemberPasswordConfigurationSection + internal class MemberPasswordConfigurationElement : PasswordConfigurationElement, IMemberPasswordConfiguration { } } diff --git a/src/Umbraco.Configuration/UmbracoSettings/RequestHandlerElement.cs b/src/Umbraco.Configuration/UmbracoSettings/RequestHandlerElement.cs index 80fcb6ca1a..f959a56e71 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/RequestHandlerElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/RequestHandlerElement.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class RequestHandlerElement : UmbracoConfigurationElement, IRequestHandlerSection + internal class RequestHandlerElement : UmbracoConfigurationElement, IRequestHandlerSettings { [ConfigurationProperty("addTrailingSlash")] public InnerTextConfigurationElement AddTrailingSlash => GetOptionalTextElement("addTrailingSlash", true); @@ -85,12 +85,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return collection; } - bool IRequestHandlerSection.AddTrailingSlash => AddTrailingSlash; + bool IRequestHandlerSettings.AddTrailingSlash => AddTrailingSlash; - bool IRequestHandlerSection.ConvertUrlsToAscii => UrlReplacing.ConvertUrlsToAscii.InvariantEquals("true"); + bool IRequestHandlerSettings.ConvertUrlsToAscii => UrlReplacing.ConvertUrlsToAscii.InvariantEquals("true"); - bool IRequestHandlerSection.TryConvertUrlsToAscii => UrlReplacing.ConvertUrlsToAscii.InvariantEquals("try"); + bool IRequestHandlerSettings.TryConvertUrlsToAscii => UrlReplacing.ConvertUrlsToAscii.InvariantEquals("try"); - IEnumerable IRequestHandlerSection.CharCollection => UrlReplacing.CharCollection; + IEnumerable IRequestHandlerSettings.CharCollection => UrlReplacing.CharCollection; } } diff --git a/src/Umbraco.Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Configuration/UmbracoSettings/SecurityElement.cs index 82012cfd0f..aec6809298 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/SecurityElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/SecurityElement.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class SecurityElement : UmbracoConfigurationElement, ISecuritySection + internal class SecurityElement : UmbracoConfigurationElement, ISecuritySettings { [ConfigurationProperty("keepUserLoggedIn")] internal InnerTextConfigurationElement KeepUserLoggedIn => GetOptionalTextElement("keepUserLoggedIn", true); @@ -38,14 +38,14 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("memberPasswordConfiguration")] public MemberPasswordConfigurationElement MemberPasswordConfiguration => (MemberPasswordConfigurationElement)this["memberPasswordConfiguration"]; - bool ISecuritySection.KeepUserLoggedIn => KeepUserLoggedIn; + bool ISecuritySettings.KeepUserLoggedIn => KeepUserLoggedIn; - bool ISecuritySection.HideDisabledUsersInBackoffice => HideDisabledUsersInBackoffice; + bool ISecuritySettings.HideDisabledUsersInBackoffice => HideDisabledUsersInBackoffice; /// /// Used to enable/disable the forgot password functionality on the back office login screen /// - bool ISecuritySection.AllowPasswordReset => AllowPasswordReset; + bool ISecuritySettings.AllowPasswordReset => AllowPasswordReset; /// /// A boolean indicating that by default the email address will be the username @@ -54,14 +54,10 @@ namespace Umbraco.Core.Configuration.UmbracoSettings /// Even if this is true and the username is different from the email in the database, the username field will still be shown. /// When this is false, the username and email fields will be shown in the user section. /// - bool ISecuritySection.UsernameIsEmail => UsernameIsEmail; + bool ISecuritySettings.UsernameIsEmail => UsernameIsEmail; - string ISecuritySection.AuthCookieName => AuthCookieName; + string ISecuritySettings.AuthCookieName => AuthCookieName; - string ISecuritySection.AuthCookieDomain => AuthCookieDomain; - - IUserPasswordConfigurationSection ISecuritySection.UserPasswordConfiguration => UserPasswordConfiguration; - - IMemberPasswordConfigurationSection ISecuritySection.MemberPasswordConfiguration => MemberPasswordConfiguration; + string ISecuritySettings.AuthCookieDomain => AuthCookieDomain; } } diff --git a/src/Umbraco.Configuration/UmbracoSettings/TourConfigElement.cs b/src/Umbraco.Configuration/UmbracoSettings/TourConfigElement.cs index dab69f3da0..f75b71fc57 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/TourConfigElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/TourConfigElement.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class TourConfigElement : UmbracoConfigurationElement, ITourSection + internal class TourConfigElement : UmbracoConfigurationElement, ITourSettings { //disabled by default so that upgraders don't get it enabled by default // TODO: we probably just want to disable the initial one from automatically loading ? diff --git a/src/Umbraco.Configuration/UmbracoSettings/UmbracoSettingsSection.cs b/src/Umbraco.Configuration/UmbracoSettings/UmbracoSettingsSection.cs index e605b86edf..781d00b979 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/UmbracoSettingsSection.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/UmbracoSettingsSection.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class UmbracoSettingsSection : ConfigurationSection, IUmbracoSettingsSection + internal class UmbracoSettingsSection : ConfigurationSection { [ConfigurationProperty("backOffice")] public BackOfficeElement BackOffice => (BackOfficeElement)this["backOffice"]; @@ -24,19 +24,5 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("keepAlive")] internal KeepAliveElement KeepAlive => (KeepAliveElement)this["keepAlive"]; - - IContentSection IUmbracoSettingsSection.Content => Content; - - ISecuritySection IUmbracoSettingsSection.Security => Security; - - IRequestHandlerSection IUmbracoSettingsSection.RequestHandler => RequestHandler; - - IBackOfficeSection IUmbracoSettingsSection.BackOffice => BackOffice; - - ILoggingSection IUmbracoSettingsSection.Logging => Logging; - - IWebRoutingSection IUmbracoSettingsSection.WebRouting => WebRouting; - - IKeepAliveSection IUmbracoSettingsSection.KeepAlive => KeepAlive; } } diff --git a/src/Umbraco.Configuration/UmbracoSettings/UserPasswordConfigurationElement.cs b/src/Umbraco.Configuration/UmbracoSettings/UserPasswordConfigurationElement.cs index 8128f3d8e7..a1d2aa8842 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/UserPasswordConfigurationElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/UserPasswordConfigurationElement.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class UserPasswordConfigurationElement : PasswordConfigurationElement, IUserPasswordConfigurationSection + internal class UserPasswordConfigurationElement : PasswordConfigurationElement, IUserPasswordConfiguration { } } diff --git a/src/Umbraco.Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Configuration/UmbracoSettings/WebRoutingElement.cs index 7b7102f2e7..206fc213d2 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/WebRoutingElement.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - internal class WebRoutingElement : ConfigurationElement, IWebRoutingSection + internal class WebRoutingElement : ConfigurationElement, IWebRoutingSettings { [ConfigurationProperty("trySkipIisCustomErrors", DefaultValue = "false")] public bool TrySkipIisCustomErrors => (bool) base["trySkipIisCustomErrors"]; diff --git a/src/Umbraco.Configuration/UmbracoVersionExtensions.cs b/src/Umbraco.Configuration/UmbracoVersionExtensions.cs deleted file mode 100644 index 168bb16f57..0000000000 --- a/src/Umbraco.Configuration/UmbracoVersionExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Configuration; -using Semver; -using Umbraco.Core; -using Umbraco.Core.Configuration; - -namespace Umbraco.Configuration -{ - public static class UmbracoVersionExtensions - { - /// - /// Gets the "local" version of the site. - /// - /// - /// Three things have a version, really: the executing code, the database model, - /// and the site/files. The database model version is entirely managed via migrations, - /// and changes during an upgrade. The executing code version changes when new code is - /// deployed. The site/files version changes during an upgrade. - /// - public static SemVersion LocalVersion(this IUmbracoVersion umbracoVersion) - { - try - { - // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4238 - stop having version in web.config appSettings - var value = ConfigurationManager.AppSettings[Constants.AppSettings.ConfigurationStatus]; - return value.IsNullOrWhiteSpace() ? null : SemVersion.TryParse(value, out var semver) ? semver : null; - } - catch - { - return null; - } - } - } -} diff --git a/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs b/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs index ec1a9210a7..eaea63b3a3 100644 --- a/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs +++ b/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs @@ -4,7 +4,7 @@ using System.Linq; using Umbraco.Core.Composing; namespace Umbraco.Web.Actions { - internal class ActionCollectionBuilder : LazyCollectionBuilderBase + public class ActionCollectionBuilder : LazyCollectionBuilderBase { protected override ActionCollectionBuilder This => this; diff --git a/src/Umbraco.Core/AssemblyExtensions.cs b/src/Umbraco.Core/AssemblyExtensions.cs index 0d0115ce20..19162e5934 100644 --- a/src/Umbraco.Core/AssemblyExtensions.cs +++ b/src/Umbraco.Core/AssemblyExtensions.cs @@ -6,6 +6,35 @@ namespace Umbraco.Core { public static class AssemblyExtensions { + private static string _rootDir = ""; + + /// + /// Utility method that returns the path to the root of the application, by getting the path to where the assembly where this + /// method is included is present, then traversing until it's past the /bin directory. Ie. this makes it work + /// even if the assembly is in a /bin/debug or /bin/release folder + /// + /// + public static string GetRootDirectorySafe(this Assembly executingAssembly) + { + if (string.IsNullOrEmpty(_rootDir) == false) + { + return _rootDir; + } + + var codeBase = executingAssembly.CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + var baseDirectory = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(baseDirectory)) + throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured."); + + _rootDir = baseDirectory.Contains("bin") + ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) + : baseDirectory; + + return _rootDir; + } + /// /// Returns the file used to load the assembly /// diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs index 452f897372..e70b40160e 100644 --- a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs +++ b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs @@ -73,7 +73,7 @@ namespace Umbraco.Core.Cache var result = SafeLazy.GetSafeLazy(factory); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache // do not store null values (backward compat), clone / reset to go into the cache - return value == null ? null : CheckCloneableAndTracksChanges(value); + return value == null ? null : CheckCloneableAndTracksChanges(value); // clone / reset to go into the cache }, timeout, isSliding, dependentFiles); @@ -107,9 +107,9 @@ namespace Umbraco.Core.Cache } /// - public void ClearOfType(string typeName) + public void ClearOfType(Type type) { - InnerCache.ClearOfType(typeName); + InnerCache.ClearOfType(type); } /// diff --git a/src/Umbraco.Core/Cache/DictionaryAppCache.cs b/src/Umbraco.Core/Cache/DictionaryAppCache.cs index d372916240..04ee3e0afa 100644 --- a/src/Umbraco.Core/Cache/DictionaryAppCache.cs +++ b/src/Umbraco.Core/Cache/DictionaryAppCache.cs @@ -71,16 +71,16 @@ namespace Umbraco.Core.Cache } /// - public virtual void ClearOfType(string typeName) + public virtual void ClearOfType(Type type) { - _items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType().ToString().InvariantEquals(typeName)); + _items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == type); } /// public virtual void ClearOfType() { var typeOfT = typeof(T); - _items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT); + ClearOfType(typeOfT); } /// diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs index 159f9cd7cb..54009af465 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs @@ -12,12 +12,6 @@ namespace Umbraco.Core.Cache /// public class FastDictionaryAppCache : IAppCache { - private readonly ITypeFinder _typeFinder; - - public FastDictionaryAppCache(ITypeFinder typeFinder) - { - _typeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); - } /// /// Gets the internal items dictionary, for tests only! @@ -83,9 +77,8 @@ namespace Umbraco.Core.Cache } /// - public void ClearOfType(string typeName) + public void ClearOfType(Type type) { - var type = _typeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs index f417c5ffd0..bb55762826 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs @@ -12,13 +12,6 @@ namespace Umbraco.Core.Cache /// public abstract class FastDictionaryAppCacheBase : IAppCache { - private readonly ITypeFinder _typeFinder; - - protected FastDictionaryAppCacheBase(ITypeFinder typeFinder) - { - _typeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); - } - // prefix cache keys so we know which one are ours protected const string CacheItemPrefix = "umbrtmche"; @@ -121,9 +114,8 @@ namespace Umbraco.Core.Cache } /// - public virtual void ClearOfType(string typeName) + public virtual void ClearOfType(Type type) { - var type = _typeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs index e698d93ebe..6ce43a7bc9 100644 --- a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Cache /// /// Initializes a new instance of the class with a context, for unit tests! /// - public HttpRequestAppCache(Func requestItems, ITypeFinder typeFinder) : base(typeFinder) + public HttpRequestAppCache(Func requestItems) : base() { ContextItems = requestItems; } diff --git a/src/Umbraco.Core/Cache/IAppCache.cs b/src/Umbraco.Core/Cache/IAppCache.cs index 674781f6d6..c84ec1135c 100644 --- a/src/Umbraco.Core/Cache/IAppCache.cs +++ b/src/Umbraco.Core/Cache/IAppCache.cs @@ -51,14 +51,14 @@ namespace Umbraco.Core.Cache /// /// Removes items of a specified type from the cache. /// - /// The name of the type to remove. + /// The type to remove. /// /// If the type is an interface, then all items of a type implementing that interface are /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from /// the specified type are not removed). /// Performs a case-sensitive search. /// - void ClearOfType(string typeName); + void ClearOfType(Type type); /// /// Removes items of a specified type from the cache. diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs index 0863577487..009e9f38d0 100644 --- a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs @@ -53,7 +53,7 @@ namespace Umbraco.Web.Cache { macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Id)); } - }; + } base.Refresh(json); } diff --git a/src/Umbraco.Core/Cache/NoAppCache.cs b/src/Umbraco.Core/Cache/NoAppCache.cs index 60bc6fb8b8..cae3a7381e 100644 --- a/src/Umbraco.Core/Cache/NoAppCache.cs +++ b/src/Umbraco.Core/Cache/NoAppCache.cs @@ -67,7 +67,7 @@ namespace Umbraco.Core.Cache { } /// - public virtual void ClearOfType(string typeName) + public virtual void ClearOfType(Type type) { } /// diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs index 208390276a..dc9163affb 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -13,15 +13,13 @@ namespace Umbraco.Core.Cache /// public class ObjectCacheAppCache : IAppPolicyCache { - private readonly ITypeFinder _typeFinder; private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); /// /// Initializes a new instance of the . /// - public ObjectCacheAppCache(ITypeFinder typeFinder) + public ObjectCacheAppCache() { - _typeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); // the MemoryCache is created with name "in-memory". That name is // used to retrieve configuration options. It does not identify the memory cache, i.e. // each instance of this class has its own, independent, memory cache. @@ -178,9 +176,8 @@ namespace Umbraco.Core.Cache } /// - public virtual void ClearOfType(string typeName) + public virtual void ClearOfType(Type type) { - var type = _typeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Core/Collections/TopoGraph.cs b/src/Umbraco.Core/Collections/TopoGraph.cs index b8ded4a458..955a210465 100644 --- a/src/Umbraco.Core/Collections/TopoGraph.cs +++ b/src/Umbraco.Core/Collections/TopoGraph.cs @@ -126,7 +126,7 @@ namespace Umbraco.Core.Collections if (_items.TryGetValue(key, out value)) yield return value; else if (throwOnMissing) - throw new Exception(MissingDependencyError); + throw new Exception($"{MissingDependencyError} Error in type {typeof(TItem).Name}, with key {key}"); } } } diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs index 41038ea4e9..0d398be83b 100644 --- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs @@ -79,9 +79,12 @@ namespace Umbraco.Core.Composing foreach (var type in types) EnsureType(type, "register"); - // register them + // register them - ensuring that each item is registered with the same lifetime as the collection. + // NOTE: Previously each one was not registered with the same lifetime which would mean that if there + // was a dependency on an individual item, it would resolve a brand new transient instance which isn't what + // we would expect to happen. The same item should be resolved from the container as the collection. foreach (var type in types) - register.Register(type); + register.Register(type, CollectionLifetime); _registeredTypes = types; } diff --git a/src/Umbraco.Core/Composing/Composition.cs b/src/Umbraco.Core/Composing/Composition.cs index a186a1f00a..f6e8655575 100644 --- a/src/Umbraco.Core/Composing/Composition.cs +++ b/src/Umbraco.Core/Composing/Composition.cs @@ -31,15 +31,16 @@ namespace Umbraco.Core.Composing /// The runtime state. /// Optional configs. /// An IOHelper + /// public Composition(IRegister register, TypeLoader typeLoader, IProfilingLogger logger, IRuntimeState runtimeState, Configs configs, IIOHelper ioHelper, AppCaches appCaches) { - _register = register; - TypeLoader = typeLoader; - Logger = logger; - RuntimeState = runtimeState; - Configs = configs; - IOHelper = ioHelper; - AppCaches = appCaches; + _register = register ?? throw new ArgumentNullException(nameof(register)); + TypeLoader = typeLoader ?? throw new ArgumentNullException(nameof(typeLoader)); + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + RuntimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState)); + Configs = configs ?? throw new ArgumentNullException(nameof(configs)); + IOHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); + AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches)); } #region Services @@ -135,7 +136,7 @@ namespace Umbraco.Core.Composing IFactory factory = null; - Configs.RegisterWith(_register, () => factory); + Configs.RegisterWith(_register); // ReSharper disable once AccessToModifiedClosure -- on purpose _register.Register(_ => factory, Lifetime.Singleton); diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs new file mode 100644 index 0000000000..4d153d8922 --- /dev/null +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Returns a list of scannable assemblies based on an entry point assembly and it's references + /// + /// + /// This will recursively search through the entry point's assemblies and Umbraco's core assemblies and their references + /// to create a list of scannable assemblies based on whether they themselves or their transitive dependencies reference Umbraco core assemblies. + /// + public class DefaultUmbracoAssemblyProvider : IAssemblyProvider + { + private readonly Assembly _entryPointAssembly; + private static readonly string[] UmbracoCoreAssemblyNames = new[] + { + "Umbraco.Core", + "Umbraco.Web", + "Umbraco.Infrastructure", + "Umbraco.PublishedCache.NuCache", + "Umbraco.ModelsBuilder.Embedded", + "Umbraco.Examine.Lucene", + "Umbraco.Web.Common", + "Umbraco.Web.BackOffice", + "Umbraco.Web.Website", + }; + + public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly) + { + _entryPointAssembly = entryPointAssembly ?? throw new ArgumentNullException(nameof(entryPointAssembly)); + } + + // TODO: It would be worth investigating a netcore3 version of this which would use + // var allAssemblies = System.Runtime.Loader.AssemblyLoadContext.All.SelectMany(x => x.Assemblies); + // that will still only resolve Assemblies that are already loaded but it would also make it possible to + // query dynamically generated assemblies once they are added. It would also provide the ability to probe + // assembly locations that are not in the same place as the entry point assemblies. + + public IEnumerable Assemblies + { + get + { + var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, UmbracoCoreAssemblyNames, true); + return finder.Find(); + } + } + } +} diff --git a/src/Umbraco.Core/Composing/DisableAttribute.cs b/src/Umbraco.Core/Composing/DisableAttribute.cs index c2a419f8bc..e826f1c472 100644 --- a/src/Umbraco.Core/Composing/DisableAttribute.cs +++ b/src/Umbraco.Core/Composing/DisableAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace Umbraco.Core.Composing { @@ -21,6 +22,11 @@ namespace Umbraco.Core.Composing public DisableAttribute() { } + public DisableAttribute(string fullTypeName, string assemblyName) + { + DisabledType = Assembly.Load(assemblyName)?.GetType(fullTypeName); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs new file mode 100644 index 0000000000..9378941166 --- /dev/null +++ b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Finds Assemblies from the entry point assemblies, it's dependencies and it's transitive dependencies that reference that targetAssemblyNames + /// + /// + /// borrowed and modified from here https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs + /// + internal class FindAssembliesWithReferencesTo + { + private readonly Assembly[] _referenceAssemblies; + private readonly string[] _targetAssemblies; + private readonly bool _includeTargets; + + /// + /// Constructor + /// + /// Entry point assemblies + /// Used to check if the entry point or it's transitive assemblies reference these assembly names + /// If true will also use the target assembly names as entry point assemblies + public FindAssembliesWithReferencesTo(Assembly[] referenceAssemblies, string[] targetAssemblyNames, bool includeTargets) + { + _referenceAssemblies = referenceAssemblies; + _targetAssemblies = targetAssemblyNames; + _includeTargets = includeTargets; + } + + public IEnumerable Find() + { + var referenceItems = new List(); + foreach (var assembly in _referenceAssemblies) + { + referenceItems.Add(assembly); + } + + if (_includeTargets) + { + foreach(var target in _targetAssemblies) + { + try + { + referenceItems.Add(Assembly.Load(target)); + } + catch (FileNotFoundException) + { + // occurs if we cannot load this ... for example in a test project where we aren't currently referencing Umbraco.Web, etc... + } + } + } + + var provider = new ReferenceResolver(_targetAssemblies, referenceItems); + var assemblyNames = provider.ResolveAssemblies(); + return assemblyNames.ToList(); + } + + } +} diff --git a/src/Umbraco.Core/Composing/IAssemblyProvider.cs b/src/Umbraco.Core/Composing/IAssemblyProvider.cs new file mode 100644 index 0000000000..bde97a9556 --- /dev/null +++ b/src/Umbraco.Core/Composing/IAssemblyProvider.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides a list of assemblies that can be scanned + /// + public interface IAssemblyProvider + { + IEnumerable Assemblies { get; } + } +} diff --git a/src/Umbraco.Core/Composing/IRuntimeHash.cs b/src/Umbraco.Core/Composing/IRuntimeHash.cs new file mode 100644 index 0000000000..c2fb829cc6 --- /dev/null +++ b/src/Umbraco.Core/Composing/IRuntimeHash.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Composing +{ + /// + /// Used to create a hash value of the current runtime + /// + /// + /// This is used to detect if the runtime itself has changed, like a DLL has changed or another dynamically compiled + /// part of the application has changed. This is used to detect if we need to re-type scan. + /// + public interface IRuntimeHash + { + string GetHashValue(); + } +} diff --git a/src/Umbraco.Core/Composing/ITypeFinder.cs b/src/Umbraco.Core/Composing/ITypeFinder.cs index f302976dd6..7ed6084074 100644 --- a/src/Umbraco.Core/Composing/ITypeFinder.cs +++ b/src/Umbraco.Core/Composing/ITypeFinder.cs @@ -51,5 +51,14 @@ namespace Umbraco.Core.Composing Type attributeType, IEnumerable assemblies, bool onlyConcreteClasses); + + /// + /// Gets a hash value of the current runtime + /// + /// + /// This is used to detect if the runtime itself has changed, like a DLL has changed or another dynamically compiled + /// part of the application has changed. This is used to detect if we need to re-type scan. + /// + string GetRuntimeHash(); } } diff --git a/src/Umbraco.Core/Composing/ReferenceResolver.cs b/src/Umbraco.Core/Composing/ReferenceResolver.cs new file mode 100644 index 0000000000..65dba8bf23 --- /dev/null +++ b/src/Umbraco.Core/Composing/ReferenceResolver.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively. + /// + /// + /// Borrowed and modified from https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/ReferenceResolver.cs + /// + internal class ReferenceResolver + { + private readonly HashSet _umbracoAssemblies; + private readonly IReadOnlyList _assemblies; + private readonly Dictionary _classifications; + private readonly List _lookup = new List(); + + public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList entryPointAssemblies) + { + _umbracoAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); + _assemblies = entryPointAssemblies; + _classifications = new Dictionary(); + + foreach (var item in entryPointAssemblies) + { + _lookup.Add(item); + } + } + + /// + /// Returns a list of assemblies that directly reference or transitively reference the targetAssemblies + /// + /// + /// + /// This includes all assemblies in the same location as the entry point assemblies + /// + public IEnumerable ResolveAssemblies() + { + var applicationParts = new List(); + + var assemblies = new HashSet(_assemblies); + + // Get the unique directories of the assemblies + var assemblyLocations = GetAssemblyFolders(assemblies).ToList(); + + // Load in each assembly in the directory of the entry assembly to be included in the search + // for Umbraco dependencies/transitive dependencies + foreach(var dir in assemblyLocations) + { + foreach(var dll in Directory.EnumerateFiles(dir, "*.dll")) + { + var assemblyName = AssemblyName.GetAssemblyName(dll); + + // don't include if this is excluded + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => assemblyName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) + continue; + + // don't include this item if it's Umbraco + // TODO: We should maybe pass an explicit list of these names in? + if (assemblyName.FullName.StartsWith("Umbraco.")) + continue; + + var assembly = Assembly.Load(assemblyName); + assemblies.Add(assembly); + } + } + + foreach (var item in assemblies) + { + var classification = Resolve(item); + if (classification == Classification.ReferencesUmbraco || classification == Classification.IsUmbraco) + { + applicationParts.Add(item); + } + } + + return applicationParts; + } + + + private IEnumerable GetAssemblyFolders(IEnumerable assemblies) + { + return assemblies.Select(x => Path.GetDirectoryName(GetAssemblyLocation(x)).ToLowerInvariant()).Distinct(); + } + + // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ApplicationParts/RelatedAssemblyAttribute.cs + private string GetAssemblyLocation(Assembly assembly) + { + if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) && + result.IsFile && string.IsNullOrWhiteSpace(result.Fragment)) + { + return result.LocalPath; + } + + return assembly.Location; + } + + private Classification Resolve(Assembly assembly) + { + if (_classifications.TryGetValue(assembly, out var classification)) + { + return classification; + } + + // Initialize the dictionary with a value to short-circuit recursive references. + classification = Classification.Unknown; + _classifications[assembly] = classification; + + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => assembly.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) + { + // if its part of the filter it doesn't reference umbraco + classification = Classification.DoesNotReferenceUmbraco; + } + else if (_umbracoAssemblies.Contains(assembly.GetName().Name)) + { + classification = Classification.IsUmbraco; + } + else + { + classification = Classification.DoesNotReferenceUmbraco; + foreach (var reference in GetReferences(assembly)) + { + // recurse + var referenceClassification = Resolve(reference); + + if (referenceClassification == Classification.IsUmbraco || referenceClassification == Classification.ReferencesUmbraco) + { + classification = Classification.ReferencesUmbraco; + break; + } + } + } + + Debug.Assert(classification != Classification.Unknown); + _classifications[assembly] = classification; + return classification; + } + + protected virtual IEnumerable GetReferences(Assembly assembly) + { + foreach (var referenceName in assembly.GetReferencedAssemblies()) + { + // don't include if this is excluded + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => referenceName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) + continue; + + var reference = Assembly.Load(referenceName); + + if (!_lookup.Contains(reference)) + { + // A dependency references an item that isn't referenced by this project. + // We'll add this reference so that we can calculate the classification. + + _lookup.Add(reference); + } + yield return reference; + } + } + + protected enum Classification + { + Unknown, + DoesNotReferenceUmbraco, + ReferencesUmbraco, + IsUmbraco, + } + } +} diff --git a/src/Umbraco.Core/Composing/RuntimeHash.cs b/src/Umbraco.Core/Composing/RuntimeHash.cs new file mode 100644 index 0000000000..16665384c6 --- /dev/null +++ b/src/Umbraco.Core/Composing/RuntimeHash.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Composing +{ + /// + /// Determines the runtime hash based on file system paths to scan + /// + public class RuntimeHash : IRuntimeHash + { + private readonly IProfilingLogger _logger; + private readonly RuntimeHashPaths _paths; + + public RuntimeHash(IProfilingLogger logger, RuntimeHashPaths paths) + { + _logger = logger; + _paths = paths; + } + + + public string GetHashValue() + { + var allPaths = _paths.GetFolders() + .Select(x => ((FileSystemInfo) x, false)) + .Concat(_paths.GetFiles().Select(x => ((FileSystemInfo) x.Key, x.Value))); + + var hash = GetFileHash(allPaths); + + return hash; + } + + /// + /// Returns a unique hash for a combination of FileInfo objects. + /// + /// A collection of files. + /// The hash. + /// Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the + /// file properties (false) or the file contents (true). + private string GetFileHash(IEnumerable<(FileSystemInfo fileOrFolder, bool scanFileContent)> filesAndFolders) + { + using (_logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) + { + // get the distinct file infos to hash + var uniqInfos = new HashSet(); + var uniqContent = new HashSet(); + + using var generator = new HashGenerator(); + + foreach (var (fileOrFolder, scanFileContent) in filesAndFolders) + { + if (scanFileContent) + { + // add each unique file's contents to the hash + // normalize the content for cr/lf and case-sensitivity + if (uniqContent.Add(fileOrFolder.FullName)) + { + if (File.Exists(fileOrFolder.FullName) == false) continue; + var content = RemoveCrLf(File.ReadAllText(fileOrFolder.FullName)); + generator.AddCaseInsensitiveString(content); + } + } + else + { + // add each unique folder/file to the hash + if (uniqInfos.Add(fileOrFolder.FullName)) + { + generator.AddFileSystemItem(fileOrFolder); + } + } + } + return generator.GenerateHash(); + } + } + + // fast! (yes, according to benchmarks) + private static string RemoveCrLf(string s) + { + var buffer = new char[s.Length]; + var count = 0; + // ReSharper disable once ForCanBeConvertedToForeach - no! + for (var i = 0; i < s.Length; i++) + { + if (s[i] != '\r' && s[i] != '\n') + buffer[count++] = s[i]; + } + return new string(buffer, 0, count); + } + } +} diff --git a/src/Umbraco.Core/Composing/RuntimeHashPaths.cs b/src/Umbraco.Core/Composing/RuntimeHashPaths.cs new file mode 100644 index 0000000000..8b5af064af --- /dev/null +++ b/src/Umbraco.Core/Composing/RuntimeHashPaths.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.IO; + +namespace Umbraco.Core.Composing +{ + /// + /// Paths used to determine the + /// + public sealed class RuntimeHashPaths + { + private readonly List _paths = new List(); + private readonly Dictionary _files = new Dictionary(); + + public void AddFolder(DirectoryInfo pathInfo) => _paths.Add(pathInfo); + public void AddFile(FileInfo fileInfo, bool scanFileContent = false) => _files.Add(fileInfo, scanFileContent); + + public IEnumerable GetFolders() => _paths; + public IReadOnlyDictionary GetFiles() => _files; + } +} diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 9d88153b0a..645182d66b 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -1,106 +1,37 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Configuration; -using System.IO; using System.Linq; using System.Reflection; using System.Security; using System.Text; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core.Composing { + /// public class TypeFinder : ITypeFinder { private readonly ILogger _logger; - - public TypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty(); - _allAssemblies = new Lazy>(() => - { - HashSet assemblies = null; - try - { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = GetRootDirectorySafe(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) - { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } - } - - //Since we are only loading in the /bin assemblies above, we will also load in anything that's already loaded (which will include gac items) - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - { - assemblies.Add(a); - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - return assemblies; - }); - } - - //Lazy access to the all assemblies list - private readonly Lazy> _allAssemblies; + private readonly IAssemblyProvider _assemblyProvider; + private readonly IRuntimeHash _runtimeHash; private volatile HashSet _localFilteredAssemblyCache; private readonly object _localFilteredAssemblyCacheLocker = new object(); private readonly List _notifiedLoadExceptionAssemblies = new List(); - private static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); - private string _rootDir = ""; + private static readonly ConcurrentDictionary TypeNamesCache = new ConcurrentDictionary(); private readonly string[] _assembliesAcceptingLoadExceptions; - // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here - private string GetRootDirectorySafe() + // used for benchmark tests + internal bool QueryWithReferencingAssemblies = true; + + public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, IRuntimeHash runtimeHash, ITypeFinderConfig typeFinderConfig = null) { - if (string.IsNullOrEmpty(_rootDir) == false) - { - return _rootDir; - } - - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - var baseDirectory = Path.GetDirectoryName(path); - if (string.IsNullOrEmpty(baseDirectory)) - throw new PanicException("No root directory could be resolved."); - - _rootDir = baseDirectory.Contains("bin") - ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) - : baseDirectory; - - return _rootDir; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _assemblyProvider = assemblyProvider; + _runtimeHash = runtimeHash; + _assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty(); } private bool AcceptsLoadExceptions(Assembly a) @@ -119,22 +50,8 @@ namespace Umbraco.Core.Composing }); } - /// - /// lazily load a reference to all assemblies and only local assemblies. - /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder - /// - /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been - /// loaded in the CLR, not all assemblies. - /// See these threads: - /// http://issues.umbraco.org/issue/U5-198 - /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app - /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl - /// - private IEnumerable GetAllAssemblies() - { - return _allAssemblies.Value; - } + + private IEnumerable GetAllAssemblies() => _assemblyProvider.Assemblies; /// public IEnumerable AssembliesToScan @@ -181,7 +98,10 @@ namespace Umbraco.Core.Composing /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" /// - private static readonly string[] KnownAssemblyExclusionFilter = { + internal static readonly string[] KnownAssemblyExclusionFilter = { + "mscorlib,", + "netstandard,", + "System,", "Antlr3.", "AutoMapper,", "AutoMapper.", @@ -228,7 +148,14 @@ namespace Umbraco.Core.Composing "WebDriver,", "itextsharp,", "mscorlib,", - "nunit.framework,", + "NUnit,", + "NUnit.", + "NUnit3.", + "Selenium.", + "ImageProcessor", + "MiniProfiler.", + "Owin,", + "SQLite", }; /// @@ -283,6 +210,9 @@ namespace Umbraco.Core.Composing return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses); } + /// + public string GetRuntimeHash() => _runtimeHash.GetHashValue(); + /// /// Returns a Type for the string type name /// @@ -290,6 +220,11 @@ namespace Umbraco.Core.Composing /// public virtual Type GetTypeByName(string name) { + + //NOTE: This will not find types in dynamic assemblies unless those assemblies are already loaded + //into the appdomain. + + // This is exactly what the BuildManager does, if the type is an assembly qualified type // name it will find it. if (TypeNameContainsAssembly(name)) @@ -340,18 +275,24 @@ namespace Umbraco.Core.Composing var stack = new Stack(); stack.Push(attributeType.Assembly); + if (!QueryWithReferencingAssemblies) + { + foreach (var a in candidateAssemblies) + stack.Push(a); + } + while (stack.Count > 0) { var assembly = stack.Pop(); - Type[] assemblyTypes = null; + IReadOnlyList assemblyTypes = null; if (assembly != attributeType.Assembly || attributeAssemblyIsCandidate) { // get all assembly types that can be assigned to baseType try { assemblyTypes = GetTypesWithFormattedException(assembly) - .ToArray(); // in try block + .ToList(); // in try block } catch (TypeLoadException ex) { @@ -371,10 +312,13 @@ namespace Umbraco.Core.Composing if (assembly != attributeType.Assembly && assemblyTypes.Where(attributeType.IsAssignableFrom).Any() == false) continue; - foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + if (QueryWithReferencingAssemblies) { - candidateAssemblies.Remove(referencing); - stack.Push(referencing); + foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + { + candidateAssemblies.Remove(referencing); + stack.Push(referencing); + } } } @@ -405,19 +349,25 @@ namespace Umbraco.Core.Composing var stack = new Stack(); stack.Push(baseType.Assembly); + if (!QueryWithReferencingAssemblies) + { + foreach (var a in candidateAssemblies) + stack.Push(a); + } + while (stack.Count > 0) { var assembly = stack.Pop(); // get all assembly types that can be assigned to baseType - Type[] assemblyTypes = null; + IReadOnlyList assemblyTypes = null; if (assembly != baseType.Assembly || baseTypeAssemblyIsCandidate) { try { assemblyTypes = GetTypesWithFormattedException(assembly) .Where(baseType.IsAssignableFrom) - .ToArray(); // in try block + .ToList(); // in try block } catch (TypeLoadException ex) { @@ -437,10 +387,13 @@ namespace Umbraco.Core.Composing if (assembly != baseType.Assembly && assemblyTypes.All(x => x.IsSealed)) continue; - foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + if (QueryWithReferencingAssemblies) { - candidateAssemblies.Remove(referencing); - stack.Push(referencing); + foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + { + candidateAssemblies.Remove(referencing); + stack.Push(referencing); + } } } @@ -522,6 +475,5 @@ namespace Umbraco.Core.Composing #endregion - } } diff --git a/src/Umbraco.Core/Composing/TypeFinderConfig.cs b/src/Umbraco.Core/Composing/TypeFinderConfig.cs new file mode 100644 index 0000000000..3dc672b27c --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeFinderConfig.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Core.Composing +{ + /// + /// TypeFinder config via appSettings + /// + internal class TypeFinderConfig : ITypeFinderConfig + { + private readonly ITypeFinderSettings _settings; + private IEnumerable _assembliesAcceptingLoadExceptions; + + public TypeFinderConfig(ITypeFinderSettings settings) + { + _settings = settings; + } + + public IEnumerable AssembliesAcceptingLoadExceptions + { + get + { + if (_assembliesAcceptingLoadExceptions != null) + return _assembliesAcceptingLoadExceptions; + + var s = _settings.AssembliesAcceptingLoadExceptions; + return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) + ? Array.Empty() + : s.Split(',').Select(x => x.Trim()).ToArray(); + } + } + } +} diff --git a/src/Umbraco.Core/Composing/TypeHelper.cs b/src/Umbraco.Core/Composing/TypeHelper.cs index 28eab6a5ec..1987a4059c 100644 --- a/src/Umbraco.Core/Composing/TypeHelper.cs +++ b/src/Umbraco.Core/Composing/TypeHelper.cs @@ -82,9 +82,9 @@ namespace Umbraco.Core.Composing /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot /// reference that assembly, same with the global.asax assembly. /// - public static Assembly[] GetReferencingAssemblies(Assembly assembly, IEnumerable assemblies) + public static IReadOnlyList GetReferencingAssemblies(Assembly assembly, IEnumerable assemblies) { - if (assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly()) + if (assembly.IsDynamic || assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly()) return EmptyAssemblies; @@ -92,7 +92,7 @@ namespace Umbraco.Core.Composing // should only be scanning those assemblies because any other assembly will definitely not // contain sub type's of the one we're currently looking for var name = assembly.GetName().Name; - return assemblies.Where(x => x == assembly || HasReference(x, name)).ToArray(); + return assemblies.Where(x => x == assembly || HasReference(x, name)).ToList(); } /// diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 76d00c472d..1d40149936 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -14,8 +14,6 @@ using File = System.IO.File; namespace Umbraco.Core.Composing { - - /// /// Provides methods to find and instantiate types. /// @@ -29,7 +27,6 @@ namespace Umbraco.Core.Composing { private const string CacheKey = "umbraco-types.list"; - private readonly IIOHelper _ioHelper; private readonly IAppPolicyCache _runtimeCache; private readonly IProfilingLogger _logger; @@ -49,30 +46,27 @@ namespace Umbraco.Core.Composing /// /// Initializes a new instance of the class. /// - /// /// /// The application runtime cache. /// Files storage location. /// A profiling logger. /// - public TypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, IEnumerable assembliesToScan = null) - : this(ioHelper, typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan) + public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, IEnumerable assembliesToScan = null) + : this(typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan) { } /// /// Initializes a new instance of the class. /// - /// /// /// The application runtime cache. /// Files storage location. /// A profiling logger. /// Whether to detect changes using hashes. /// - public TypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable assembliesToScan = null) + public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable assembliesToScan = null) { TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); - _ioHelper = ioHelper; _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); _localTempPath = localTempPath; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -124,7 +118,7 @@ namespace Umbraco.Core.Composing /// This is for unit tests. /// // internal for tests - public IEnumerable AssembliesToScan => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan); + public IEnumerable AssembliesToScan => _assemblies ??= TypeFinder.AssembliesToScan; /// /// Gets the type lists. @@ -183,19 +177,7 @@ namespace Umbraco.Core.Composing if (_currentAssembliesHash != null) return _currentAssembliesHash; - _currentAssembliesHash = GetFileHash(new List> - { - // TODO: Would be nicer to abstract this logic out into IAssemblyHash - - // TODO: Use constants from SystemDirectories when we can (once it's ported to netstandard lib) - - // the bin folder and everything in it - new Tuple(new DirectoryInfo(_ioHelper.MapPath("~/bin")), false), - // the app code folder and everything in it - new Tuple(new DirectoryInfo(_ioHelper.MapPath("~/App_Code")), false), - // global.asax (the app domain also monitors this, if it changes will do a full restart) - new Tuple(new FileInfo(_ioHelper.MapPath("~/global.asax")), false) - }, _logger); + _currentAssembliesHash = TypeFinder.GetRuntimeHash(); return _currentAssembliesHash; } @@ -210,92 +192,6 @@ namespace Umbraco.Core.Composing File.WriteAllText(typesHashFilePath, CurrentAssembliesHash, Encoding.UTF8); } - /// - /// Returns a unique hash for a combination of FileInfo objects. - /// - /// A collection of files. - /// A profiling logger. - /// The hash. - /// Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the - /// file properties (false) or the file contents (true). - private static string GetFileHash(IEnumerable> filesAndFolders, IProfilingLogger logger) - { - using (logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) - { - // get the distinct file infos to hash - var uniqInfos = new HashSet(); - var uniqContent = new HashSet(); - using (var generator = new HashGenerator()) - { - foreach (var fileOrFolder in filesAndFolders) - { - var info = fileOrFolder.Item1; - if (fileOrFolder.Item2) - { - // add each unique file's contents to the hash - // normalize the content for cr/lf and case-sensitivity - if (uniqContent.Add(info.FullName)) - { - if (File.Exists(info.FullName) == false) continue; - var content = RemoveCrLf(File.ReadAllText(info.FullName)); - generator.AddCaseInsensitiveString(content); - } - } - else - { - // add each unique folder/file to the hash - if (uniqInfos.Add(info.FullName)) - { - generator.AddFileSystemItem(info); - } - } - } - return generator.GenerateHash(); - } - } - } - - // fast! (yes, according to benchmarks) - private static string RemoveCrLf(string s) - { - var buffer = new char[s.Length]; - var count = 0; - // ReSharper disable once ForCanBeConvertedToForeach - no! - for (var i = 0; i < s.Length; i++) - { - if (s[i] != '\r' && s[i] != '\n') - buffer[count++] = s[i]; - } - return new string(buffer, 0, count); - } - - /// - /// Returns a unique hash for a combination of FileInfo objects. - /// - /// A collection of files. - /// A profiling logger. - /// The hash. - // internal for tests - public static string GetFileHash(IEnumerable filesAndFolders, IProfilingLogger logger) - { - using (logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) - { - using (var generator = new HashGenerator()) - { - // get the distinct file infos to hash - var uniqInfos = new HashSet(); - - foreach (var fileOrFolder in filesAndFolders) - { - if (uniqInfos.Contains(fileOrFolder.FullName)) continue; - uniqInfos.Add(fileOrFolder.FullName); - generator.AddFileSystemItem(fileOrFolder); - } - return generator.GenerateHash(); - } - } - } - #endregion #region Cache @@ -516,29 +412,29 @@ namespace Umbraco.Core.Composing #region Get Assembly Attributes - /// - /// Gets the assembly attributes of the specified type . - /// - /// The attribute type. - /// - /// The assembly attributes of the specified type . - /// - public IEnumerable GetAssemblyAttributes() - where T : Attribute - { - return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); - } + ///// + ///// Gets the assembly attributes of the specified type . + ///// + ///// The attribute type. + ///// + ///// The assembly attributes of the specified type . + ///// + //public IEnumerable GetAssemblyAttributes() + // where T : Attribute + //{ + // return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + //} - /// - /// Gets all the assembly attributes. - /// - /// - /// All assembly attributes. - /// - public IEnumerable GetAssemblyAttributes() - { - return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); - } + ///// + ///// Gets all the assembly attributes. + ///// + ///// + ///// All assembly attributes. + ///// + //public IEnumerable GetAssemblyAttributes() + //{ + // return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + //} /// /// Gets the assembly attributes of the specified . diff --git a/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs b/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs new file mode 100644 index 0000000000..034af3b80c --- /dev/null +++ b/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs @@ -0,0 +1,19 @@ +using System; + +namespace Umbraco.Core.Composing +{ + /// + /// A runtime hash this is always different on each app startup + /// + public sealed class VaryingRuntimeHash : IRuntimeHash + { + private readonly string _hash; + + public VaryingRuntimeHash() + { + _hash = DateTime.Now.Ticks.ToString(); + } + + public string GetHashValue() => _hash; + } +} diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index bea78f82ed..bbea868f55 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -1,6 +1,13 @@ using Umbraco.Core.Composing; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Actions; +using Umbraco.Web.ContentApps; using Umbraco.Web.Dashboards; +using Umbraco.Web.Editors; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.Routing; +using Umbraco.Web.Sections; +using Umbraco.Web.Tour; namespace Umbraco.Core { @@ -9,6 +16,72 @@ namespace Umbraco.Core #region Collection Builders + /// + /// Gets the actions collection builder. + /// + /// The composition. + /// + public static ActionCollectionBuilder Actions(this Composition composition) + => composition.WithCollectionBuilder(); + + /// + /// Gets the content apps collection builder. + /// + /// The composition. + /// + public static ContentAppFactoryCollectionBuilder ContentApps(this Composition composition) + => composition.WithCollectionBuilder(); + + /// + /// Gets the content finders collection builder. + /// + /// The composition. + /// + public static ContentFinderCollectionBuilder ContentFinders(this Composition composition) + => composition.WithCollectionBuilder(); + + /// + /// Gets the editor validators collection builder. + /// + /// The composition. + /// + public static EditorValidatorCollectionBuilder EditorValidators(this Composition composition) + => composition.WithCollectionBuilder(); + + /// + /// Gets the health checks collection builder. + /// + /// The composition. + public static HealthCheckCollectionBuilder HealthChecks(this Composition composition) + => composition.WithCollectionBuilder(); + + /// + /// Gets the TourFilters collection builder. + /// + public static TourFilterCollectionBuilder TourFilters(this Composition composition) + => composition.WithCollectionBuilder(); + + /// + /// Gets the url providers collection builder. + /// + /// The composition. + public static UrlProviderCollectionBuilder UrlProviders(this Composition composition) + => composition.WithCollectionBuilder(); + + /// + /// Gets the media url providers collection builder. + /// + /// The composition. + public static MediaUrlProviderCollectionBuilder MediaUrlProviders(this Composition composition) + => composition.WithCollectionBuilder(); + + /// + /// Gets the backoffice sections/applications collection builder. + /// + /// The composition. + public static SectionCollectionBuilder Sections(this Composition composition) + => composition.WithCollectionBuilder(); + /// /// Gets the components collection builder. /// diff --git a/src/Umbraco.Core/CompositionExtensions_Uniques.cs b/src/Umbraco.Core/CompositionExtensions_Uniques.cs index 52f1ce7ecd..8352eb33ec 100644 --- a/src/Umbraco.Core/CompositionExtensions_Uniques.cs +++ b/src/Umbraco.Core/CompositionExtensions_Uniques.cs @@ -31,5 +31,20 @@ namespace Umbraco.Core /// public static void RegisterUnique(this Composition composition, TService instance) => composition.RegisterUnique(typeof(TService), instance); + + + + /// + /// Registers a unique service with an implementation type. + /// + public static void RegisterMultipleUnique(this Composition composition) + where TImplementing : class, TService1, TService2 + where TService1 : class + where TService2 : class + { + composition.RegisterUnique(); + composition.RegisterUnique(factory => factory.GetInstance()); + composition.RegisterUnique(factory => factory.GetInstance()); + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Configs.cs b/src/Umbraco.Core/Configuration/Configs.cs index abb06d525f..821ee308f0 100644 --- a/src/Umbraco.Core/Configuration/Configs.cs +++ b/src/Umbraco.Core/Configuration/Configs.cs @@ -13,16 +13,8 @@ namespace Umbraco.Core.Configuration /// public class Configs { - private readonly Func _configSectionResolver; - - public Configs(Func configSectionResolver) - { - _configSectionResolver = configSectionResolver ?? throw new ArgumentNullException(nameof(configSectionResolver)); - } - private readonly Dictionary> _configs = new Dictionary>(); private Dictionary> _registerings = new Dictionary>(); - private Lazy _factory; /// /// Gets a configuration. @@ -52,61 +44,15 @@ namespace Umbraco.Core.Configuration _registerings[typeOfConfig] = register => register.Register(_ => (TConfig) lazyConfigFactory.Value, Lifetime.Singleton); } - /// - /// Adds a configuration, provided by a factory. - /// - public void Add(Func configFactory) - where TConfig : class - { - // make sure it is not too late - if (_registerings == null) - throw new InvalidOperationException("Configurations have already been registered."); - - var typeOfConfig = typeof(TConfig); - - _configs[typeOfConfig] = new Lazy(() => - { - if (!(_factory is null)) return _factory.Value.GetInstance(); - throw new InvalidOperationException($"Cannot get configuration of type {typeOfConfig} during composition."); - }); - _registerings[typeOfConfig] = register => register.Register(configFactory, Lifetime.Singleton); - } - - /// - /// Adds a configuration, provided by a configuration section. - /// - public void Add(string sectionName) - where TConfig : class - { - Add(() => GetConfig(sectionName)); - } - - private TConfig GetConfig(string sectionName) - where TConfig : class - { - // note: need to use SafeCallContext here because ConfigurationManager.GetSection ends up getting AppDomain.Evidence - // which will want to serialize the call context including anything that is in there - what a mess! - - using (new SafeCallContext()) - { - if ((_configSectionResolver(sectionName) is TConfig config)) - return config; - var ex = new InvalidOperationException($"Could not get configuration section \"{sectionName}\" from config files."); - throw ex; - } - } - /// /// Registers configurations in a register. /// - public void RegisterWith(IRegister register, Func factory) + public void RegisterWith(IRegister register) { // do it only once if (_registerings == null) throw new InvalidOperationException("Configurations have already been registered."); - _factory = new Lazy(factory); - register.Register(this); foreach (var registering in _registerings.Values) diff --git a/src/Umbraco.Core/Configuration/ConfigsExtensions.cs b/src/Umbraco.Core/Configuration/ConfigsExtensions.cs index 1723785069..f9ea352399 100644 --- a/src/Umbraco.Core/Configuration/ConfigsExtensions.cs +++ b/src/Umbraco.Core/Configuration/ConfigsExtensions.cs @@ -1,13 +1,7 @@ -using System.IO; -using Umbraco.Core.Cache; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Manifest; namespace Umbraco.Core { @@ -16,6 +10,9 @@ namespace Umbraco.Core /// public static class ConfigsExtensions { + + public static IImagingSettings Imaging(this Configs configs) + => configs.GetConfig(); public static IGlobalSettings Global(this Configs configs) => configs.GetConfig(); @@ -25,47 +22,28 @@ namespace Umbraco.Core public static IConnectionStrings ConnectionStrings(this Configs configs) => configs.GetConfig(); - public static IUmbracoSettingsSection Settings(this Configs configs) - => configs.GetConfig(); + public static IContentSettings Content(this Configs configs) + => configs.GetConfig(); - public static IHealthChecks HealthChecks(this Configs configs) - => configs.GetConfig(); + public static ISecuritySettings Security(this Configs configs) + => configs.GetConfig(); - public static IGridConfig Grids(this Configs configs) - => configs.GetConfig(); - - public static ICoreDebug CoreDebug(this Configs configs) - => configs.GetConfig(); public static IUserPasswordConfiguration UserPasswordConfiguration(this Configs configs) => configs.GetConfig(); - public static IMemberPasswordConfiguration MemberPasswordConfiguration(this Configs configs) => configs.GetConfig(); - public static void AddPasswordConfigurations(this Configs configs) - { - configs.Add(() => - { - return new UserPasswordConfiguration(configs.Settings().Security.UserPasswordConfiguration); - }); - configs.Add(() => - { - return new MemberPasswordConfiguration(configs.Settings().Security.MemberPasswordConfiguration); - }); - } + public static IRequestHandlerSettings RequestHandler(this Configs configs) + => configs.GetConfig(); - public static void AddCoreConfigs(this Configs configs, IIOHelper ioHelper) - { - var configDir = new DirectoryInfo(ioHelper.MapPath(Constants.SystemDirectories.Config)); + public static IWebRoutingSettings WebRouting(this Configs configs) + => configs.GetConfig(); + + public static IHealthChecksSettings HealthChecks(this Configs configs) + => configs.GetConfig(); + public static ICoreDebugSettings CoreDebug(this Configs configs) + => configs.GetConfig(); - // GridConfig depends on runtime caches, manifest parsers... and cannot be available during composition - configs.Add(factory => new GridConfig( - factory.GetInstance(), - factory.GetInstance(), - configDir, - factory.GetInstance(), - factory.GetInstance().Debug)); - } } } diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs index 4d8039dfbb..a9a7f578d0 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs @@ -1,12 +1,25 @@ using System; -using Umbraco.Core.IO; +using Umbraco.Core.Hosting; namespace Umbraco.Core.Configuration { public static class GlobalSettingsExtensions { private static string _mvcArea; + private static string _backOfficePath; + /// + /// Returns the absolute path for the Umbraco back office + /// + /// + /// + /// + public static string GetBackOfficePath(this IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) + { + if (_backOfficePath != null) return _backOfficePath; + _backOfficePath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath); + return _backOfficePath; + } /// /// This returns the string of the MVC Area route. @@ -19,27 +32,27 @@ namespace Umbraco.Core.Configuration /// We also make sure that the virtual directory (SystemDirectories.Root) is stripped off first, otherwise we'd end up with something /// like "MyVirtualDirectory-Umbraco" instead of just "Umbraco". /// - public static string GetUmbracoMvcArea(this IGlobalSettings globalSettings, IIOHelper ioHelper) + public static string GetUmbracoMvcArea(this IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { if (_mvcArea != null) return _mvcArea; - _mvcArea = GetUmbracoMvcAreaNoCache(globalSettings, ioHelper); + _mvcArea = globalSettings.GetUmbracoMvcAreaNoCache(hostingEnvironment); return _mvcArea; } - internal static string GetUmbracoMvcAreaNoCache(this IGlobalSettings globalSettings, IIOHelper ioHelper) + internal static string GetUmbracoMvcAreaNoCache(this IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { - if (globalSettings.Path.IsNullOrWhiteSpace()) - { - throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); - } + var path = string.IsNullOrEmpty(globalSettings.UmbracoPath) + ? string.Empty + : hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath); - var path = globalSettings.Path; - if (path.StartsWith(ioHelper.Root)) // beware of TrimStart, see U4-2518 - path = path.Substring(ioHelper.Root.Length); + if (path.IsNullOrWhiteSpace()) + throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); + + if (path.StartsWith(hostingEnvironment.ApplicationVirtualPath)) // beware of TrimStart, see U4-2518 + path = path.Substring(hostingEnvironment.ApplicationVirtualPath.Length); return path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower(); } - } } diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs index de4f7ccd5a..72c720e3d6 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs @@ -1,15 +1,18 @@ using System.IO; using Umbraco.Core.Cache; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; +using Umbraco.Core.Serialization; namespace Umbraco.Core.Configuration.Grid { public class GridConfig : IGridConfig { - public GridConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, IManifestParser manifestParser, bool isDebug) + public GridConfig(AppCaches appCaches, IIOHelper ioHelper, IManifestParser manifestParser, IJsonSerializer jsonSerializer, IHostingEnvironment hostingEnvironment) { - EditorsConfig = new GridEditorsConfig(logger, appCaches, configFolder, manifestParser, isDebug); + EditorsConfig = new GridEditorsConfig(appCaches, ioHelper, manifestParser, jsonSerializer, hostingEnvironment.IsDebugMode); } public IGridEditorsConfig EditorsConfig { get; } diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index a1ebf008fc..410c83ff1a 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -1,27 +1,30 @@ using System; using System.Collections.Generic; using System.IO; +using Umbraco.Composing; using Umbraco.Core.Cache; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Serialization; namespace Umbraco.Core.Configuration.Grid { internal class GridEditorsConfig : IGridEditorsConfig { - private readonly ILogger _logger; private readonly AppCaches _appCaches; - private readonly DirectoryInfo _configFolder; + private readonly IIOHelper _ioHelper; private readonly IManifestParser _manifestParser; private readonly bool _isDebug; + private readonly IJsonSerializer _jsonSerializer; - public GridEditorsConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, IManifestParser manifestParser, bool isDebug) + public GridEditorsConfig(AppCaches appCaches, IIOHelper ioHelper, IManifestParser manifestParser,IJsonSerializer jsonSerializer, bool isDebug) { - _logger = logger; _appCaches = appCaches; - _configFolder = configFolder; + _ioHelper = ioHelper; _manifestParser = manifestParser; + _jsonSerializer = jsonSerializer; _isDebug = isDebug; } @@ -31,19 +34,20 @@ namespace Umbraco.Core.Configuration.Grid { List GetResult() { + var configFolder = new DirectoryInfo(_ioHelper.MapPath(Constants.SystemDirectories.Config)); var editors = new List(); - var gridConfig = Path.Combine(_configFolder.FullName, "grid.editors.config.js"); + var gridConfig = Path.Combine(configFolder.FullName, "grid.editors.config.js"); if (File.Exists(gridConfig)) { var sourceString = File.ReadAllText(gridConfig); try { - editors.AddRange(_manifestParser.ParseGridEditors(sourceString)); + editors.AddRange(_jsonSerializer.Deserialize>(sourceString)); } catch (Exception ex) { - _logger.Error(ex, "Could not parse the contents of grid.editors.config.js into a JSON array '{Json}", sourceString); + Current.Logger.Error(ex, "Could not parse the contents of grid.editors.config.js into a JSON array '{Json}", sourceString); } } @@ -63,7 +67,6 @@ namespace Umbraco.Core.Configuration.Grid return result; } - } } } diff --git a/src/Umbraco.Core/Configuration/HealthChecks/IHealthChecks.cs b/src/Umbraco.Core/Configuration/HealthChecks/IHealthChecksSettings.cs similarity index 84% rename from src/Umbraco.Core/Configuration/HealthChecks/IHealthChecks.cs rename to src/Umbraco.Core/Configuration/HealthChecks/IHealthChecksSettings.cs index fa98e3b054..785e8d5651 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/IHealthChecks.cs +++ b/src/Umbraco.Core/Configuration/HealthChecks/IHealthChecksSettings.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.HealthChecks { - public interface IHealthChecks + public interface IHealthChecksSettings { IEnumerable DisabledChecks { get; } IHealthCheckNotificationSettings NotificationSettings { get; } diff --git a/src/Umbraco.Core/Configuration/HealthChecks/TrySkipIisCustomErrorsCheck.cs b/src/Umbraco.Core/Configuration/HealthChecks/TrySkipIisCustomErrorsCheck.cs index 58172ba063..e6ec49a1e7 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/TrySkipIisCustomErrorsCheck.cs +++ b/src/Umbraco.Core/Configuration/HealthChecks/TrySkipIisCustomErrorsCheck.cs @@ -13,13 +13,13 @@ namespace Umbraco.Web.HealthCheck.Checks.Config Group = "Configuration")] public class TrySkipIisCustomErrorsCheck : AbstractConfigCheck { - private readonly IHostingEnvironment _hostingEnvironment; + private readonly Version _iisVersion; public TrySkipIisCustomErrorsCheck(ILocalizedTextService textService, IIOHelper ioHelper, ILogger logger, IHostingEnvironment hostingEnvironment) : base(textService, ioHelper, logger) { - _hostingEnvironment = hostingEnvironment; + _iisVersion = hostingEnvironment.IISVersion; } public override string FilePath => "~/Config/umbracoSettings.config"; @@ -33,7 +33,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config get { // beware! 7.5 and 7.5.0 are not the same thing! - var recommendedValue = _hostingEnvironment.IISVersion >= new Version("7.5") + var recommendedValue = _iisVersion >= new Version("7.5") ? bool.TrueString.ToLower() : bool.FalseString.ToLower(); return new List { new AcceptableConfiguration { IsRecommended = true, Value = recommendedValue } }; @@ -45,7 +45,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config get { return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckSuccessMessage", - new[] { Values.First(v => v.IsRecommended).Value, _hostingEnvironment.IISVersion.ToString() }); + new[] { Values.First(v => v.IsRecommended).Value, _iisVersion.ToString() }); } } @@ -54,7 +54,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config get { return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckErrorMessage", - new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, _hostingEnvironment.IISVersion.ToString() }); + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, _iisVersion.ToString() }); } } @@ -63,7 +63,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config get { return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckRectifySuccessMessage", - new[] { Values.First(v => v.IsRecommended).Value, _hostingEnvironment.IISVersion.ToString() }); + new[] { Values.First(v => v.IsRecommended).Value, _iisVersion.ToString() }); } } } diff --git a/src/Umbraco.Core/Configuration/IActiveDirectorySettings.cs b/src/Umbraco.Core/Configuration/IActiveDirectorySettings.cs new file mode 100644 index 0000000000..e6b9202c06 --- /dev/null +++ b/src/Umbraco.Core/Configuration/IActiveDirectorySettings.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Configuration +{ + public interface IActiveDirectorySettings + { + string ActiveDirectoryDomain { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/IConfigManipulator.cs b/src/Umbraco.Core/Configuration/IConfigManipulator.cs new file mode 100644 index 0000000000..1d9230be44 --- /dev/null +++ b/src/Umbraco.Core/Configuration/IConfigManipulator.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.IO; + +namespace Umbraco.Core.Configuration +{ + public interface IConfigManipulator + { + void RemoveConnectionString(); + void SaveConnectionString(string connectionString, string providerName); + void SaveConfigValue(string itemPath, object value); + } +} diff --git a/src/Umbraco.Core/Configuration/IConfigsFactory.cs b/src/Umbraco.Core/Configuration/IConfigsFactory.cs index 98a24ca37f..dd2459b88c 100644 --- a/src/Umbraco.Core/Configuration/IConfigsFactory.cs +++ b/src/Umbraco.Core/Configuration/IConfigsFactory.cs @@ -1,9 +1,10 @@ using Umbraco.Core.IO; +using Umbraco.Core.Logging; namespace Umbraco.Core.Configuration { public interface IConfigsFactory { - Configs Create(IIOHelper ioHelper); + Configs Create(); } } diff --git a/src/Umbraco.Core/Configuration/IConnectionStrings.cs b/src/Umbraco.Core/Configuration/IConnectionStrings.cs index acd2281a1e..0d33378669 100644 --- a/src/Umbraco.Core/Configuration/IConnectionStrings.cs +++ b/src/Umbraco.Core/Configuration/IConnectionStrings.cs @@ -6,7 +6,5 @@ namespace Umbraco.Core.Configuration { get; } - - void RemoveConnectionString(string umbracoConnectionName); } } diff --git a/src/Umbraco.Core/Configuration/ICoreDebug.cs b/src/Umbraco.Core/Configuration/ICoreDebugSettings.cs similarity index 93% rename from src/Umbraco.Core/Configuration/ICoreDebug.cs rename to src/Umbraco.Core/Configuration/ICoreDebugSettings.cs index 4ff2a1a300..586e4bc3e4 100644 --- a/src/Umbraco.Core/Configuration/ICoreDebug.cs +++ b/src/Umbraco.Core/Configuration/ICoreDebugSettings.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.Configuration { - public interface ICoreDebug + public interface ICoreDebugSettings { /// /// When set to true, Scope logs the stack trace for any scope that gets disposed without being completed. diff --git a/src/Umbraco.Core/Configuration/IExceptionFilterSettings.cs b/src/Umbraco.Core/Configuration/IExceptionFilterSettings.cs new file mode 100644 index 0000000000..169c04da5f --- /dev/null +++ b/src/Umbraco.Core/Configuration/IExceptionFilterSettings.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Configuration +{ + public interface IExceptionFilterSettings + { + bool Disabled { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/IGlobalSettings.cs b/src/Umbraco.Core/Configuration/IGlobalSettings.cs index 1b1f328142..434aef6e68 100644 --- a/src/Umbraco.Core/Configuration/IGlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/IGlobalSettings.cs @@ -20,11 +20,6 @@ /// The reserved paths. string ReservedPaths { get; } - /// - /// Gets the path to umbraco's root directory (/umbraco by default). - /// - string Path { get; } - /// /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. /// @@ -61,6 +56,9 @@ /// The version check period in days (0 = never). int VersionCheckPeriod { get; } + /// + /// Gets the path to umbraco's root directory. + /// string UmbracoPath { get; } string UmbracoCssPath { get; } string UmbracoScriptsPath { get; } @@ -95,5 +93,11 @@ bool DisableElectionForSingleServer { get; } string RegisterType { get; } string DatabaseFactoryServerVersion { get; } + string MainDomLock { get; } + + /// + /// Gets the path to the razor file used when no published content is available. + /// + string NoNodesViewPath { get; } } } diff --git a/src/Umbraco.Core/Configuration/IHostingSettings.cs b/src/Umbraco.Core/Configuration/IHostingSettings.cs index ef399177a4..48ff4f216e 100644 --- a/src/Umbraco.Core/Configuration/IHostingSettings.cs +++ b/src/Umbraco.Core/Configuration/IHostingSettings.cs @@ -1,11 +1,24 @@ +using Umbraco.Core.Hosting; + namespace Umbraco.Core.Configuration { public interface IHostingSettings { bool DebugMode { get; } + /// /// Gets the configuration for the location of temporary files. /// LocalTempStorage LocalTempStorageLocation { get; } + + /// + /// Optional property to explicitly configure the application's virtual path + /// + /// + /// By default this is null which will mean that the is automatically configured, + /// otherwise this explicitly sets it. + /// If set, this value must begin with a "/" and cannot end with "/". + /// + string ApplicationVirtualPath { get; } } } diff --git a/src/Umbraco.Core/Configuration/IImagingSettings.cs b/src/Umbraco.Core/Configuration/IImagingSettings.cs new file mode 100644 index 0000000000..13e1b30389 --- /dev/null +++ b/src/Umbraco.Core/Configuration/IImagingSettings.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Configuration +{ + public interface IImagingSettings + { + int MaxBrowserCacheDays { get;} + int MaxCacheDays { get; } + uint CachedNameLength { get; } + int MaxResizeWidth { get; } + int MaxResizeHeight { get; } + string CacheFolder { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/IIndexCreatorSettings.cs b/src/Umbraco.Core/Configuration/IIndexCreatorSettings.cs new file mode 100644 index 0000000000..b3e2854a0d --- /dev/null +++ b/src/Umbraco.Core/Configuration/IIndexCreatorSettings.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Configuration +{ + public interface IIndexCreatorSettings + { + string LuceneDirectoryFactory { get; } + } +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs b/src/Umbraco.Core/Configuration/IModelsBuilderConfig.cs similarity index 80% rename from src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs rename to src/Umbraco.Core/Configuration/IModelsBuilderConfig.cs index 7e96aec60e..990bde9843 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs +++ b/src/Umbraco.Core/Configuration/IModelsBuilderConfig.cs @@ -1,4 +1,4 @@ -namespace Umbraco.ModelsBuilder.Embedded.Configuration +namespace Umbraco.Core.Configuration { public interface IModelsBuilderConfig { @@ -7,7 +7,6 @@ int DebugLevel { get; } bool EnableFactory { get; } bool FlagOutOfDateModels { get; } - bool IsDebug { get; } string ModelsDirectory { get; } ModelsMode ModelsMode { get; } string ModelsNamespace { get; } diff --git a/src/Umbraco.Core/Configuration/INuCacheSettings.cs b/src/Umbraco.Core/Configuration/INuCacheSettings.cs new file mode 100644 index 0000000000..c6524297a6 --- /dev/null +++ b/src/Umbraco.Core/Configuration/INuCacheSettings.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Configuration +{ + public interface INuCacheSettings + { + string BTreeBlockSize { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs index 98cd1010c0..6a5fd8e73f 100644 --- a/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs @@ -6,13 +6,11 @@ /// public interface IPasswordConfiguration { - int RequiredLength { get; } + int RequiredLength { get; } bool RequireNonLetterOrDigit { get; } bool RequireDigit { get; } bool RequireLowercase { get; } bool RequireUppercase { get; } - - bool UseLegacyEncoding { get; } string HashAlgorithmType { get; } // TODO: This doesn't really belong here diff --git a/src/Umbraco.Core/Configuration/IRuntimeSettings.cs b/src/Umbraco.Core/Configuration/IRuntimeSettings.cs new file mode 100644 index 0000000000..915e774186 --- /dev/null +++ b/src/Umbraco.Core/Configuration/IRuntimeSettings.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Configuration +{ + public interface IRuntimeSettings + { + int? MaxQueryStringLength { get; } + int? MaxRequestLength { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs b/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs new file mode 100644 index 0000000000..15e72a1f40 --- /dev/null +++ b/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Configuration +{ + public interface ITypeFinderSettings + { + string AssembliesAcceptingLoadExceptions { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs index 58c907c31f..8e7cd97f35 100644 --- a/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs @@ -7,9 +7,9 @@ namespace Umbraco.Core.Configuration /// public class MemberPasswordConfiguration : PasswordConfiguration, IMemberPasswordConfiguration { - public MemberPasswordConfiguration(IMemberPasswordConfigurationSection configSection) - : base(configSection) - { + public MemberPasswordConfiguration(IMemberPasswordConfiguration configSettings) + : base(configSettings) + { } } } diff --git a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs new file mode 100644 index 0000000000..3e3b116395 --- /dev/null +++ b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs @@ -0,0 +1,57 @@ +using System.Configuration; +using System.IO; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Configuration +{ + public static class ModelsBuilderConfigExtensions + { + private static string _modelsDirectoryAbsolute = null; + + public static string ModelsDirectoryAbsolute(this IModelsBuilderConfig modelsBuilderConfig, IIOHelper ioHelper) + { + + if (_modelsDirectoryAbsolute is null) + { + var modelsDirectory = modelsBuilderConfig.ModelsDirectory; + var root = ioHelper.MapPath("~/"); + + _modelsDirectoryAbsolute = GetModelsDirectory(root, modelsDirectory, + modelsBuilderConfig.AcceptUnsafeModelsDirectory); + } + + return _modelsDirectoryAbsolute; + } + + // internal for tests + internal static string GetModelsDirectory(string root, string config, bool acceptUnsafe) + { + // making sure it is safe, ie under the website root, + // unless AcceptUnsafeModelsDirectory and then everything is OK. + + if (!Path.IsPathRooted(root)) + throw new ConfigurationErrorsException($"Root is not rooted \"{root}\"."); + + if (config.StartsWith("~/")) + { + var dir = Path.Combine(root, config.TrimStart("~/")); + + // sanitize - GetFullPath will take care of any relative + // segments in path, eg '../../foo.tmp' - it may throw a SecurityException + // if the combined path reaches illegal parts of the filesystem + dir = Path.GetFullPath(dir); + root = Path.GetFullPath(root); + + if (!dir.StartsWith(root) && !acceptUnsafe) + throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); + + return dir; + } + + if (acceptUnsafe) + return Path.GetFullPath(config); + + throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); + } + } +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs b/src/Umbraco.Core/Configuration/ModelsMode.cs similarity index 92% rename from src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs rename to src/Umbraco.Core/Configuration/ModelsMode.cs index e0286fdab1..2483367394 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs +++ b/src/Umbraco.Core/Configuration/ModelsMode.cs @@ -1,4 +1,4 @@ -namespace Umbraco.ModelsBuilder.Embedded.Configuration +namespace Umbraco.Core.Configuration { /// /// Defines the models generation modes. @@ -8,7 +8,7 @@ /// /// Do not generate models. /// - Nothing = 0, // default value + Nothing = 0, // default value /// /// Generate models in memory. diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs b/src/Umbraco.Core/Configuration/ModelsModeExtensions.cs similarity index 94% rename from src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs rename to src/Umbraco.Core/Configuration/ModelsModeExtensions.cs index be638729ea..8d1b51cd28 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs +++ b/src/Umbraco.Core/Configuration/ModelsModeExtensions.cs @@ -1,4 +1,6 @@ -namespace Umbraco.ModelsBuilder.Embedded.Configuration +using Umbraco.Core.Configuration; + +namespace Umbraco.Configuration { /// /// Provides extensions for the enumeration. diff --git a/src/Umbraco.Core/Configuration/PasswordConfiguration.cs b/src/Umbraco.Core/Configuration/PasswordConfiguration.cs index 9edf1a462e..0c5ed9adb0 100644 --- a/src/Umbraco.Core/Configuration/PasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/PasswordConfiguration.cs @@ -5,21 +5,20 @@ namespace Umbraco.Core.Configuration { public abstract class PasswordConfiguration : IPasswordConfiguration { - protected PasswordConfiguration(IPasswordConfigurationSection configSection) + protected PasswordConfiguration(IPasswordConfiguration configSettings) { - if (configSection == null) + if (configSettings == null) { - throw new ArgumentNullException(nameof(configSection)); + throw new ArgumentNullException(nameof(configSettings)); } - RequiredLength = configSection.RequiredLength; - RequireNonLetterOrDigit = configSection.RequireNonLetterOrDigit; - RequireDigit = configSection.RequireDigit; - RequireLowercase = configSection.RequireLowercase; - RequireUppercase = configSection.RequireUppercase; - UseLegacyEncoding = configSection.UseLegacyEncoding; - HashAlgorithmType = configSection.HashAlgorithmType; - MaxFailedAccessAttemptsBeforeLockout = configSection.MaxFailedAccessAttemptsBeforeLockout; + RequiredLength = configSettings.RequiredLength; + RequireNonLetterOrDigit = configSettings.RequireNonLetterOrDigit; + RequireDigit = configSettings.RequireDigit; + RequireLowercase = configSettings.RequireLowercase; + RequireUppercase = configSettings.RequireUppercase; + HashAlgorithmType = configSettings.HashAlgorithmType; + MaxFailedAccessAttemptsBeforeLockout = configSettings.MaxFailedAccessAttemptsBeforeLockout; } public int RequiredLength { get; } @@ -32,8 +31,6 @@ namespace Umbraco.Core.Configuration public bool RequireUppercase { get; } - public bool UseLegacyEncoding { get; } - public string HashAlgorithmType { get; } public int MaxFailedAccessAttemptsBeforeLockout { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs index 82cc5928cf..d100eb0a74 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings /// The file extension. /// /// A value indicating whether the file extension corresponds to an image. - public static bool IsImageFile(this IContentSection contentConfig, string extension) + public static bool IsImageFile(this IContentSettings contentConfig, string extension) { if (contentConfig == null) throw new ArgumentNullException(nameof(contentConfig)); if (extension == null) return false; @@ -24,22 +24,22 @@ namespace Umbraco.Core.Configuration.UmbracoSettings /// held in settings. /// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted. /// - public static bool IsFileAllowedForUpload(this IContentSection contentSection, string extension) + public static bool IsFileAllowedForUpload(this IContentSettings contentSettings, string extension) { - return contentSection.AllowedUploadFiles.Any(x => x.InvariantEquals(extension)) || - (contentSection.AllowedUploadFiles.Any() == false && - contentSection.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false); + return contentSettings.AllowedUploadFiles.Any(x => x.InvariantEquals(extension)) || + (contentSettings.AllowedUploadFiles.Any() == false && + contentSettings.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false); } /// /// Gets the auto-fill configuration for a specified property alias. /// - /// + /// /// The property type alias. /// The auto-fill configuration for the specified property alias, or null. - public static IImagingAutoFillUploadField GetConfig(this IContentSection contentSection, string propertyTypeAlias) + public static IImagingAutoFillUploadField GetConfig(this IContentSettings contentSettings, string propertyTypeAlias) { - var autoFillConfigs = contentSection.ImageAutoFillProperties; + var autoFillConfigs = contentSettings.ImageAutoFillProperties; return autoFillConfigs?.FirstOrDefault(x => x.Alias == propertyTypeAlias); } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IBackOfficeSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IBackOfficeSection.cs index 36dd6a22ed..85df4540c0 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IBackOfficeSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IBackOfficeSection.cs @@ -2,6 +2,6 @@ { public interface IBackOfficeSection { - ITourSection Tours { get; } + ITourSettings Tours { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSettings.cs similarity index 93% rename from src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs rename to src/Umbraco.Core/Configuration/UmbracoSettings/IContentSettings.cs index 228b0923dc..08f6231309 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSettings.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Macros; namespace Umbraco.Core.Configuration.UmbracoSettings { - public interface IContentSection : IUmbracoConfigurationSection + public interface IContentSettings : IUmbracoConfigurationSection { string NotificationEmailAddress { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IKeepAliveSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IKeepAliveSettings.cs similarity index 68% rename from src/Umbraco.Core/Configuration/UmbracoSettings/IKeepAliveSection.cs rename to src/Umbraco.Core/Configuration/UmbracoSettings/IKeepAliveSettings.cs index 3a0ad258c5..58a8151474 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IKeepAliveSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IKeepAliveSettings.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - public interface IKeepAliveSection : IUmbracoConfigurationSection + public interface IKeepAliveSettings : IUmbracoConfigurationSection { bool DisableKeepAliveTask { get; } string KeepAlivePingUrl { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSettings.cs similarity index 66% rename from src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs rename to src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSettings.cs index 6c1be8ade5..ee5647ee27 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSettings.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - public interface ILoggingSection : IUmbracoConfigurationSection + public interface ILoggingSettings : IUmbracoConfigurationSection { int MaxLogAge { get; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IMemberPasswordConfigurationSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IMemberPasswordConfigurationSection.cs deleted file mode 100644 index cbbb933857..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IMemberPasswordConfigurationSection.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IMemberPasswordConfigurationSection : IPasswordConfigurationSection - { - } -} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSettings.cs similarity index 78% rename from src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs rename to src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSettings.cs index 2393c5af63..11fdaa8310 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSettings.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - public interface IRequestHandlerSection : IUmbracoConfigurationSection + public interface IRequestHandlerSettings : IUmbracoConfigurationSection { bool AddTrailingSlash { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySettings.cs similarity index 78% rename from src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs rename to src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySettings.cs index a6ed188713..6ab520fefd 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySettings.cs @@ -1,9 +1,9 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - public interface ISecuritySection : IUmbracoConfigurationSection + public interface ISecuritySettings : IUmbracoConfigurationSection { bool KeepUserLoggedIn { get; } - + bool HideDisabledUsersInBackoffice { get; } /// @@ -23,9 +23,5 @@ /// When this is false, the username and email fields will be shown in the user section. /// bool UsernameIsEmail { get; } - - IUserPasswordConfigurationSection UserPasswordConfiguration { get; } - - IMemberPasswordConfigurationSection MemberPasswordConfiguration { get; } } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ITourSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ITourSettings.cs similarity index 75% rename from src/Umbraco.Core/Configuration/UmbracoSettings/ITourSection.cs rename to src/Umbraco.Core/Configuration/UmbracoSettings/ITourSettings.cs index 938642521e..d3d8293140 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ITourSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ITourSettings.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - public interface ITourSection + public interface ITourSettings { bool EnableTours { get; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs index fd5b18ed39..a290c26d15 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; namespace Umbraco.Core.Configuration.UmbracoSettings { diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs deleted file mode 100644 index acd9c92588..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IUmbracoSettingsSection : IUmbracoConfigurationSection - { - IBackOfficeSection BackOffice { get; } - - IContentSection Content { get; } - - ISecuritySection Security { get; } - - IRequestHandlerSection RequestHandler { get; } - - ILoggingSection Logging { get; } - - IWebRoutingSection WebRouting { get; } - - IKeepAliveSection KeepAlive { get; } - } -} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IUserPasswordConfigurationSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IUserPasswordConfigurationSection.cs deleted file mode 100644 index d80dd2b7e5..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IUserPasswordConfigurationSection.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IUserPasswordConfigurationSection : IPasswordConfigurationSection - { - } -} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSettings.cs similarity index 86% rename from src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs rename to src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSettings.cs index f0a986efe2..f7f6a94d30 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSettings.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { - public interface IWebRoutingSection : IUmbracoConfigurationSection + public interface IWebRoutingSettings : IUmbracoConfigurationSection { bool TrySkipIisCustomErrors { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index dd96e6edd7..35154a9f50 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Configuration private readonly IGlobalSettings _globalSettings; public UmbracoVersion(IGlobalSettings globalSettings) - :this() + : this() { _globalSettings = globalSettings; } @@ -55,7 +55,7 @@ namespace Umbraco.Core.Configuration /// Is the one that the CLR checks for compatibility. Therefore, it changes only on /// hard-breaking changes (for instance, on new major versions). /// - public Version AssemblyVersion {get; } + public Version AssemblyVersion { get; } /// /// Gets the assembly file version of the Umbraco code. @@ -83,11 +83,13 @@ namespace Umbraco.Core.Configuration /// and changes during an upgrade. The executing code version changes when new code is /// deployed. The site/files version changes during an upgrade. /// - public SemVersion LocalVersion { + public SemVersion LocalVersion + { get { var value = _globalSettings.ConfigurationStatus; return value.IsNullOrWhiteSpace() ? null : SemVersion.TryParse(value, out var semver) ? semver : null; - } } + } + } } } diff --git a/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs index 4cfee5280b..07e6603cee 100644 --- a/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs @@ -7,9 +7,9 @@ namespace Umbraco.Core.Configuration /// public class UserPasswordConfiguration : PasswordConfiguration, IUserPasswordConfiguration { - public UserPasswordConfiguration(IUserPasswordConfigurationSection configSection) - : base(configSection) - { + public UserPasswordConfiguration(IUserPasswordConfiguration configSettings) + : base(configSettings) + { } } } diff --git a/src/Umbraco.Core/Configuration/XmlConfigManipulator.cs b/src/Umbraco.Core/Configuration/XmlConfigManipulator.cs new file mode 100644 index 0000000000..80d795bb48 --- /dev/null +++ b/src/Umbraco.Core/Configuration/XmlConfigManipulator.cs @@ -0,0 +1,123 @@ +using System; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Configuration +{ + public class XmlConfigManipulator : IConfigManipulator + { + private readonly IIOHelper _ioHelper; + private readonly ILogger _logger; + + public XmlConfigManipulator(IIOHelper ioHelper, ILogger logger) + { + _ioHelper = ioHelper; + _logger = logger; + } + + public void RemoveConnectionString() + { + var key = Constants.System.UmbracoConnectionName; + var fileName = _ioHelper.MapPath("~/web.config"); + var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); + + var appSettings = xml.Root.DescendantsAndSelf("appSettings").Single(); + var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key); + + if (setting != null) + { + setting.Remove(); + xml.Save(fileName, SaveOptions.DisableFormatting); + ConfigurationManager.RefreshSection("appSettings"); + } + + var settings = ConfigurationManager.ConnectionStrings[key]; + } + + /// + /// Saves the connection string as a proper .net connection string in web.config. + /// + /// Saves the ConnectionString in the very nasty 'medium trust'-supportive way. + /// The connection string. + /// The provider name. + public void SaveConnectionString(string connectionString, string providerName) + { + if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(connectionString)); + if (providerName == null) throw new ArgumentNullException(nameof(providerName)); + if (string.IsNullOrWhiteSpace(providerName)) + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(providerName)); + + + var fileSource = "web.config"; + var fileName = _ioHelper.MapPath("~/" + fileSource); + + var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); + if (xml.Root == null) throw new Exception($"Invalid {fileSource} file (no root)."); + + var connectionStrings = xml.Root.DescendantsAndSelf("connectionStrings").FirstOrDefault(); + if (connectionStrings == null) throw new Exception($"Invalid {fileSource} file (no connection strings)."); + + // handle configSource + var configSourceAttribute = connectionStrings.Attribute("configSource"); + if (configSourceAttribute != null) + { + fileSource = configSourceAttribute.Value; + fileName = _ioHelper.MapPath("~/" + fileSource); + + if (!File.Exists(fileName)) + throw new Exception($"Invalid configSource \"{fileSource}\" (no such file)."); + + xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); + if (xml.Root == null) throw new Exception($"Invalid {fileSource} file (no root)."); + + connectionStrings = xml.Root.DescendantsAndSelf("connectionStrings").FirstOrDefault(); + if (connectionStrings == null) + throw new Exception($"Invalid {fileSource} file (no connection strings)."); + } + + // create or update connection string + var setting = connectionStrings.Descendants("add").FirstOrDefault(s => + s.Attribute("name")?.Value == Constants.System.UmbracoConnectionName); + if (setting == null) + { + connectionStrings.Add(new XElement("add", + new XAttribute("name", Constants.System.UmbracoConnectionName), + new XAttribute("connectionString", connectionString), + new XAttribute("providerName", providerName))); + } + else + { + AddOrUpdateAttribute(setting, "connectionString", connectionString); + AddOrUpdateAttribute(setting, "providerName", providerName); + } + + // save + _logger.Info("Saving connection string to {ConfigFile}.", fileSource); + xml.Save(fileName, SaveOptions.DisableFormatting); + _logger.Info("Saved connection string to {ConfigFile}.", fileSource); + } + + public void SaveConfigValue(string itemPath, object value) + { + throw new NotImplementedException(); + } + + private static void AddOrUpdateAttribute(XElement element, string name, string value) + { + var attribute = element.Attribute(name); + if (attribute == null) + element.Add(new XAttribute(name, value)); + else + attribute.Value = value; + } + } +} diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index 4c47f12ba0..3551aa1c31 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -41,32 +41,27 @@ namespace Umbraco.Core /// /// Gets the path to umbraco's root directory (/umbraco by default). /// - public const string Path = "Umbraco.Core.Path"; + public const string UmbracoPath = "Umbraco.Core.Path"; /// /// The reserved urls from web.config. /// public const string ReservedUrls = "Umbraco.Core.ReservedUrls"; - - /// - /// The path of backoffice. - /// - public const string UmbracoPath = "umbracoPath"; - + /// /// The path of the stylesheet folder. /// - public const string UmbracoCssPath = "umbracoCssPath"; + public const string UmbracoCssPath = "Umbraco.Web.CssPath"; /// /// The path of script folder. /// - public const string UmbracoScriptsPath = "umbracoScriptsPath"; + public const string UmbracoScriptsPath = "Umbraco.Core.ScriptsPath"; /// /// The path of media folder. /// - public const string UmbracoMediaPath = "umbracoMediaPath"; + public const string UmbracoMediaPath = "Umbraco.Core.MediaPath"; /// /// The reserved paths from web.config @@ -115,6 +110,11 @@ namespace Umbraco.Core /// public const string DisableElectionForSingleServer = "Umbraco.Core.DisableElectionForSingleServer"; + /// + /// Gets the path to the razor file used when no published content is available. + /// + public const string NoNodesViewPath = "Umbraco.Core.NoNodesViewPath"; + /// /// Debug specific web.config AppSetting keys for Umbraco /// diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs new file mode 100644 index 0000000000..86a02affb6 --- /dev/null +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -0,0 +1,21 @@ +namespace Umbraco.Core +{ + public static partial class Constants + { + public static class Configuration + { + /// + /// Case insensitive prefix for all configurations + /// + /// + /// ":" is used as marker for nested objects in json. E.g. "Umbraco:CMS:" = {"Umbraco":{"CMS":{....}} + /// + 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-ModelsBuilder.cs b/src/Umbraco.Core/Constants-ModelsBuilder.cs new file mode 100644 index 0000000000..28e70ed383 --- /dev/null +++ b/src/Umbraco.Core/Constants-ModelsBuilder.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Core +{ + /// + /// Defines constants. + /// + public static partial class Constants + { + /// + /// Defines constants for ModelsBuilder. + /// + public static class ModelsBuilder + { + + public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; + } + } +} 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/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index abb92298f4..837db01b63 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core + namespace Umbraco.Core { public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 4574934939..26e66c8cfc 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -35,6 +35,11 @@ /// public const string AngularHeadername = "X-UMB-XSRF-TOKEN"; + /// + /// The route name of the page shown when Umbraco has no published content. + /// + public const string NoContentRouteName = "umbraco-no-content"; + /// /// The claim type for the ASP.NET Identity security stamp /// diff --git a/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs index add7e2f16a..467bc6c29f 100644 --- a/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { - internal class ContentEditorContentAppFactory : IContentAppFactory + public class ContentEditorContentAppFactory : IContentAppFactory { // see note on ContentApp internal const int Weight = -100; diff --git a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs index 3e0dea0f5e..d66de3b238 100644 --- a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs @@ -10,7 +10,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.ContentApps { - internal class ListViewContentAppFactory : IContentAppFactory + public class ListViewContentAppFactory : IContentAppFactory { // see note on ContentApp private const int Weight = -666; diff --git a/src/Umbraco.Web.BackOffice/Dashboards/ContentDashboard.cs b/src/Umbraco.Core/Dashboards/ContentDashboard.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/ContentDashboard.cs rename to src/Umbraco.Core/Dashboards/ContentDashboard.cs diff --git a/src/Umbraco.Web.BackOffice/Dashboards/ExamineDashboard.cs b/src/Umbraco.Core/Dashboards/ExamineDashboard.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/ExamineDashboard.cs rename to src/Umbraco.Core/Dashboards/ExamineDashboard.cs diff --git a/src/Umbraco.Web.BackOffice/Dashboards/FormsDashboard.cs b/src/Umbraco.Core/Dashboards/FormsDashboard.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/FormsDashboard.cs rename to src/Umbraco.Core/Dashboards/FormsDashboard.cs diff --git a/src/Umbraco.Web.BackOffice/Dashboards/HealthCheckDashboard.cs b/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/HealthCheckDashboard.cs rename to src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs diff --git a/src/Umbraco.Web.BackOffice/Dashboards/MediaDashboard.cs b/src/Umbraco.Core/Dashboards/MediaDashboard.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/MediaDashboard.cs rename to src/Umbraco.Core/Dashboards/MediaDashboard.cs diff --git a/src/Umbraco.Web.BackOffice/Dashboards/MembersDashboard.cs b/src/Umbraco.Core/Dashboards/MembersDashboard.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/MembersDashboard.cs rename to src/Umbraco.Core/Dashboards/MembersDashboard.cs diff --git a/src/Umbraco.Web.BackOffice/Dashboards/ProfilerDashboard.cs b/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/ProfilerDashboard.cs rename to src/Umbraco.Core/Dashboards/ProfilerDashboard.cs diff --git a/src/Umbraco.Web.BackOffice/Dashboards/PublishedStatusDashboard.cs b/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/PublishedStatusDashboard.cs rename to src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs diff --git a/src/Umbraco.Web.BackOffice/Dashboards/RedirectUrlDashboard.cs b/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/RedirectUrlDashboard.cs rename to src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs diff --git a/src/Umbraco.Web.BackOffice/Dashboards/SettingsDashboards.cs b/src/Umbraco.Core/Dashboards/SettingsDashboards.cs similarity index 100% rename from src/Umbraco.Web.BackOffice/Dashboards/SettingsDashboards.cs rename to src/Umbraco.Core/Dashboards/SettingsDashboards.cs diff --git a/src/Umbraco.Web/DefaultEventMessagesFactory.cs b/src/Umbraco.Core/DefaultEventMessagesFactory.cs similarity index 92% rename from src/Umbraco.Web/DefaultEventMessagesFactory.cs rename to src/Umbraco.Core/DefaultEventMessagesFactory.cs index 39be829a7d..0d53645b5e 100644 --- a/src/Umbraco.Web/DefaultEventMessagesFactory.cs +++ b/src/Umbraco.Core/DefaultEventMessagesFactory.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Events; namespace Umbraco.Web { - internal class DefaultEventMessagesFactory : IEventMessagesFactory + public class DefaultEventMessagesFactory : IEventMessagesFactory { private readonly IEventMessagesAccessor _eventMessagesAccessor; diff --git a/src/Umbraco.Core/HashGenerator.cs b/src/Umbraco.Core/HashGenerator.cs index 62be0dacdd..c7fad08089 100644 --- a/src/Umbraco.Core/HashGenerator.cs +++ b/src/Umbraco.Core/HashGenerator.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core public void AddDateTime(DateTime d) { - _writer.Write(d.Ticks);; + _writer.Write(d.Ticks); } public void AddString(string s) diff --git a/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs b/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs new file mode 100644 index 0000000000..93441f1a47 --- /dev/null +++ b/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Hosting +{ + public interface IApplicationShutdownRegistry + { + void RegisterObject(IRegisteredObject registeredObject); + void UnregisterObject(IRegisteredObject registeredObject); + } +} diff --git a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs index 1662879cf2..0bdfe5c425 100644 --- a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs +++ b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs @@ -9,25 +9,38 @@ namespace Umbraco.Core.Hosting string ApplicationPhysicalPath { get; } string LocalTempPath { get; } + + /// + /// The web application's hosted path + /// + /// + /// In most cases this will return "/" but if the site is hosted in a virtual directory then this will return the virtual directory's path such as "/mysite". + /// This value must begin with a "/" and cannot end with "/". + /// string ApplicationVirtualPath { get; } - int CurrentDomainId { get; } - bool IsDebugMode { get; } + /// /// Gets a value indicating whether Umbraco is hosted. /// bool IsHosted { get; } + Version IISVersion { get; } string MapPath(string path); - string ToAbsolute(string virtualPath, string root); /// - /// Terminates the current application. The application restarts the next time a request is received for it. + /// Maps a virtual path to the application's web root /// - void LazyRestartApplication(); - - void RegisterObject(IRegisteredObject registeredObject); - void UnregisterObject(IRegisteredObject registeredObject); + /// The virtual path. Must start with either ~/ or / else an exception is thrown. + /// + /// + /// This maps the virtual path syntax to the web root. For example when hosting in a virtual directory called "site" and the value "~/pages/test" is passed in, it will + /// map to "/site/pages/test" where "/site" is the value of . + /// + /// + /// If virtualPath does not start with ~/ or / + /// + string ToAbsolute(string virtualPath); } } diff --git a/src/Umbraco.Core/HybridEventMessagesAccessor.cs b/src/Umbraco.Core/HybridEventMessagesAccessor.cs index b2700eb137..82e784e093 100644 --- a/src/Umbraco.Core/HybridEventMessagesAccessor.cs +++ b/src/Umbraco.Core/HybridEventMessagesAccessor.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Events; namespace Umbraco.Web { - internal class HybridEventMessagesAccessor : HybridAccessorBase, IEventMessagesAccessor + public class HybridEventMessagesAccessor : HybridAccessorBase, IEventMessagesAccessor { protected override string ItemKey => "Umbraco.Core.Events.HybridEventMessagesAccessor"; diff --git a/src/Umbraco.Web/HybridUmbracoContextAccessor.cs b/src/Umbraco.Core/HybridUmbracoContextAccessor.cs similarity index 87% rename from src/Umbraco.Web/HybridUmbracoContextAccessor.cs rename to src/Umbraco.Core/HybridUmbracoContextAccessor.cs index bb8e2d6993..1ad4777460 100644 --- a/src/Umbraco.Web/HybridUmbracoContextAccessor.cs +++ b/src/Umbraco.Core/HybridUmbracoContextAccessor.cs @@ -5,7 +5,7 @@ namespace Umbraco.Web /// /// Implements a hybrid . /// - internal class HybridUmbracoContextAccessor : HybridAccessorBase, IUmbracoContextAccessor + public class HybridUmbracoContextAccessor : HybridAccessorBase, IUmbracoContextAccessor { /// /// Initializes a new instance of the class. diff --git a/src/Umbraco.Core/IO/IIOHelper.cs b/src/Umbraco.Core/IO/IIOHelper.cs index 11f5c6c565..42ca804f44 100644 --- a/src/Umbraco.Core/IO/IIOHelper.cs +++ b/src/Umbraco.Core/IO/IIOHelper.cs @@ -4,15 +4,19 @@ namespace Umbraco.Core.IO { public interface IIOHelper { - bool ForceNotHosted { get; set; } - - char DirSepChar { get; } string FindFile(string virtualPath); - string ResolveVirtualUrl(string path); - string ResolveUrl(string virtualPath); - Attempt TryResolveUrl(string virtualPath); - string MapPath(string path); + // TODO: This is the same as IHostingEnvironment.ToAbsolute + string ResolveUrl(string virtualPath); + + Attempt TryResolveUrl(string virtualPath); + + /// + /// Maps a virtual path to a physical path in the content root folder (i.e. www) + /// + /// + /// + string MapPath(string path); /// /// Verifies that the current filepath matches a directory where the user is allowed to edit a file. @@ -40,24 +44,6 @@ namespace Umbraco.Core.IO bool PathStartsWith(string path, string root, char separator); - /// - /// Returns the path to the root of the application, by getting the path to where the assembly where this - /// method is included is present, then traversing until it's past the /bin directory. Ie. this makes it work - /// even if the assembly is in a /bin/debug or /bin/release folder - /// - /// - string GetRootDirectorySafe(); - - string GetRootDirectoryBinFolder(); - - /// - /// Allows you to overwrite RootDirectory, which would otherwise be resolved - /// automatically upon application start. - /// - /// The supplied path should be the absolute path to the root of the umbraco site. - /// - void SetRootDirectory(string rootPath); - void EnsurePathExists(string path); /// @@ -67,14 +53,5 @@ namespace Umbraco.Core.IO /// string GetRelativePath(string path); - /// - /// Gets the root path of the application - /// - string Root - { - get; - set; //Only required for unit tests - } - } } diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index d5e1335f35..a71a307e01 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Reflection; using System.IO; using System.Linq; +using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; using Umbraco.Core.Strings; @@ -13,54 +14,34 @@ namespace Umbraco.Core.IO { private readonly IHostingEnvironment _hostingEnvironment; - public IOHelper(IHostingEnvironment hostingEnvironment) + public IOHelper(IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings) { - _hostingEnvironment = hostingEnvironment; + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); } - /// - /// Gets or sets a value forcing Umbraco to consider it is non-hosted. - /// - /// This should always be false, unless unit testing. - public bool ForceNotHosted { get; set; } - - private static string _rootDir = ""; - // static compiled regex for faster performance //private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - public char DirSepChar => Path.DirectorySeparatorChar; - //helper to try and match the old path to a new virtual one public string FindFile(string virtualPath) { string retval = virtualPath; if (virtualPath.StartsWith("~")) - retval = virtualPath.Replace("~", Root); + retval = virtualPath.Replace("~", _hostingEnvironment.ApplicationVirtualPath); - if (virtualPath.StartsWith("/") && virtualPath.StartsWith(Root) == false) - retval = Root + "/" + virtualPath.TrimStart('/'); + if (virtualPath.StartsWith("/") && virtualPath.StartsWith(_hostingEnvironment.ApplicationVirtualPath) == false) + retval = _hostingEnvironment.ApplicationVirtualPath + "/" + virtualPath.TrimStart('/'); return retval; } - public string ResolveVirtualUrl(string path) - { - if (string.IsNullOrWhiteSpace(path)) return path; - return path.StartsWith("~/") ? ResolveUrl(path) : path; - } - - //Replaces tildes with the root dir + // TODO: This is the same as IHostingEnvironment.ToAbsolute public string ResolveUrl(string virtualPath) { - if (virtualPath.StartsWith("~")) - return virtualPath.Replace("~", Root).Replace("//", "/"); - else if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) - return virtualPath; - else - return _hostingEnvironment.ToAbsolute(virtualPath, Root); + if (string.IsNullOrWhiteSpace(virtualPath)) return virtualPath; + return _hostingEnvironment.ToAbsolute(virtualPath); + } public Attempt TryResolveUrl(string virtualPath) @@ -68,10 +49,11 @@ namespace Umbraco.Core.IO try { if (virtualPath.StartsWith("~")) - return Attempt.Succeed(virtualPath.Replace("~", Root).Replace("//", "/")); + return Attempt.Succeed(virtualPath.Replace("~", _hostingEnvironment.ApplicationVirtualPath).Replace("//", "/")); if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) return Attempt.Succeed(virtualPath); - return Attempt.Succeed(_hostingEnvironment.ToAbsolute(virtualPath, Root)); + + return Attempt.Succeed(_hostingEnvironment.ToAbsolute(virtualPath)); } catch (Exception ex) { @@ -95,20 +77,17 @@ namespace Umbraco.Core.IO if (_hostingEnvironment.IsHosted) { - var result = (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(Root))) - ? _hostingEnvironment.MapPath(path) + var result = (!string.IsNullOrEmpty(path) && (path.StartsWith("~") || path.StartsWith(_hostingEnvironment.ApplicationVirtualPath))) + ? _hostingEnvironment.MapPath(path) : _hostingEnvironment.MapPath("~/" + path.TrimStart('/')); if (result != null) return result; - - } - - - var root = GetRootDirectorySafe(); - var newPath = path.TrimStart('~', '/').Replace('/', DirSepChar); - var retval = root + DirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; + var dirSepChar = Path.DirectorySeparatorChar; + var root = Assembly.GetExecutingAssembly().GetRootDirectorySafe(); + var newPath = path.TrimStart('~', '/').Replace('/', dirSepChar); + var retval = root + dirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; return retval; } @@ -142,7 +121,7 @@ namespace Umbraco.Core.IO // TODO: what's below is dirty, there are too many ways to get the root dir, etc. // not going to fix everything today - var mappedRoot = MapPath(Root); + var mappedRoot = MapPath(_hostingEnvironment.ApplicationVirtualPath); if (filePath.StartsWith(mappedRoot) == false) filePath = MapPath(filePath); @@ -186,71 +165,6 @@ namespace Umbraco.Core.IO return path[root.Length] == separator; } - /// - /// Returns the path to the root of the application, by getting the path to where the assembly where this - /// method is included is present, then traversing until it's past the /bin directory. Ie. this makes it work - /// even if the assembly is in a /bin/debug or /bin/release folder - /// - /// - public string GetRootDirectorySafe() - { - if (String.IsNullOrEmpty(_rootDir) == false) - { - return _rootDir; - } - - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - var baseDirectory = Path.GetDirectoryName(path); - if (String.IsNullOrEmpty(baseDirectory)) - throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured."); - - _rootDir = baseDirectory.Contains("bin") - ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) - : baseDirectory; - - return _rootDir; - } - - public string GetRootDirectoryBinFolder() - { - string binFolder = String.Empty; - if (String.IsNullOrEmpty(_rootDir)) - { - binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory.FullName; - return binFolder; - } - - binFolder = Path.Combine(GetRootDirectorySafe(), "bin"); - - // do this all the time (no #if DEBUG) because Umbraco release - // can be used in tests by an app (eg Deploy) being debugged - var debugFolder = Path.Combine(binFolder, "debug"); - if (Directory.Exists(debugFolder)) - return debugFolder; - - var releaseFolder = Path.Combine(binFolder, "release"); - if (Directory.Exists(releaseFolder)) - return releaseFolder; - - if (Directory.Exists(binFolder)) - return binFolder; - - return _rootDir; - } - - /// - /// Allows you to overwrite RootDirectory, which would otherwise be resolved - /// automatically upon application start. - /// - /// The supplied path should be the absolute path to the root of the umbraco site. - /// - public void SetRootDirectory(string rootPath) - { - _rootDir = rootPath; - } - public void EnsurePathExists(string path) { var absolutePath = MapPath(path); @@ -267,7 +181,7 @@ namespace Umbraco.Core.IO { if (path.IsFullPath()) { - var rootDirectory = GetRootDirectorySafe(); + var rootDirectory = MapPath("~"); var relativePath = path.ToLowerInvariant().Replace(rootDirectory.ToLowerInvariant(), string.Empty); path = relativePath; } @@ -275,27 +189,5 @@ namespace Umbraco.Core.IO return PathUtility.EnsurePathIsApplicationRootPrefixed(path); } - private string _root; - - /// - /// Gets the root path of the application - /// - public string Root - { - get - { - if (_root != null) return _root; - - var appPath = _hostingEnvironment.ApplicationVirtualPath; - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (appPath == null || appPath == "/") appPath = string.Empty; - - _root = appPath; - - return _root; - } - //Only required for unit tests - set => _root = value; - } } } diff --git a/src/Umbraco.Core/IO/IOHelperExtensions.cs b/src/Umbraco.Core/IO/IOHelperExtensions.cs index 64b57e7dc1..6912974196 100644 --- a/src/Umbraco.Core/IO/IOHelperExtensions.cs +++ b/src/Umbraco.Core/IO/IOHelperExtensions.cs @@ -5,6 +5,19 @@ namespace Umbraco.Core.IO { public static class IOHelperExtensions { + /// + /// Will resolve a virtual path URL to an absolute path, else if it is not a virtual path (i.e. starts with ~/) then + /// it will just return the path as-is (relative). + /// + /// + /// + /// + public static string ResolveRelativeOrVirtualUrl(this IIOHelper ioHelper, string path) + { + if (string.IsNullOrWhiteSpace(path)) return path; + return path.StartsWith("~/") ? ioHelper.ResolveUrl(path) : path; + } + /// /// Tries to create a directory. /// @@ -35,5 +48,7 @@ namespace Umbraco.Core.IO { return "umbraco-test." + Guid.NewGuid().ToString("N").Substring(0, 8); } + + } } diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 24284fec98..403557958a 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -59,7 +59,8 @@ namespace Umbraco.Core.IO if (Path.IsPathRooted(rootPath) == false) { // but the test suite App.config cannot really "root" anything so we have to do it here - var localRoot = _ioHelper.GetRootDirectorySafe(); + // TODO: This will map to the web content root (www) not the web app root, is that what we want? Else we need to use IHostingEnvironment.ApplicationPhysicalPath + var localRoot = _ioHelper.MapPath("~"); rootPath = Path.Combine(localRoot, rootPath); } diff --git a/src/Umbraco.Tests/TestHelpers/ConsoleLogger.cs b/src/Umbraco.Core/Logging/ConsoleLogger.cs similarity index 98% rename from src/Umbraco.Tests/TestHelpers/ConsoleLogger.cs rename to src/Umbraco.Core/Logging/ConsoleLogger.cs index 53d6078e4b..275b8d988b 100644 --- a/src/Umbraco.Tests/TestHelpers/ConsoleLogger.cs +++ b/src/Umbraco.Core/Logging/ConsoleLogger.cs @@ -1,7 +1,6 @@ using System; -using Umbraco.Core.Logging; -namespace Umbraco.Tests.TestHelpers +namespace Umbraco.Core.Logging { public class ConsoleLogger : ILogger { diff --git a/src/Umbraco.Core/Logging/ILogger.cs b/src/Umbraco.Core/Logging/ILogger.cs index 4f49d0b3b4..fe7d798ebf 100644 --- a/src/Umbraco.Core/Logging/ILogger.cs +++ b/src/Umbraco.Core/Logging/ILogger.cs @@ -2,6 +2,7 @@ namespace Umbraco.Core.Logging { + /// /// Defines the logging service. /// diff --git a/src/Umbraco.Web/Macros/IMacroRenderer.cs b/src/Umbraco.Core/Macros/IMacroRenderer.cs similarity index 100% rename from src/Umbraco.Web/Macros/IMacroRenderer.cs rename to src/Umbraco.Core/Macros/IMacroRenderer.cs diff --git a/src/Umbraco.Core/Macros/MacroContent.cs b/src/Umbraco.Core/Macros/MacroContent.cs new file mode 100644 index 0000000000..60dfb9210d --- /dev/null +++ b/src/Umbraco.Core/Macros/MacroContent.cs @@ -0,0 +1,20 @@ +using System; + +namespace Umbraco.Web.Macros +{ + // represents the content of a macro + public class MacroContent + { + // gets or sets the text content + public string Text { get; set; } + + // gets or sets the date the content was generated + public DateTime Date { get; set; } = DateTime.Now; + + // a value indicating whether the content is empty + public bool IsEmpty => Text is null; + + // gets an empty macro content + public static MacroContent Empty { get; } = new MacroContent(); + } +} diff --git a/src/Umbraco.Web/Macros/MacroModel.cs b/src/Umbraco.Core/Macros/MacroModel.cs similarity index 91% rename from src/Umbraco.Web/Macros/MacroModel.cs rename to src/Umbraco.Core/Macros/MacroModel.cs index dd9a73b147..6b5013115a 100644 --- a/src/Umbraco.Web/Macros/MacroModel.cs +++ b/src/Umbraco.Core/Macros/MacroModel.cs @@ -20,8 +20,6 @@ namespace Umbraco.Web.Macros /// public string Alias { get; set; } - public MacroTypes MacroType { get; set; } - public string MacroSource { get; set; } public int CacheDuration { get; set; } @@ -46,7 +44,6 @@ namespace Umbraco.Web.Macros Id = macro.Id; Name = macro.Name; Alias = macro.Alias; - MacroType = macro.MacroType; MacroSource = macro.MacroSource; CacheDuration = macro.CacheDuration; CacheByPage = macro.CacheByPage; @@ -55,8 +52,6 @@ namespace Umbraco.Web.Macros foreach (var prop in macro.Properties) Properties.Add(new MacroPropertyModel(prop.Alias, string.Empty, prop.EditorAlias)); - - MacroType = macro.MacroType; } } } diff --git a/src/Umbraco.Web/Macros/MacroPropertyModel.cs b/src/Umbraco.Core/Macros/MacroPropertyModel.cs similarity index 100% rename from src/Umbraco.Web/Macros/MacroPropertyModel.cs rename to src/Umbraco.Core/Macros/MacroPropertyModel.cs diff --git a/src/Umbraco.Core/Manifest/IManifestParser.cs b/src/Umbraco.Core/Manifest/IManifestParser.cs index eeb0c756f6..3eec7007b3 100644 --- a/src/Umbraco.Core/Manifest/IManifestParser.cs +++ b/src/Umbraco.Core/Manifest/IManifestParser.cs @@ -17,7 +17,5 @@ namespace Umbraco.Core.Manifest /// Parses a manifest. /// PackageManifest ParseManifest(string text); - - IEnumerable ParseGridEditors(string text); } } diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs index 903efe2897..298047754c 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs @@ -136,14 +136,14 @@ namespace Umbraco.Core.Manifest // else // content app can be displayed - return _app ?? (_app = new ContentApp + return _app ??= new ContentApp { Alias = _definition.Alias, Name = _definition.Name, Icon = _definition.Icon, - View = _ioHelper.ResolveVirtualUrl(_definition.View), + View = _ioHelper.ResolveRelativeOrVirtualUrl(_definition.View), Weight = _definition.Weight - }); + }; } private class ShowRule diff --git a/src/Umbraco.Core/Manifest/ManifestWatcher.cs b/src/Umbraco.Core/Manifest/ManifestWatcher.cs index 8d81ac3de5..23caac3a1b 100644 --- a/src/Umbraco.Core/Manifest/ManifestWatcher.cs +++ b/src/Umbraco.Core/Manifest/ManifestWatcher.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using Umbraco.Core.Hosting; using Umbraco.Core.Logging; +using Umbraco.Net; namespace Umbraco.Core.Manifest { @@ -13,13 +14,13 @@ namespace Umbraco.Core.Manifest private static volatile bool _isRestarting; private readonly ILogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly List _fws = new List(); - public ManifestWatcher(ILogger logger, IHostingEnvironment hostingEnvironment) + public ManifestWatcher(ILogger logger, IUmbracoApplicationLifetime umbracoApplicationLifetime) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _hostingEnvironment = hostingEnvironment; + _umbracoApplicationLifetime = umbracoApplicationLifetime; } public void Start(params string[] packageFolders) @@ -57,7 +58,7 @@ namespace Umbraco.Core.Manifest _isRestarting = true; _logger.Info("Manifest has changed, app pool is restarting ({Path})", e.FullPath); - _hostingEnvironment.LazyRestartApplication(); + _umbracoApplicationLifetime.Restart(); Dispose(); // uh? if the app restarts then this should be disposed anyways? } } 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/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index 6470de912b..453b455d4b 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -81,9 +81,10 @@ namespace Umbraco.Core.Models if (propertyInfo.PropertyType.IsGenericType && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) - || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>))) { - //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> + //if it is a IEnumerable<>, IReadOnlyCollection, IList or ICollection<> we'll use a List<> since it implements them all var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType }; } 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/IMacro.cs b/src/Umbraco.Core/Models/IMacro.cs index c3d37b0700..9d1c47154c 100644 --- a/src/Umbraco.Core/Models/IMacro.cs +++ b/src/Umbraco.Core/Models/IMacro.cs @@ -58,22 +58,10 @@ namespace Umbraco.Core.Models [DataMember] string MacroSource { get; set; } - /// - /// Gets or set the macro type - /// - [DataMember] - MacroTypes MacroType { get; set; } - /// /// Gets or sets a list of Macro Properties /// [DataMember] MacroPropertyCollection Properties { get; } - - ///// - ///// Returns an enum based on the properties on the Macro - ///// - ///// - //MacroTypes MacroType(); } } 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/MacroTypes.cs b/src/Umbraco.Core/Models/MacroTypes.cs deleted file mode 100644 index 5f8440845d..0000000000 --- a/src/Umbraco.Core/Models/MacroTypes.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Enum for the various types of Macros - /// - [Serializable] - [DataContract(IsReference = true)] - public enum MacroTypes - { - [EnumMember] - Unknown = 4, - [EnumMember] - PartialView = 7 - } -} diff --git a/src/Umbraco.Core/Models/PartialViewMacroModel.cs b/src/Umbraco.Core/Models/PartialViewMacroModel.cs index 9964877cba..f3272644f4 100644 --- a/src/Umbraco.Core/Models/PartialViewMacroModel.cs +++ b/src/Umbraco.Core/Models/PartialViewMacroModel.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.Models /// /// The model used when rendering Partial View Macros /// - public class PartialViewMacroModel + public class PartialViewMacroModel : IContentModel { public PartialViewMacroModel(IPublishedContent page, 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/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index 28290685c2..4c676feed6 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -31,8 +31,6 @@ namespace Umbraco.Core.Models _childObjectType = childObjectType; } - - /// /// Gets or sets the Name of the RelationType /// diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs new file mode 100644 index 0000000000..a032720d46 --- /dev/null +++ b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs @@ -0,0 +1,25 @@ +using System; + +namespace Umbraco.Net +{ + // TODO: This shouldn't be in this namespace? + public interface IUmbracoApplicationLifetime + { + /// + /// A value indicating whether the application is restarting after the current request. + /// + bool IsRestarting { get; } + /// + /// 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/Net/IUserAgentProvider.cs b/src/Umbraco.Core/Net/IUserAgentProvider.cs new file mode 100644 index 0000000000..14246ea99e --- /dev/null +++ b/src/Umbraco.Core/Net/IUserAgentProvider.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Net +{ + public interface IUserAgentProvider + { + string GetUserAgent(); + } +} diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs index 7425f64727..9a976bea41 100644 --- a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Packaging { - public class ConflictingPackageData + public class ConflictingPackageData { private readonly IMacroService _macroService; private readonly IFileService _fileService; @@ -23,7 +23,7 @@ namespace Umbraco.Core.Packaging return stylesheetNodes .Select(n => { - var xElement = n.Element("Name") ?? n.Element("name"); ; + var xElement = n.Element("Name") ?? n.Element("name"); if (xElement == null) throw new FormatException("Missing \"Name\" element"); diff --git a/src/Umbraco.Core/Packaging/PackageDefinition.cs b/src/Umbraco.Core/Packaging/PackageDefinition.cs index 11bf26c579..29a1919a2b 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinition.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinition.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Models.Packaging /// /// This is used only for conversions and will not 'get' a PackageDefinition from the repository with a valid ID /// - internal static PackageDefinition FromCompiledPackage(CompiledPackage compiled) + public static PackageDefinition FromCompiledPackage(CompiledPackage compiled) { return new PackageDefinition { diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index a2a30f3a06..2c5b964518 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -90,7 +90,7 @@ namespace Umbraco.Core.Packaging { var packagesXml = EnsureStorage(out _); if (packagesXml?.Root == null) - yield break;; + yield break; foreach (var packageXml in packagesXml.Root.Elements("package")) yield return _parser.ToPackageDefinition(packageXml); @@ -518,7 +518,6 @@ namespace Umbraco.Core.Packaging private XElement GetStylesheetXml(string name, bool includeProperties) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); -; var sts = _fileService.GetStylesheetByName(name); if (sts == null) return null; var stylesheetXml = new XElement("Stylesheet"); 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/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs index 84ee8c314b..03ce0c066a 100644 --- a/src/Umbraco.Core/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -149,11 +149,11 @@ namespace Umbraco.Core } public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, - IUmbracoSettingsSection umbracoSettingsSection, int templateId) + IWebRoutingSettings webRoutingSettings, int templateId) { return content.IsAllowedTemplate(contentTypeService, - umbracoSettingsSection.WebRouting.DisableAlternativeTemplates, - umbracoSettingsSection.WebRouting.ValidateAlternativeTemplates, templateId); + webRoutingSettings.DisableAlternativeTemplates, + webRoutingSettings.ValidateAlternativeTemplates, templateId); } public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, int templateId) diff --git a/src/Umbraco.Core/Routing/AliasUrlProvider.cs b/src/Umbraco.Core/Routing/AliasUrlProvider.cs index c9cc3d5156..e71de2f6c6 100644 --- a/src/Umbraco.Core/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Core/Routing/AliasUrlProvider.cs @@ -14,13 +14,13 @@ namespace Umbraco.Web.Routing public class AliasUrlProvider : IUrlProvider { private readonly IGlobalSettings _globalSettings; - private readonly IRequestHandlerSection _requestConfig; + private readonly IRequestHandlerSettings _requestConfig; private readonly ISiteDomainHelper _siteDomainHelper; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly UriUtility _uriUtility; private readonly IPublishedValueFallback _publishedValueFallback; - public AliasUrlProvider(IGlobalSettings globalSettings, IRequestHandlerSection requestConfig, ISiteDomainHelper siteDomainHelper, UriUtility uriUtility, IPublishedValueFallback publishedValueFallback, IUmbracoContextAccessor umbracoContextAccessor) + public AliasUrlProvider(IGlobalSettings globalSettings, IRequestHandlerSettings requestConfig, ISiteDomainHelper siteDomainHelper, UriUtility uriUtility, IPublishedValueFallback publishedValueFallback, IUmbracoContextAccessor umbracoContextAccessor) { _globalSettings = globalSettings; _requestConfig = requestConfig; diff --git a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs similarity index 79% rename from src/Umbraco.Web/Routing/ContentFinderByIdPath.cs rename to src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index bf7d5ef7c4..4b58832e8e 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -16,14 +16,14 @@ namespace Umbraco.Web.Routing public class ContentFinderByIdPath : IContentFinder { private readonly ILogger _logger; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IWebRoutingSection _webRoutingSection; + private readonly IRequestAccessor _requestAccessor; + private readonly IWebRoutingSettings _webRoutingSettings; - public ContentFinderByIdPath(IWebRoutingSection webRoutingSection, ILogger logger, IHttpContextAccessor httpContextAccessor) + public ContentFinderByIdPath(IWebRoutingSettings webRoutingSettings, ILogger logger, IRequestAccessor requestAccessor) { - _webRoutingSection = webRoutingSection ?? throw new System.ArgumentNullException(nameof(webRoutingSection)); + _webRoutingSettings = webRoutingSettings ?? throw new System.ArgumentNullException(nameof(webRoutingSettings)); _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); - _httpContextAccessor = httpContextAccessor; + _requestAccessor = requestAccessor; } /// @@ -35,7 +35,7 @@ namespace Umbraco.Web.Routing { if (frequest.UmbracoContext != null && frequest.UmbracoContext.InPreviewMode == false - && _webRoutingSection.DisableFindContentByIdPath) + && _webRoutingSettings.DisableFindContentByIdPath) return false; IPublishedContent node = null; @@ -56,12 +56,14 @@ namespace Umbraco.Web.Routing if (node != null) { - var httpContext = _httpContextAccessor.GetRequiredHttpContext(); + + var cultureFromQuerystring = _requestAccessor.GetQueryStringValue("culture"); + //if we have a node, check if we have a culture in the query string - if (httpContext.Request.QueryString.ContainsKey("culture")) + if (!string.IsNullOrEmpty(cultureFromQuerystring)) { //we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though - frequest.Culture = CultureInfo.GetCultureInfo(httpContext.Request.QueryString["culture"]); + frequest.Culture = CultureInfo.GetCultureInfo(cultureFromQuerystring); } frequest.PublishedContent = node; diff --git a/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs similarity index 69% rename from src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs rename to src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs index fb79e13dbc..16d7a0c4cf 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs @@ -1,4 +1,5 @@ -namespace Umbraco.Web.Routing + +namespace Umbraco.Web.Routing { /// /// This looks up a document by checking for the umbPageId of a request/query string @@ -9,17 +10,17 @@ /// public class ContentFinderByPageIdQuery : IContentFinder { - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IRequestAccessor _requestAccessor; - public ContentFinderByPageIdQuery(IHttpContextAccessor httpContextAccessor) + public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor) { - _httpContextAccessor = httpContextAccessor; + _requestAccessor = requestAccessor; } public bool TryFindContent(IPublishedRequest frequest) { int pageId; - if (int.TryParse(_httpContextAccessor.GetRequiredHttpContext().Request["umbPageID"], out pageId)) + if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), out pageId)) { var doc = frequest.UmbracoContext.Content.GetById(pageId); diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs index 933ab47150..7bcea4681e 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs @@ -17,15 +17,16 @@ namespace Umbraco.Web.Routing public class ContentFinderByUrlAndTemplate : ContentFinderByUrl { private readonly IFileService _fileService; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; - private readonly IContentTypeService _contentTypeService; - public ContentFinderByUrlAndTemplate(ILogger logger, IFileService fileService, IUmbracoSettingsSection umbracoSettingsSection, IContentTypeService contentTypeService) + private readonly IContentTypeService _contentTypeService; + private readonly IWebRoutingSettings _webRoutingSettings; + + public ContentFinderByUrlAndTemplate(ILogger logger, IFileService fileService, IContentTypeService contentTypeService, IWebRoutingSettings webRoutingSettings) : base(logger) { _fileService = fileService; - _umbracoSettingsSection = umbracoSettingsSection; _contentTypeService = contentTypeService; + _webRoutingSettings = webRoutingSettings; } /// @@ -75,7 +76,7 @@ namespace Umbraco.Web.Routing } // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings - if (!node.IsAllowedTemplate(_contentTypeService, _umbracoSettingsSection, template.Id)) + if (!node.IsAllowedTemplate(_contentTypeService, _webRoutingSettings, template.Id)) { Logger.Warn("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", template.Alias, node.Id); frequest.PublishedContent = null; // clear diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index 81102810e8..f56d96b6b3 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -12,14 +12,14 @@ namespace Umbraco.Web.Routing /// public class DefaultUrlProvider : IUrlProvider { - private readonly IRequestHandlerSection _requestSettings; + private readonly IRequestHandlerSettings _requestSettings; private readonly ILogger _logger; private readonly IGlobalSettings _globalSettings; private readonly ISiteDomainHelper _siteDomainHelper; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly UriUtility _uriUtility; - public DefaultUrlProvider(IRequestHandlerSection requestSettings, ILogger logger, IGlobalSettings globalSettings, ISiteDomainHelper siteDomainHelper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility) + public DefaultUrlProvider(IRequestHandlerSettings requestSettings, ILogger logger, IGlobalSettings globalSettings, ISiteDomainHelper siteDomainHelper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility) { _requestSettings = requestSettings; _logger = logger; diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index 9b00e59deb..6e4d0008a9 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Routing public class PublishedRequest : IPublishedRequest { private readonly IPublishedRouter _publishedRouter; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; + private readonly IWebRoutingSettings _webRoutingSettings; private bool _readonly; // after prepared private bool _readonlyUri; // after preparing @@ -33,11 +33,11 @@ namespace Umbraco.Web.Routing /// The published router. /// The Umbraco context. /// The request Uri. - internal PublishedRequest(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IUmbracoSettingsSection umbracoSettingsSection, Uri uri = null) + internal PublishedRequest(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IWebRoutingSettings webRoutingSettings, Uri uri = null) { UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); - _umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection)); + _webRoutingSettings = webRoutingSettings; Uri = uri ?? umbracoContext.CleanedUmbracoUrl; } @@ -178,7 +178,7 @@ namespace Umbraco.Web.Routing IsInternalRedirectPublishedContent = isInternalRedirect; // must restore the template if it's an internal redirect & the config option is set - if (isInternalRedirect && _umbracoSettingsSection.WebRouting.InternalRedirectPreservesTemplate) + if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) { // restore TemplateModel = template; diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs similarity index 86% rename from src/Umbraco.Web/Routing/PublishedRouter.cs rename to src/Umbraco.Core/Routing/PublishedRouter.cs index 1a6048e4ec..1a422dea92 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -4,13 +4,11 @@ using System.Threading; using System.Globalization; using System.IO; using Umbraco.Core; -using Umbraco.Web.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; -using Umbraco.Web.Macros; using Umbraco.Web.Security; namespace Umbraco.Web.Routing @@ -20,47 +18,56 @@ namespace Umbraco.Web.Routing /// public class PublishedRouter : IPublishedRouter { - private readonly IWebRoutingSection _webRoutingSection; + private readonly IWebRoutingSettings _webRoutingSettings; private readonly ContentFinderCollection _contentFinders; private readonly IContentLastChanceFinder _contentLastChanceFinder; - private readonly ServiceContext _services; private readonly IProfilingLogger _profilingLogger; private readonly IVariationContextAccessor _variationContextAccessor; private readonly ILogger _logger; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; - private readonly IHttpContextAccessor _httpContextAccessor; private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IRequestAccessor _requestAccessor; + private readonly IPublishedValueFallback _publishedValueFallback; + private readonly IPublicAccessChecker _publicAccessChecker; + private readonly IFileService _fileService; + private readonly IContentTypeService _contentTypeService; + private readonly IPublicAccessService _publicAccessService; /// /// Initializes a new instance of the class. /// public PublishedRouter( - IWebRoutingSection webRoutingSection, + IWebRoutingSettings webRoutingSettings, ContentFinderCollection contentFinders, IContentLastChanceFinder contentLastChanceFinder, IVariationContextAccessor variationContextAccessor, - ServiceContext services, IProfilingLogger proflog, - IUmbracoSettingsSection umbracoSettingsSection, - IHttpContextAccessor httpContextAccessor, - IPublishedUrlProvider publishedUrlProvider) + IPublishedUrlProvider publishedUrlProvider, + IRequestAccessor requestAccessor, + IPublishedValueFallback publishedValueFallback, + IPublicAccessChecker publicAccessChecker, + IFileService fileService, + IContentTypeService contentTypeService, + IPublicAccessService publicAccessService) { - _webRoutingSection = webRoutingSection ?? throw new ArgumentNullException(nameof(webRoutingSection)); + _webRoutingSettings = webRoutingSettings ?? throw new ArgumentNullException(nameof(webRoutingSettings)); _contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders)); _contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder)); - _services = services ?? throw new ArgumentNullException(nameof(services)); _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); _logger = proflog; - _umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection)); - _httpContextAccessor = httpContextAccessor; _publishedUrlProvider = publishedUrlProvider; + _requestAccessor = requestAccessor; + _publishedValueFallback = publishedValueFallback; + _publicAccessChecker = publicAccessChecker; + _fileService = fileService; + _contentTypeService = contentTypeService; + _publicAccessService = publicAccessService; } /// public IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null) { - return new PublishedRequest(this, umbracoContext, _umbracoSettingsSection, uri ?? umbracoContext.CleanedUmbracoUrl); + return new PublishedRequest(this, umbracoContext, _webRoutingSettings, uri ?? umbracoContext.CleanedUmbracoUrl); } #region Request @@ -358,7 +365,7 @@ namespace Umbraco.Web.Routing /// public ITemplate GetTemplate(string alias) { - return _services.FileService.GetTemplate(alias); + return _fileService.GetTemplate(alias); } /// @@ -498,7 +505,7 @@ namespace Umbraco.Web.Routing var redirect = false; var valid = false; IPublishedContent internalRedirectNode = null; - var internalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); + var internalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); if (internalRedirectId > 0) { @@ -508,7 +515,7 @@ namespace Umbraco.Web.Routing } else { - var udiInternalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId); + var udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId); if (udiInternalRedirectId != null) { // try and get the redirect node from a UDI Guid @@ -555,58 +562,34 @@ namespace Umbraco.Web.Routing var path = request.PublishedContent.Path; - var publicAccessAttempt = _services.PublicAccessService.IsProtected(path); + var publicAccessAttempt = _publicAccessService.IsProtected(path); if (publicAccessAttempt) { _logger.Debug("EnsurePublishedContentAccess: Page is protected, check for access"); - var membershipHelper = Current.Factory.GetInstance(); - - if (membershipHelper.IsLoggedIn() == false) + var status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id); + switch (status) { - _logger.Debug("EnsurePublishedContentAccess: Not logged in, redirect to login page"); - - var loginPageId = publicAccessAttempt.Result.LoginNodeId; - - if (loginPageId != request.PublishedContent.Id) - request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(loginPageId); - } - else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, membershipHelper.CurrentUserName, membershipHelper.GetCurrentUserRoles()) == false) - { - _logger.Debug("EnsurePublishedContentAccess: Current member has not access, redirect to error page"); - var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; - if (errorPageId != request.PublishedContent.Id) - request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); - } - else - { - // grab the current member - var member = membershipHelper.GetCurrentMember(); - // if the member has the "approved" and/or "locked out" properties, make sure they're correctly set before allowing access - var memberIsActive = true; - if (member != null) - { - if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false) - memberIsActive = member.Value(Constants.Conventions.Member.IsApproved); - - if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) == false) - memberIsActive = member.Value(Constants.Conventions.Member.IsLockedOut) == false; - } - - if (memberIsActive == false) - { - _logger.Debug( - "Current member is either unapproved or locked out, redirect to error page"); - var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; - if (errorPageId != request.PublishedContent.Id) - request.PublishedContent = - request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); - } - else - { + case PublicAccessStatus.NotLoggedIn: + _logger.Debug("EnsurePublishedContentAccess: Not logged in, redirect to login page"); + SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.LoginNodeId); + break; + case PublicAccessStatus.AccessDenied: + _logger.Debug("EnsurePublishedContentAccess: Current member has not access, redirect to error page"); + SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId); + break; + case PublicAccessStatus.LockedOut: + _logger.Debug("Current member is locked out, redirect to error page"); + SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId); + break; + case PublicAccessStatus.NotApproved: + _logger.Debug("Current member is unapproved, redirect to error page"); + SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId); + break; + case PublicAccessStatus.AccessAccepted: _logger.Debug("Current member has access"); - } + break; } } else @@ -615,6 +598,12 @@ namespace Umbraco.Web.Routing } } + private static void SetPublishedContentAsOtherPage(IPublishedRequest request, int errorPageId) + { + if (errorPageId != request.PublishedContent.Id) + request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); + } + /// /// Finds a template for the current node, if any. /// @@ -635,9 +624,9 @@ namespace Umbraco.Web.Routing // does not apply // + optionally, apply the alternate template on internal redirects var useAltTemplate = request.IsInitialPublishedContent - || (_webRoutingSection.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent); + || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent); var altTemplate = useAltTemplate - ? _httpContextAccessor.GetRequiredHttpContext().Request[Constants.Conventions.Url.AltTemplate] + ? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate) : null; if (string.IsNullOrWhiteSpace(altTemplate)) @@ -674,10 +663,15 @@ namespace Umbraco.Web.Routing _logger.Debug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings - if (request.PublishedContent.IsAllowedTemplate(altTemplate)) + if (request.PublishedContent.IsAllowedTemplate( + _fileService, + _contentTypeService, + _webRoutingSettings.DisableAlternativeTemplates, + _webRoutingSettings.ValidateAlternativeTemplates, + altTemplate)) { // allowed, use - var template = _services.FileService.GetTemplate(altTemplate); + var template = _fileService.GetTemplate(altTemplate); if (template != null) { @@ -731,7 +725,7 @@ namespace Umbraco.Web.Routing if (templateId == null) throw new InvalidOperationException("The template is not set, the page cannot render."); - var template = _services.FileService.GetTemplate(templateId.Value); + var template = _fileService.GetTemplate(templateId.Value); if (template == null) throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render."); _logger.Debug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); @@ -750,7 +744,7 @@ namespace Umbraco.Web.Routing if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) return; - var redirectId = request.PublishedContent.Value(Constants.Conventions.Content.Redirect, defaultValue: -1); + var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, defaultValue: -1); var redirectUrl = "#"; if (redirectId > 0) { @@ -759,7 +753,7 @@ namespace Umbraco.Web.Routing else { // might be a UDI instead of an int Id - var redirectUdi = request.PublishedContent.Value(Constants.Conventions.Content.Redirect); + var redirectUdi = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect); if (redirectUdi != null) redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid); } diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs index 11bb1dabfb..0c68580204 100644 --- a/src/Umbraco.Core/Routing/UriUtility.cs +++ b/src/Umbraco.Core/Routing/UriUtility.cs @@ -15,6 +15,7 @@ namespace Umbraco.Web public UriUtility(IHostingEnvironment hostingEnvironment) { + if (hostingEnvironment is null) throw new ArgumentNullException(nameof(hostingEnvironment)); ResetAppDomainAppVirtualPath(hostingEnvironment); } @@ -61,7 +62,7 @@ namespace Umbraco.Web // maps an internal umbraco uri to a public uri // ie with virtual directory, .aspx if required... - public Uri UriFromUmbraco(Uri uri, IGlobalSettings globalSettings, IRequestHandlerSection requestConfig) + public Uri UriFromUmbraco(Uri uri, IGlobalSettings globalSettings, IRequestHandlerSettings requestConfig) { var path = uri.GetSafeAbsolutePath(); diff --git a/src/Umbraco.Core/Routing/UrlProvider.cs b/src/Umbraco.Core/Routing/UrlProvider.cs index 80169e54e2..fa764cf7ff 100644 --- a/src/Umbraco.Core/Routing/UrlProvider.cs +++ b/src/Umbraco.Core/Routing/UrlProvider.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing /// The list of media url providers. /// The current variation accessor. /// - public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IWebRoutingSection routingSettings, UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, IVariationContextAccessor variationContextAccessor) + public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IWebRoutingSettings routingSettings, UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, IVariationContextAccessor variationContextAccessor) { if (routingSettings == null) throw new ArgumentNullException(nameof(routingSettings)); diff --git a/src/Umbraco.Core/Runtime/IMainDom.cs b/src/Umbraco.Core/Runtime/IMainDom.cs index 444fc1c7d0..93a560ff7d 100644 --- a/src/Umbraco.Core/Runtime/IMainDom.cs +++ b/src/Umbraco.Core/Runtime/IMainDom.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Hosting; // TODO: Can't change namespace due to breaking changes, change in netcore namespace Umbraco.Core @@ -16,10 +17,17 @@ namespace Umbraco.Core /// Gets a value indicating whether the current domain is the main domain. /// /// - /// When the first call is made to this there will generally be some logic executed to acquire a distributed lock lease. + /// Acquire must be called first else this will always return false /// bool IsMainDom { get; } + /// + /// Tries to acquire the MainDom, returns true if successful else false + /// + /// + /// + bool Acquire(IApplicationShutdownRegistry hostingEnvironment); + /// /// Registers a resource that requires the current AppDomain to be the main domain to function. /// diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index 2c56852095..81db1b700d 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Runtime #region Vars private readonly ILogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; + private IApplicationShutdownRegistry _hostingEnvironment; private readonly IMainDomLock _mainDomLock; // our own lock for local consistency @@ -42,17 +42,25 @@ namespace Umbraco.Core.Runtime #region Ctor // initializes a new instance of MainDom - public MainDom(ILogger logger, IHostingEnvironment hostingEnvironment, IMainDomLock systemLock) + public MainDom(ILogger logger, IMainDomLock systemLock) { - hostingEnvironment.RegisterObject(this); - _logger = logger; - _hostingEnvironment = hostingEnvironment; _mainDomLock = systemLock; } #endregion + public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) + { + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + + return LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => + { + hostingEnvironment.RegisterObject(this); + return Acquire(); + }); + } + /// /// Registers a resource that requires the current AppDomain to be the main domain to function. /// @@ -180,10 +188,9 @@ namespace Umbraco.Core.Runtime /// Gets a value indicating whether the current domain is the main domain. /// /// - /// The lazy initializer call will only call the Acquire callback when it's not been initialized, else it will just return - /// the value from _isMainDom which means when we set _isMainDom to false again after being signaled, this will return false; + /// Acquire must be called first else this will always return false /// - public bool IsMainDom => LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => Acquire()); + public bool IsMainDom => _isMainDom; // IRegisteredObject void IRegisteredObject.Stop(bool immediate) @@ -193,7 +200,7 @@ namespace Umbraco.Core.Runtime // The web app is stopping, need to wind down Dispose(true); - _hostingEnvironment.UnregisterObject(this); + _hostingEnvironment?.UnregisterObject(this); } #region IDisposable Support diff --git a/src/Umbraco.Core/Scheduling/KeepAlive.cs b/src/Umbraco.Core/Scheduling/KeepAlive.cs index c677c66b83..1c4ef075ae 100644 --- a/src/Umbraco.Core/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Core/Scheduling/KeepAlive.cs @@ -11,18 +11,22 @@ namespace Umbraco.Web.Scheduling { public class KeepAlive : RecurringTaskBase { - private readonly IRuntimeState _runtime; - private readonly IKeepAliveSection _keepAliveSection; + private readonly IRuntimeState _runtimeState; + private readonly IMainDom _mainDom; + private readonly IKeepAliveSettings _keepAliveSettings; private readonly IProfilingLogger _logger; + private readonly IServerRegistrar _serverRegistrar; private static HttpClient _httpClient; public KeepAlive(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IRuntimeState runtime, IKeepAliveSection keepAliveSection, IProfilingLogger logger) + IRuntimeState runtimeState, IMainDom mainDom, IKeepAliveSettings keepAliveSettings, IProfilingLogger logger, IServerRegistrar serverRegistrar) : base(runner, delayMilliseconds, periodMilliseconds) { - _runtime = runtime; - _keepAliveSection = keepAliveSection; + _runtimeState = runtimeState; + _mainDom = mainDom; + _keepAliveSettings = keepAliveSettings; _logger = logger; + _serverRegistrar = serverRegistrar; if (_httpClient == null) _httpClient = new HttpClient(); } @@ -30,7 +34,7 @@ namespace Umbraco.Web.Scheduling public override async Task PerformRunAsync(CancellationToken token) { // not on replicas nor unknown role servers - switch (_runtime.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.Debug("Does not run on replica servers."); @@ -41,7 +45,7 @@ namespace Umbraco.Web.Scheduling } // ensure we do not run if not main domain, but do NOT lock it - if (_runtime.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down @@ -49,12 +53,12 @@ namespace Umbraco.Web.Scheduling using (_logger.DebugDuration("Keep alive executing", "Keep alive complete")) { - var keepAlivePingUrl = _keepAliveSection.KeepAlivePingUrl; + var keepAlivePingUrl = _keepAliveSettings.KeepAlivePingUrl; try { if (keepAlivePingUrl.Contains("{umbracoApplicationUrl}")) { - var umbracoAppUrl = _runtime.ApplicationUrl.ToString(); + var umbracoAppUrl = _runtimeState.ApplicationUrl.ToString(); if (umbracoAppUrl.IsNullOrWhiteSpace()) { _logger.Warn("No umbracoApplicationUrl for service (yet), skip."); diff --git a/src/Umbraco.Core/Scheduling/TempFileCleanup.cs b/src/Umbraco.Core/Scheduling/TempFileCleanup.cs index aefaf605db..90bf4ee9eb 100644 --- a/src/Umbraco.Core/Scheduling/TempFileCleanup.cs +++ b/src/Umbraco.Core/Scheduling/TempFileCleanup.cs @@ -14,26 +14,26 @@ namespace Umbraco.Web.Scheduling { private readonly DirectoryInfo[] _tempFolders; private readonly TimeSpan _age; - private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; private readonly IProfilingLogger _logger; public TempFileCleanup(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, IEnumerable tempFolders, TimeSpan age, - IRuntimeState runtime, IProfilingLogger logger) + IMainDom mainDom, IProfilingLogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { //SystemDirectories.TempFileUploads _tempFolders = tempFolders.ToArray(); _age = age; - _runtime = runtime; + _mainDom = mainDom; _logger = logger; } public override bool PerformRun() { // ensure we do not run if not main domain - if (_runtime.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down diff --git a/src/Umbraco.Infrastructure/Scoping/RepositoryCacheMode.cs b/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs similarity index 100% rename from src/Umbraco.Infrastructure/Scoping/RepositoryCacheMode.cs rename to src/Umbraco.Core/Scoping/RepositoryCacheMode.cs diff --git a/src/Umbraco.Core/Security/IMemberUserKeyProvider.cs b/src/Umbraco.Core/Security/IMemberUserKeyProvider.cs new file mode 100644 index 0000000000..439e7a82b8 --- /dev/null +++ b/src/Umbraco.Core/Security/IMemberUserKeyProvider.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Security +{ + public interface IMemberUserKeyProvider + { + object GetMemberProviderUserKey(); + } +} diff --git a/src/Umbraco.Core/Security/IPublicAccessChecker.cs b/src/Umbraco.Core/Security/IPublicAccessChecker.cs new file mode 100644 index 0000000000..a47186394e --- /dev/null +++ b/src/Umbraco.Core/Security/IPublicAccessChecker.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Web.Security +{ + public interface IPublicAccessChecker + { + PublicAccessStatus HasMemberAccessToContent(int publishedContentId); + } +} diff --git a/src/Umbraco.Core/Security/PublicAccessStatus.cs b/src/Umbraco.Core/Security/PublicAccessStatus.cs new file mode 100644 index 0000000000..57df423749 --- /dev/null +++ b/src/Umbraco.Core/Security/PublicAccessStatus.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Web.Security +{ + public enum PublicAccessStatus + { + NotLoggedIn, + AccessDenied, + NotApproved, + LockedOut, + AccessAccepted + } +} diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs index 82e5c6f171..4c818aa87c 100644 --- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs @@ -51,7 +51,10 @@ namespace Umbraco.Core.Services IEnumerable GetComposedOf(int id); // composition axis IEnumerable GetChildren(int id); + IEnumerable GetChildren(Guid id); + bool HasChildren(int id); + bool HasChildren(Guid id); void Save(TItem item, int userId = Constants.Security.SuperUserId); void Save(IEnumerable items, int userId = Constants.Security.SuperUserId); 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/IRuntime.cs b/src/Umbraco.Core/Services/IRuntime.cs index e846342dbc..4715068073 100644 --- a/src/Umbraco.Core/Services/IRuntime.cs +++ b/src/Umbraco.Core/Services/IRuntime.cs @@ -13,13 +13,15 @@ namespace Umbraco.Core /// /// The application register. /// The application factory. - IFactory Boot(IRegister register); + IFactory Configure(IRegister register); /// /// Gets the runtime state. /// IRuntimeState State { get; } + void Start(); + /// /// Terminates the runtime. /// diff --git a/src/Umbraco.Core/Services/IRuntimeState.cs b/src/Umbraco.Core/Services/IRuntimeState.cs index 38da246cc1..4b5e26651b 100644 --- a/src/Umbraco.Core/Services/IRuntimeState.cs +++ b/src/Umbraco.Core/Services/IRuntimeState.cs @@ -25,33 +25,12 @@ namespace Umbraco.Core /// SemVersion SemanticVersion { get; } - /// - /// Gets a value indicating whether the application is running in debug mode. - /// - bool Debug { get; } - - /// - /// Gets a value indicating whether the runtime is the current main domain. - /// - bool IsMainDom { get; } - - /// - /// Get the server's current role. - /// - ServerRole ServerRole { get; } - /// /// Gets the Umbraco application url. /// /// This is eg "http://www.example.com". Uri ApplicationUrl { get; } - /// - /// Gets the Umbraco application virtual path. - /// - /// This is either "/" or eg "/virtual". - string ApplicationVirtualPath { get; } - /// /// Gets the runtime level of execution. /// @@ -77,6 +56,5 @@ namespace Umbraco.Core /// BootFailedException BootFailedException { get; } - IMainDom MainDom { get; } } } diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index 7f97b7b71a..f66ab5ef62 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -7,7 +7,7 @@ using Umbraco.Web.Sections; namespace Umbraco.Web.Services { - internal class SectionService : ISectionService + public class SectionService : ISectionService { private readonly IUserService _userService; private readonly SectionCollection _sectionCollection; 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/Services/TreeService.cs b/src/Umbraco.Core/Services/TreeService.cs index dc500f2583..48cc98b2db 100644 --- a/src/Umbraco.Core/Services/TreeService.cs +++ b/src/Umbraco.Core/Services/TreeService.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.Services /// /// Implements . /// - internal class TreeService : ITreeService + public class TreeService : ITreeService { private readonly TreeCollection _treeCollection; diff --git a/src/Umbraco.Core/SimpleMainDom.cs b/src/Umbraco.Core/SimpleMainDom.cs index 87cc7bcff1..e6bdda67d7 100644 --- a/src/Umbraco.Core/SimpleMainDom.cs +++ b/src/Umbraco.Core/SimpleMainDom.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Hosting; namespace Umbraco.Core { @@ -16,6 +17,9 @@ namespace Umbraco.Core /// public bool IsMainDom { get; private set; } = true; + // always acquire + public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) => true; + /// public bool Register(Action release, int weight = 100) => Register(null, release, weight); diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index d61c0a87ca..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; } /// @@ -938,7 +954,24 @@ namespace Umbraco.Core return text; } + /// + /// Returns a new string in which only the first occurrence of a specified string is replaced by a specified replacement string. + /// + /// The string to filter. + /// The string to replace. + /// The replacement string. + /// The filtered string. + public static string ReplaceFirst(this string text, string search, string replace) + { + if (text == null) throw new ArgumentNullException(nameof(text)); + var pos = text.IndexOf(search, StringComparison.InvariantCulture); + + if (pos < 0) + return text; + + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 6361186604..c3e7fa85c3 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Strings { #region Ctor, consts and vars - public DefaultShortStringHelper(IUmbracoSettingsSection settings) + public DefaultShortStringHelper(IRequestHandlerSettings settings) { _config = new DefaultShortStringHelperConfig().WithDefault(settings); } @@ -619,6 +619,6 @@ namespace Umbraco.Core.Strings return new string(output, 0, opos); } - #endregion + #endregion } } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs index e75e00defb..25ee781ae9 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Strings public string DefaultCulture { get; set; } = ""; // invariant public Dictionary UrlReplaceCharacters { get; set; } - + public DefaultShortStringHelperConfig WithConfig(Config config) { return WithConfig(DefaultCulture, CleanStringType.RoleMask, config); @@ -57,16 +57,16 @@ namespace Umbraco.Core.Strings /// Sets the default configuration. /// /// The short string helper. - public DefaultShortStringHelperConfig WithDefault(IUmbracoSettingsSection umbracoSettings) + public DefaultShortStringHelperConfig WithDefault(IRequestHandlerSettings requestHandlerSettings) { - UrlReplaceCharacters = umbracoSettings.RequestHandler.CharCollection + UrlReplaceCharacters = requestHandlerSettings.CharCollection .Where(x => string.IsNullOrEmpty(x.Char) == false) .ToDictionary(x => x.Char, x => x.Replacement); var urlSegmentConvertTo = CleanStringType.Utf8; - if (umbracoSettings.RequestHandler.ConvertUrlsToAscii) + if (requestHandlerSettings.ConvertUrlsToAscii) urlSegmentConvertTo = CleanStringType.Ascii; - if (umbracoSettings.RequestHandler.TryConvertUrlsToAscii) + if (requestHandlerSettings.TryConvertUrlsToAscii) urlSegmentConvertTo = CleanStringType.TryAscii; return WithConfig(CleanStringType.UrlSegment, new Config diff --git a/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs index d63478ef96..d8ec82818d 100644 --- a/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs @@ -3,8 +3,10 @@ namespace Umbraco.Core.Sync /// /// An implementation that works by storing messages in the database. /// - public interface IBatchedDatabaseServerMessenger : IServerMessenger + public interface IBatchedDatabaseServerMessenger : IDatabaseServerMessenger { void FlushBatch(); + DatabaseServerMessengerOptions Options { get; } + void Startup(); } } diff --git a/src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs new file mode 100644 index 0000000000..a49cfdd023 --- /dev/null +++ b/src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Sync +{ + public interface IDatabaseServerMessenger: IServerMessenger + { + void Sync(); + } +} diff --git a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs index 3f62291ff1..2ecd61f7d2 100644 --- a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs +++ b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Templates _publishedUrlProvider = publishedUrlProvider; } - internal IEnumerable FindUdisFromLocalLinks(string text) + public IEnumerable FindUdisFromLocalLinks(string text) { foreach ((int? intId, GuidUdi udi, string tagValue) in FindLocalLinkIds(text)) { diff --git a/src/Umbraco.Core/Templates/HtmlUrlParser.cs b/src/Umbraco.Core/Templates/HtmlUrlParser.cs index a2fdb93292..566fce8b87 100644 --- a/src/Umbraco.Core/Templates/HtmlUrlParser.cs +++ b/src/Umbraco.Core/Templates/HtmlUrlParser.cs @@ -7,16 +7,16 @@ namespace Umbraco.Web.Templates { public sealed class HtmlUrlParser { - private readonly IContentSection _contentSection; + private readonly IContentSettings _contentSettings; private readonly IIOHelper _ioHelper; private readonly IProfilingLogger _logger; private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - public HtmlUrlParser(IContentSection contentSection, IProfilingLogger logger, IIOHelper ioHelper) + public HtmlUrlParser(IContentSettings contentSettings, IProfilingLogger logger, IIOHelper ioHelper) { - _contentSection = contentSection; + _contentSettings = contentSettings; _ioHelper = ioHelper; _logger = logger; } @@ -32,7 +32,7 @@ namespace Umbraco.Web.Templates /// public string EnsureUrls(string text) { - if (_contentSection.ResolveUrlsFromTextString == false) return text; + if (_contentSettings.ResolveUrlsFromTextString == false) return text; using (var timer = _logger.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) { diff --git a/src/Umbraco.Core/Search/SearchableTreeAttribute.cs b/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs similarity index 98% rename from src/Umbraco.Core/Search/SearchableTreeAttribute.cs rename to src/Umbraco.Core/Trees/SearchableTreeAttribute.cs index d81a85bb4b..2a30725fde 100644 --- a/src/Umbraco.Core/Search/SearchableTreeAttribute.cs +++ b/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Web.Search +namespace Umbraco.Web.Trees { [AttributeUsage(AttributeTargets.Class)] public sealed class SearchableTreeAttribute : Attribute diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7a15e7fbed..c083cccf8f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs b/src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs similarity index 100% rename from src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs rename to src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs diff --git a/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs b/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs new file mode 100644 index 0000000000..74e2dd7380 --- /dev/null +++ b/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs @@ -0,0 +1,19 @@ +using System; +using Umbraco.Web; + +namespace Umbraco.Core +{ + public static class UmbracoContextAccessorExtensions + { + public static IUmbracoContext GetRequiredUmbracoContext(this IUmbracoContextAccessor umbracoContextAccessor) + { + if (umbracoContextAccessor == null) throw new ArgumentNullException(nameof(umbracoContextAccessor)); + + var umbracoContext = umbracoContextAccessor.UmbracoContext; + + if(umbracoContext is null) throw new InvalidOperationException("UmbracoContext is null"); + + return umbracoContext; + } + } +} diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index cf84d7914d..a8aa6f8c91 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Umbraco.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; namespace Umbraco.Core @@ -16,44 +17,42 @@ namespace Umbraco.Core /// Checks if the current uri is a back office request /// /// - /// - /// The current application path or VirtualPath - /// - /// - /// + /// + /// /// /// /// There are some special routes we need to check to properly determine this: - /// + /// /// If any route has an extension in the path like .aspx = back office - /// + /// /// These are def back office: /// /Umbraco/BackOffice = back office /// /Umbraco/Preview = back office /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. - /// + /// /// These are def front-end: /// /Umbraco/Surface = front-end /// /Umbraco/Api = front-end /// But if we've got this far we'll just have to assume it's front-end anyways. - /// + /// /// - internal static bool IsBackOfficeRequest(this Uri url, string applicationPath, IGlobalSettings globalSettings, IIOHelper ioHelper) + internal static bool IsBackOfficeRequest(this Uri url, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { - applicationPath = applicationPath ?? string.Empty; - + var applicationPath = hostingEnvironment.ApplicationVirtualPath; + var fullUrlPath = url.AbsolutePath.TrimStart(new[] {'/'}); var appPath = applicationPath.TrimStart(new[] {'/'}); var urlPath = fullUrlPath.TrimStart(appPath).EnsureStartsWith('/'); //check if this is in the umbraco back office - var isUmbracoPath = urlPath.InvariantStartsWith(globalSettings.Path.EnsureStartsWith('/').TrimStart(appPath.EnsureStartsWith('/')).EnsureStartsWith('/')); + var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment); + var isUmbracoPath = urlPath.InvariantStartsWith(backOfficePath.EnsureStartsWith('/').TrimStart(appPath.EnsureStartsWith('/')).EnsureStartsWith('/')); //if not, then def not back office if (isUmbracoPath == false) return false; - var mvcArea = globalSettings.GetUmbracoMvcArea(ioHelper); + var mvcArea = globalSettings.GetUmbracoMvcArea(hostingEnvironment); //if its the normal /umbraco path if (urlPath.InvariantEquals("/" + mvcArea) || urlPath.InvariantEquals("/" + mvcArea + "/")) @@ -108,9 +107,9 @@ namespace Umbraco.Core /// Checks if the current uri is an install request /// /// - /// + /// /// - internal static bool IsInstallerRequest(this Uri url, IIOHelper ioHelper) + internal static bool IsInstallerRequest(this Uri url, IHostingEnvironment hostingEnvironment) { var authority = url.GetLeftPart(UriPartial.Authority); var afterAuthority = url.GetLeftPart(UriPartial.Query) @@ -118,7 +117,7 @@ namespace Umbraco.Core .TrimStart("/"); //check if this is in the umbraco back office - return afterAuthority.InvariantStartsWith(ioHelper.ResolveUrl("~/install").TrimStart("/")); + return afterAuthority.InvariantStartsWith(hostingEnvironment.ToAbsolute("~/install").TrimStart("/")); } /// @@ -126,13 +125,15 @@ namespace Umbraco.Core /// /// /// + /// /// - internal static bool IsDefaultBackOfficeRequest(this Uri url, IGlobalSettings globalSettings) + internal static bool IsDefaultBackOfficeRequest(this Uri url, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { - if (url.AbsolutePath.InvariantEquals(globalSettings.Path.TrimEnd("/")) - || url.AbsolutePath.InvariantEquals(globalSettings.Path.EnsureEndsWith('/')) - || url.AbsolutePath.InvariantEquals(globalSettings.Path.EnsureEndsWith('/') + "Default") - || url.AbsolutePath.InvariantEquals(globalSettings.Path.EnsureEndsWith('/') + "Default/")) + var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment); + if (url.AbsolutePath.InvariantEquals(backOfficePath.TrimEnd("/")) + || url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/')) + || url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/') + "Default") + || url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/') + "Default/")) { return true; } @@ -146,7 +147,7 @@ namespace Umbraco.Core /// /// /// - internal static bool IsClientSideRequest(this Uri url) + public static bool IsClientSideRequest(this Uri url) { try { diff --git a/src/Umbraco.Core/Cookie/ICookieManager.cs b/src/Umbraco.Core/Web/ICookieManager.cs similarity index 89% rename from src/Umbraco.Core/Cookie/ICookieManager.cs rename to src/Umbraco.Core/Web/ICookieManager.cs index af0ee7b1f6..dc5d3f65c2 100644 --- a/src/Umbraco.Core/Cookie/ICookieManager.cs +++ b/src/Umbraco.Core/Web/ICookieManager.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Cookie +namespace Umbraco.Web { public interface ICookieManager { @@ -7,4 +7,5 @@ namespace Umbraco.Core.Cookie void SetCookieValue(string cookieName, string value); bool HasCookie(string cookieName); } + } diff --git a/src/Umbraco.Core/Web/IRequestAccessor.cs b/src/Umbraco.Core/Web/IRequestAccessor.cs new file mode 100644 index 0000000000..60f8ccbc40 --- /dev/null +++ b/src/Umbraco.Core/Web/IRequestAccessor.cs @@ -0,0 +1,13 @@ +using System; +using Umbraco.Web.Routing; + +namespace Umbraco.Web +{ + public interface IRequestAccessor + { + string GetRequestValue(string name); + string GetQueryStringValue(string culture); + event EventHandler EndRequest; + event EventHandler RouteAttempt; + } +} diff --git a/src/Umbraco.Core/Web/ISessionManager.cs b/src/Umbraco.Core/Web/ISessionManager.cs new file mode 100644 index 0000000000..e7bee5012d --- /dev/null +++ b/src/Umbraco.Core/Web/ISessionManager.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Web +{ + public interface ISessionManager + { + string GetSessionValue(string sessionName); + void SetSessionValue(string sessionName, string value); + } +} diff --git a/src/Umbraco.Core/WebAssets/AssetFile.cs b/src/Umbraco.Core/WebAssets/AssetFile.cs new file mode 100644 index 0000000000..ceb3816633 --- /dev/null +++ b/src/Umbraco.Core/WebAssets/AssetFile.cs @@ -0,0 +1,23 @@ +using System.Diagnostics; + +namespace Umbraco.Core.WebAssets +{ + /// + /// Represents a dependency file + /// + [DebuggerDisplay("Type: {DependencyType}, File: {FilePath}")] + public class AssetFile : IAssetFile + { + #region IAssetFile Members + + public string FilePath { get; set; } + public AssetType DependencyType { get; } + + #endregion + + public AssetFile(AssetType type) + { + DependencyType = type; + } + } +} diff --git a/src/Umbraco.Core/WebAssets/AssetType.cs b/src/Umbraco.Core/WebAssets/AssetType.cs new file mode 100644 index 0000000000..c08e2be6c1 --- /dev/null +++ b/src/Umbraco.Core/WebAssets/AssetType.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.WebAssets +{ + public enum AssetType + { + Javascript, + Css + } +} diff --git a/src/Umbraco.Core/WebAssets/CssFile.cs b/src/Umbraco.Core/WebAssets/CssFile.cs new file mode 100644 index 0000000000..42c677807e --- /dev/null +++ b/src/Umbraco.Core/WebAssets/CssFile.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.WebAssets +{ + /// + /// Represents a CSS asset file + /// + public class CssFile : AssetFile + { + public CssFile(string filePath) + : base(AssetType.Css) + { + FilePath = filePath; + } + } +} diff --git a/src/Umbraco.Core/WebAssets/IAssetFile.cs b/src/Umbraco.Core/WebAssets/IAssetFile.cs new file mode 100644 index 0000000000..721c415ffe --- /dev/null +++ b/src/Umbraco.Core/WebAssets/IAssetFile.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.WebAssets +{ + public interface IAssetFile + { + string FilePath { get; set; } + AssetType DependencyType { get; } + } +} diff --git a/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs new file mode 100644 index 0000000000..03f6b179ff --- /dev/null +++ b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Umbraco.Core.WebAssets +{ + /// + /// Used for bundling and minifying web assets at runtime + /// + public interface IRuntimeMinifier + { + /// + /// Returns the cache buster value + /// + string CacheBuster { get; } + + /// + /// Creates a css bundle + /// + /// + /// + /// + /// All files must be absolute paths, relative paths will throw + /// + /// + /// Thrown if any of the paths specified are not absolute + /// + void CreateCssBundle(string bundleName, params string[] filePaths); + + /// + /// Renders the html link tag for the bundle + /// + /// + /// + /// An html encoded string + /// + string RenderCssHere(string bundleName); + + /// + /// Creates a JS bundle + /// + /// + /// + /// + /// All files must be absolute paths, relative paths will throw + /// + /// + /// Thrown if any of the paths specified are not absolute + /// + void CreateJsBundle(string bundleName, params string[] filePaths); + + /// + /// Renders the html script tag for the bundle + /// + /// + /// + /// An html encoded string + /// + string RenderJsHere(string bundleName); + + /// + /// Returns the asset paths for the bundle name + /// + /// + /// + /// If debug mode is enabled this will return all asset paths (not bundled), else it will return a bundle URL + /// + Task> GetAssetPathsAsync(string bundleName); + + /// + /// Minify the file content, of a given type + /// + /// + /// + /// + Task MinifyAsync(string fileContent, AssetType assetType); + + /// + /// Ensures that all runtime minifications are refreshed on next request. E.g. Clearing cache. + /// + void Reset(); + + } +} diff --git a/src/Umbraco.Core/WebAssets/JavascriptFile.cs b/src/Umbraco.Core/WebAssets/JavascriptFile.cs new file mode 100644 index 0000000000..1f5b1a1f77 --- /dev/null +++ b/src/Umbraco.Core/WebAssets/JavascriptFile.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.WebAssets +{ + /// + /// Represents a JS asset file + /// + public class JavaScriptFile : AssetFile + { + public JavaScriptFile(string filePath) + : base(AssetType.Javascript) + { + FilePath = filePath; + } + } +} diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index 099b73ef36..4b76dc3799 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -336,7 +336,7 @@ namespace Umbraco.Core.Xml var child = parent.SelectSingleNode(name); if (child != null) { - child.InnerXml = ""; ; + child.InnerXml = ""; return child; } return AddCDataNode(xd, name, value); diff --git a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs index d73dc89948..c4e7f264fb 100644 --- a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs +++ b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs @@ -6,8 +6,6 @@ using System.Text.RegularExpressions; using Examine; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.Models.ContentEditing; diff --git a/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs b/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs index ccc0248868..df230bc3da 100644 --- a/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs +++ b/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.IO; using Examine; using Examine.LuceneEngine.Directories; using Lucene.Net.Store; +using Umbraco.Core.Configuration; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; namespace Umbraco.Examine @@ -18,12 +19,14 @@ namespace Umbraco.Examine public abstract class LuceneIndexCreator : IIndexCreator { private readonly ITypeFinder _typeFinder; - private readonly IIOHelper _ioHelper; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIndexCreatorSettings _settings; - protected LuceneIndexCreator(ITypeFinder typeFinder, IIOHelper ioHelper) + protected LuceneIndexCreator(ITypeFinder typeFinder, IHostingEnvironment hostingEnvironment, IIndexCreatorSettings settings) { _typeFinder = typeFinder; - _ioHelper = ioHelper; + _hostingEnvironment = hostingEnvironment; + _settings = settings; } public abstract IEnumerable Create(); @@ -38,12 +41,13 @@ namespace Umbraco.Examine public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string folderName) { - var dirInfo = new DirectoryInfo(Path.Combine(_ioHelper.MapPath(Constants.SystemDirectories.TempData), "ExamineIndexes", folderName)); + var dirInfo = new DirectoryInfo(Path.Combine(_hostingEnvironment.MapPath(Constants.SystemDirectories.TempData), "ExamineIndexes", folderName)); if (!dirInfo.Exists) System.IO.Directory.CreateDirectory(dirInfo.FullName); //check if there's a configured directory factory, if so create it and use that to create the lucene dir - var configuredDirectoryFactory = ConfigurationManager.AppSettings["Umbraco.Examine.LuceneDirectoryFactory"]; + var configuredDirectoryFactory = _settings.LuceneDirectoryFactory; + if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) { //this should be a fully qualified type diff --git a/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs b/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs index 81ec7e3dcb..836d5f6e66 100644 --- a/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs +++ b/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs @@ -6,18 +6,19 @@ using Lucene.Net.Store; using Umbraco.Core.IO; using System.Linq; using Umbraco.Core.Composing; +using Umbraco.Core.Hosting; namespace Umbraco.Examine { public class LuceneIndexDiagnostics : IIndexDiagnostics { - private IIOHelper _ioHelper; + private readonly IHostingEnvironment _hostingEnvironment; - public LuceneIndexDiagnostics(LuceneIndex index, ILogger logger, IIOHelper ioHelper) + public LuceneIndexDiagnostics(LuceneIndex index, ILogger logger, IHostingEnvironment hostingEnvironment) { + _hostingEnvironment = hostingEnvironment; Index = index; Logger = logger; - _ioHelper = ioHelper; } public LuceneIndex Index { get; } @@ -75,7 +76,9 @@ namespace Umbraco.Examine if (luceneDir is FSDirectory fsDir) { - d[nameof(UmbracoExamineIndex.LuceneIndexFolder)] = fsDir.Directory.ToString().ToLowerInvariant().TrimStart(_ioHelper.MapPath(_ioHelper.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'); + + var rootDir = _hostingEnvironment.ApplicationPhysicalPath; + d[nameof(UmbracoExamineIndex.LuceneIndexFolder)] = fsDir.Directory.ToString().ToLowerInvariant().TrimStart(rootDir.ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'); } return d; diff --git a/src/Umbraco.Examine.Lucene/LuceneIndexDiagnosticsFactory.cs b/src/Umbraco.Examine.Lucene/LuceneIndexDiagnosticsFactory.cs index cc1fc115ca..9141f7f6dd 100644 --- a/src/Umbraco.Examine.Lucene/LuceneIndexDiagnosticsFactory.cs +++ b/src/Umbraco.Examine.Lucene/LuceneIndexDiagnosticsFactory.cs @@ -1,5 +1,6 @@ using Examine; using Examine.LuceneEngine.Providers; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.IO; @@ -12,12 +13,12 @@ namespace Umbraco.Examine public class LuceneIndexDiagnosticsFactory : IndexDiagnosticsFactory { private readonly ILogger _logger; - private readonly IIOHelper _ioHelper; + private readonly IHostingEnvironment _hostingEnvironment; - public LuceneIndexDiagnosticsFactory(ILogger logger, IIOHelper ioHelper) + public LuceneIndexDiagnosticsFactory(ILogger logger, IHostingEnvironment hostingEnvironment) { _logger = logger; - _ioHelper = ioHelper; + _hostingEnvironment = hostingEnvironment; } public override IIndexDiagnostics Create(IIndex index) @@ -25,7 +26,7 @@ namespace Umbraco.Examine if (!(index is IIndexDiagnostics indexDiag)) { if (index is LuceneIndex luceneIndex) - indexDiag = new LuceneIndexDiagnostics(luceneIndex, _logger, _ioHelper); + indexDiag = new LuceneIndexDiagnostics(luceneIndex, _logger, _hostingEnvironment); else indexDiag = base.Create(index); } diff --git a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs index 1c8cd4b074..70b44340bd 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs @@ -11,6 +11,7 @@ using Lucene.Net.Store; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Examine.LuceneEngine; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; namespace Umbraco.Examine @@ -33,7 +34,7 @@ namespace Umbraco.Examine /// /// /// - /// + /// /// /// /// @@ -44,12 +45,12 @@ namespace Umbraco.Examine FieldDefinitionCollection fieldDefinitions, Analyzer defaultAnalyzer, IProfilingLogger profilingLogger, - IIOHelper ioHelper, + IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState, ILocalizationService languageService, IContentValueSetValidator validator, IReadOnlyDictionary indexValueTypes = null) - : base(name, luceneDirectory, fieldDefinitions, defaultAnalyzer, profilingLogger, ioHelper, runtimeState, validator, indexValueTypes) + : base(name, luceneDirectory, fieldDefinitions, defaultAnalyzer, profilingLogger, hostingEnvironment, runtimeState, validator, indexValueTypes) { if (validator == null) throw new ArgumentNullException(nameof(validator)); LanguageService = languageService ?? throw new ArgumentNullException(nameof(languageService)); diff --git a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs index 880440f4f9..696c6a58a5 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs @@ -9,6 +9,7 @@ using Umbraco.Core; using Examine; using Examine.LuceneEngine; using Lucene.Net.Store; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Directory = Lucene.Net.Store.Directory; @@ -38,7 +39,7 @@ namespace Umbraco.Examine /// /// /// - /// + /// /// /// /// @@ -48,7 +49,7 @@ namespace Umbraco.Examine FieldDefinitionCollection fieldDefinitions, Analyzer defaultAnalyzer, IProfilingLogger profilingLogger, - IIOHelper ioHelper, + IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState, IValueSetValidator validator = null, IReadOnlyDictionary indexValueTypes = null) @@ -61,7 +62,7 @@ namespace Umbraco.Examine if (luceneDirectory is FSDirectory fsDir) LuceneIndexFolder = fsDir.Directory; - _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger, ioHelper); + _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger, hostingEnvironment); } private readonly bool _configBased = false; diff --git a/src/Umbraco.Examine.Lucene/UmbracoExamineIndexDiagnostics.cs b/src/Umbraco.Examine.Lucene/UmbracoExamineIndexDiagnostics.cs index 2ac8fde960..777b198232 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoExamineIndexDiagnostics.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoExamineIndexDiagnostics.cs @@ -2,6 +2,7 @@ using System.Linq; using Lucene.Net.Store; using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -11,8 +12,8 @@ namespace Umbraco.Examine { private readonly UmbracoExamineIndex _index; - public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger, IIOHelper ioHelper) - : base(index, logger, ioHelper) + public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger, IHostingEnvironment hostingEnvironment) + : base(index, logger, hostingEnvironment) { _index = index; } diff --git a/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs b/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs index 173300a472..3cf7d6d386 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs @@ -4,8 +4,10 @@ using Umbraco.Core.Services; using Lucene.Net.Analysis.Standard; using Examine.LuceneEngine; using Examine; +using Umbraco.Core.Configuration; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; namespace Umbraco.Examine @@ -24,20 +26,21 @@ namespace Umbraco.Examine IPublicAccessService publicAccessService, IMemberService memberService, IUmbracoIndexConfig umbracoIndexConfig, - IIOHelper ioHelper, - IRuntimeState runtimeState) : base(typeFinder, ioHelper) + IHostingEnvironment hostingEnvironment, + IRuntimeState runtimeState, + IIndexCreatorSettings settings) : base(typeFinder, hostingEnvironment, settings) { ProfilingLogger = profilingLogger ?? throw new System.ArgumentNullException(nameof(profilingLogger)); LanguageService = languageService ?? throw new System.ArgumentNullException(nameof(languageService)); PublicAccessService = publicAccessService ?? throw new System.ArgumentNullException(nameof(publicAccessService)); MemberService = memberService ?? throw new System.ArgumentNullException(nameof(memberService)); UmbracoIndexConfig = umbracoIndexConfig; - IOHelper = ioHelper ?? throw new System.ArgumentNullException(nameof(ioHelper)); + HostingEnvironment = hostingEnvironment ?? throw new System.ArgumentNullException(nameof(hostingEnvironment)); RuntimeState = runtimeState ?? throw new System.ArgumentNullException(nameof(runtimeState)); } protected IProfilingLogger ProfilingLogger { get; } - protected IIOHelper IOHelper { get; } + protected IHostingEnvironment HostingEnvironment { get; } protected IRuntimeState RuntimeState { get; } protected ILocalizationService LanguageService { get; } protected IPublicAccessService PublicAccessService { get; } @@ -66,7 +69,7 @@ namespace Umbraco.Examine new UmbracoFieldDefinitionCollection(), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, - IOHelper, + HostingEnvironment, RuntimeState, LanguageService, UmbracoIndexConfig.GetContentValueSetValidator() @@ -82,7 +85,7 @@ namespace Umbraco.Examine new UmbracoFieldDefinitionCollection(), new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), ProfilingLogger, - IOHelper, + HostingEnvironment, RuntimeState, LanguageService, UmbracoIndexConfig.GetPublishedContentValueSetValidator()); @@ -97,7 +100,7 @@ namespace Umbraco.Examine CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.MembersIndexPath), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, - IOHelper, + HostingEnvironment, RuntimeState, UmbracoIndexConfig.GetMemberValueSetValidator() ); diff --git a/src/Umbraco.Examine.Lucene/UmbracoMemberIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoMemberIndex.cs index 0e9128d31a..12678fa00a 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoMemberIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoMemberIndex.cs @@ -1,6 +1,7 @@ using Examine; using Lucene.Net.Analysis; using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Directory = Lucene.Net.Store.Directory; @@ -20,7 +21,8 @@ namespace Umbraco.Examine /// /// /// - /// + /// + /// /// /// public UmbracoMemberIndex( @@ -29,10 +31,10 @@ namespace Umbraco.Examine Directory luceneDirectory, Analyzer analyzer, IProfilingLogger profilingLogger, - IIOHelper ioHelper, + IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState, IValueSetValidator validator = null) : - base(name, luceneDirectory, fieldDefinitions, analyzer, profilingLogger, ioHelper, runtimeState, validator) + base(name, luceneDirectory, fieldDefinitions, analyzer, profilingLogger, hostingEnvironment, runtimeState, validator) { } diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs similarity index 84% rename from src/Umbraco.Web/BatchedDatabaseServerMessenger.cs rename to src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs index a62aad3e5d..fa76fb6f11 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs @@ -1,21 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Web; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; using Umbraco.Core.Sync; using Umbraco.Web.Routing; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Scoping; -using Umbraco.Web.Composing; -using System.ComponentModel; using Umbraco.Core.Hosting; -using Umbraco.Core.IO; namespace Umbraco.Web { @@ -29,19 +24,31 @@ namespace Umbraco.Web { private readonly IUmbracoDatabaseFactory _databaseFactory; private readonly IRequestCache _requestCache; + private readonly IRequestAccessor _requestAccessor; public BatchedDatabaseServerMessenger( - IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, DatabaseServerMessengerOptions options, IHostingEnvironment hostingEnvironment, CacheRefresherCollection cacheRefreshers, IRequestCache requestCache) - : base(runtime, scopeProvider, sqlContext, proflog, true, options, hostingEnvironment, cacheRefreshers) + IMainDom mainDom, + IUmbracoDatabaseFactory databaseFactory, + IScopeProvider scopeProvider, + ISqlContext sqlContext, + IProfilingLogger proflog, + IServerRegistrar serverRegistrar, + DatabaseServerMessengerOptions options, + IHostingEnvironment hostingEnvironment, + CacheRefresherCollection cacheRefreshers, + IRequestCache requestCache, + IRequestAccessor requestAccessor) + : base(mainDom, scopeProvider, sqlContext, proflog, serverRegistrar, true, options, hostingEnvironment, cacheRefreshers) { _databaseFactory = databaseFactory; _requestCache = requestCache; + _requestAccessor = requestAccessor; } // invoked by DatabaseServerRegistrarAndMessengerComponent - internal void Startup() + public void Startup() { - UmbracoModule.EndRequest += UmbracoModule_EndRequest; + _requestAccessor.EndRequest += UmbracoModule_EndRequest; if (_databaseFactory.CanConnect == false) { @@ -104,7 +111,7 @@ namespace Umbraco.Web protected ICollection GetBatch(bool create) { - var key = typeof (BatchedDatabaseServerMessenger).Name; + var key = nameof(BatchedDatabaseServerMessenger); if (!_requestCache.IsAvailable) return null; diff --git a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs similarity index 91% rename from src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs rename to src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs index 1fa455ed8a..fdc672c534 100644 --- a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs @@ -82,7 +82,7 @@ namespace Umbraco.Web.Compose { private object _locker = new object(); private readonly DatabaseServerRegistrar _registrar; - private readonly BatchedDatabaseServerMessenger _messenger; + private readonly IBatchedDatabaseServerMessenger _messenger; private readonly IRuntimeState _runtime; private readonly ILogger _logger; private readonly IServerRegistrationService _registrationService; @@ -90,14 +90,21 @@ namespace Umbraco.Web.Compose private readonly BackgroundTaskRunner _processTaskRunner; private bool _started; private IBackgroundTask[] _tasks; - private IndexRebuilder _indexRebuilder; + private readonly IRequestAccessor _requestAccessor; - public DatabaseServerRegistrarAndMessengerComponent(IRuntimeState runtime, IServerRegistrar serverRegistrar, IServerMessenger serverMessenger, IServerRegistrationService registrationService, ILogger logger, IHostingEnvironment hostingEnvironment, IndexRebuilder indexRebuilder) + public DatabaseServerRegistrarAndMessengerComponent( + IRuntimeState runtime, + IServerRegistrar serverRegistrar, + IServerMessenger serverMessenger, + IServerRegistrationService registrationService, + ILogger logger, + IApplicationShutdownRegistry hostingEnvironment, + IRequestAccessor requestAccessor) { _runtime = runtime; _logger = logger; _registrationService = registrationService; - _indexRebuilder = indexRebuilder; + _requestAccessor = requestAccessor; // create task runner for DatabaseServerRegistrar _registrar = serverRegistrar as DatabaseServerRegistrar; @@ -108,7 +115,7 @@ namespace Umbraco.Web.Compose } // create task runner for BatchedDatabaseServerMessenger - _messenger = serverMessenger as BatchedDatabaseServerMessenger; + _messenger = serverMessenger as IBatchedDatabaseServerMessenger; if (_messenger != null) { _processTaskRunner = new BackgroundTaskRunner("ServerInstProcess", @@ -120,7 +127,7 @@ namespace Umbraco.Web.Compose { //We will start the whole process when a successful request is made if (_registrar != null || _messenger != null) - UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce; + _requestAccessor.RouteAttempt += RegisterBackgroundTasksOnce; // must come last, as it references some _variables _messenger?.Startup(); @@ -137,7 +144,7 @@ namespace Umbraco.Web.Compose /// /// We require this because: /// - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest - /// - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest + /// - RegisterServer is called on _requestAccessor.RouteAttempt which is triggered in ProcessRequest /// we are safe, UmbracoApplicationUrl has been initialized /// private void RegisterBackgroundTasksOnce(object sender, RoutableAttemptEventArgs e) @@ -146,7 +153,7 @@ namespace Umbraco.Web.Compose { case EnsureRoutableOutcome.IsRoutable: case EnsureRoutableOutcome.NotDocumentRequest: - UmbracoModule.RouteAttempt -= RegisterBackgroundTasksOnce; + _requestAccessor.RouteAttempt -= RegisterBackgroundTasksOnce; RegisterBackgroundTasks(); break; } @@ -196,11 +203,11 @@ namespace Umbraco.Web.Compose private class InstructionProcessTask : RecurringTaskBase { - private readonly DatabaseServerMessenger _messenger; + private readonly IDatabaseServerMessenger _messenger; private readonly ILogger _logger; public InstructionProcessTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - DatabaseServerMessenger messenger, ILogger logger) + IDatabaseServerMessenger messenger, ILogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { _messenger = messenger; diff --git a/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs b/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs index 334183a48c..a2ee650595 100644 --- a/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs @@ -4,31 +4,32 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; +using Umbraco.Net; namespace Umbraco.Core.Compose { public sealed class ManifestWatcherComponent : IComponent { - private readonly IRuntimeState _runtimeState; + private readonly IHostingEnvironment _hosting; private readonly ILogger _logger; private readonly IIOHelper _ioHelper; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; // if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for // package.manifest chances and restarts the application on any change private ManifestWatcher _mw; - public ManifestWatcherComponent(IRuntimeState runtimeState, ILogger logger, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment) + public ManifestWatcherComponent(IHostingEnvironment hosting, ILogger logger, IIOHelper ioHelper, IUmbracoApplicationLifetime umbracoApplicationLifetime) { - _runtimeState = runtimeState; + _hosting = hosting; _logger = logger; _ioHelper = ioHelper; - _hostingEnvironment = hostingEnvironment; + _umbracoApplicationLifetime = umbracoApplicationLifetime; } public void Initialize() { - if (_runtimeState.Debug == false) return; + if (_hosting.IsDebugMode == false) return; //if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false) // return; @@ -36,7 +37,7 @@ namespace Umbraco.Core.Compose var appPlugins = _ioHelper.MapPath("~/App_Plugins/"); if (Directory.Exists(appPlugins) == false) return; - _mw = new ManifestWatcher(_logger, _hostingEnvironment); + _mw = new ManifestWatcher(_logger, _umbracoApplicationLifetime); _mw.Start(Directory.GetDirectories(appPlugins)); } diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Configuration.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Configuration.cs deleted file mode 100644 index 7169b93cb4..0000000000 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Configuration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core.Configuration.UmbracoSettings; - -namespace Umbraco.Core.Composing.CompositionExtensions -{ - /// - /// Compose configurations. - /// - internal static class Configuration - { - public static Composition ComposeConfiguration(this Composition composition) - { - // common configurations are already registered - // register others - - composition.RegisterUnique(factory => factory.GetInstance().Content); - composition.RegisterUnique(factory => factory.GetInstance().RequestHandler); - composition.RegisterUnique(factory => factory.GetInstance().Security); - - return composition; - } - } -} 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/Composing/CompositionExtensions/Services.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Services.cs index a41015c4e8..2ab56308ef 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Services.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Services.cs @@ -69,11 +69,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions composition.RegisterUnique(factory => CreatePackageRepository(factory, "installedPackages.config")); composition.RegisterUnique(); composition.RegisterUnique(); - composition.RegisterUnique(factory => //factory required because we need to pass in a string path - new PackageInstallation( - factory.GetInstance(), factory.GetInstance(), - factory.GetInstance(), factory.GetInstance(), - new DirectoryInfo( factory.GetInstance().GetRootDirectorySafe()))); + composition.RegisterUnique(); return composition; } diff --git a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs new file mode 100644 index 0000000000..2099778185 --- /dev/null +++ b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Hosting; + +namespace Umbraco.Core.Composing +{ + /// + /// Extends the to enable Umbraco to be used as the service container. + /// + public static class HostBuilderExtensions + { + /// + /// Assigns a custom service provider factory to use Umbraco's container + /// + /// + /// + public static IHostBuilder UseUmbraco(this IHostBuilder builder) + => builder.UseUmbraco(new UmbracoServiceProviderFactory()); + + public static IHostBuilder UseUmbraco(this IHostBuilder builder, UmbracoServiceProviderFactory umbracoServiceProviderFactory) + => builder.UseServiceProviderFactory(umbracoServiceProviderFactory); + } +} diff --git a/src/Umbraco.Infrastructure/Composing/LightInject/LightInjectContainer.cs b/src/Umbraco.Infrastructure/Composing/LightInject/LightInjectContainer.cs index 5200dced90..a6db0b7b2b 100644 --- a/src/Umbraco.Infrastructure/Composing/LightInject/LightInjectContainer.cs +++ b/src/Umbraco.Infrastructure/Composing/LightInject/LightInjectContainer.cs @@ -16,11 +16,13 @@ namespace Umbraco.Core.Composing.LightInject /// /// Initializes a new instance of the with a LightInject container. /// - protected LightInjectContainer(ServiceContainer container) + public LightInjectContainer(ServiceContainer container) { - Container = container; + Container = ConfigureContainer(container); } + //TODO: The Create methods can die when net framework is gone + /// /// Creates a new instance of the class. /// @@ -33,7 +35,12 @@ namespace Umbraco.Core.Composing.LightInject protected static ServiceContainer CreateServiceContainer() { var container = new ServiceContainer(new ContainerOptions { EnablePropertyInjection = false }); + ConfigureContainer(container); + return container; + } + private static ServiceContainer ConfigureContainer(ServiceContainer container) + { // note: the block below is disabled, as it is too LightInject-specific // // supports annotated constructor injections @@ -84,7 +91,7 @@ namespace Umbraco.Core.Composing.LightInject /// /// Gets the LightInject container. /// - protected ServiceContainer Container { get; } + public ServiceContainer Container { get; } /// /// diff --git a/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs b/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs index 8f842e14fe..835bd0b9a8 100644 --- a/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs +++ b/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs @@ -1,14 +1,22 @@ -using System; +using LightInject; +using LightInject.Microsoft.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; using System.Reflection; +using Umbraco.Core.Composing.LightInject; using Umbraco.Core.Configuration; namespace Umbraco.Core.Composing { + /// /// Creates the container. /// public static class RegisterFactory { + //TODO: This can die when net framework is gone + // cannot use typeof().AssemblyQualifiedName on the web container - we don't reference it // a normal Umbraco site should run on the web container, but an app may run on the core one private const string CoreLightInjectContainerTypeName = "Umbraco.Core.Composing.LightInject.LightInjectContainer,Umbraco.Core"; diff --git a/src/Umbraco.Infrastructure/Composing/UmbracoServiceProviderFactory.cs b/src/Umbraco.Infrastructure/Composing/UmbracoServiceProviderFactory.cs new file mode 100644 index 0000000000..6fd0bda61e --- /dev/null +++ b/src/Umbraco.Infrastructure/Composing/UmbracoServiceProviderFactory.cs @@ -0,0 +1,78 @@ +using LightInject; +using LightInject.Microsoft.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using Umbraco.Core.Composing.LightInject; + +namespace Umbraco.Core.Composing +{ + /// + /// Used to create Umbraco's container and cross-wire it up before the applicaton starts + /// + public class UmbracoServiceProviderFactory : IServiceProviderFactory + { + public UmbracoServiceProviderFactory(ServiceContainer container) + { + _container = new LightInjectContainer(container); + } + + /// + /// Creates an ASP.NET Core compatible service container + /// + /// + public static ServiceContainer CreateServiceContainer() => new ServiceContainer(ContainerOptions.Default.Clone().WithMicrosoftSettings().WithAspNetCoreSettings()); + + /// + /// Default ctor for use in Host Builder configuration + /// + public UmbracoServiceProviderFactory() + { + var container = CreateServiceContainer(); + UmbracoContainer = _container = new LightInjectContainer(container); + IsActive = true; + } + + // see here for orig lightinject version https://github.com/seesharper/LightInject.Microsoft.DependencyInjection/blob/412566e3f70625e6b96471db5e1f7cd9e3e1eb18/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.cs#L263 + // we don't really need all that, we're manually creating our container with the correct options and that + // is what we'll return in CreateBuilder + + IServiceCollection _services; + readonly LightInjectContainer _container; + + internal LightInjectContainer GetContainer() => _container; + + /// + /// When the empty ctor is used this returns if this factory is active + /// + public static bool IsActive { get; private set; } + + /// + /// When the empty ctor is used this returns the created IRegister + /// + public static IRegister UmbracoContainer { get; private set; } + + /// + /// Create the container with the required settings for aspnetcore3 + /// + /// + /// + public IServiceContainer CreateBuilder(IServiceCollection services) + { + _services = services; + return _container.Container; + } + + /// + /// This cross-wires the container just before the application calls "Configure" + /// + /// + /// + public IServiceProvider CreateServiceProvider(IServiceContainer containerBuilder) + { + var provider = containerBuilder.CreateServiceProvider(_services); + return provider; + } + + } +} diff --git a/src/Umbraco.Infrastructure/CompositionExtensions.cs b/src/Umbraco.Infrastructure/CompositionExtensions.cs index bd221e6fd3..d55effcd7a 100644 --- a/src/Umbraco.Infrastructure/CompositionExtensions.cs +++ b/src/Umbraco.Infrastructure/CompositionExtensions.cs @@ -11,6 +11,8 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; using Umbraco.Core.Sync; +using Umbraco.Web.Media.EmbedProviders; +using Umbraco.Web.Search; namespace Umbraco.Core { @@ -84,7 +86,20 @@ namespace Umbraco.Core public static ManifestFilterCollectionBuilder ManifestFilters(this Composition composition) => composition.WithCollectionBuilder(); - + /// + /// Gets the backoffice OEmbed Providers collection builder. + /// + /// The composition. + public static EmbedProvidersCollectionBuilder OEmbedProviders(this Composition composition) + => composition.WithCollectionBuilder(); + + /// + /// Gets the back office searchable tree collection builder + /// + /// + /// + public static SearchableTreeCollectionBuilder SearchableTrees(this Composition composition) + => composition.WithCollectionBuilder(); #endregion @@ -98,7 +113,7 @@ namespace Umbraco.Core public static void SetCultureDictionaryFactory(this Composition composition) where T : ICultureDictionaryFactory { - composition.RegisterUnique(); + composition.RegisterUnique(); } /// diff --git a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs index eb74d37590..02e5a6f5ee 100644 --- a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs +++ b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs @@ -1,6 +1,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -15,6 +16,9 @@ namespace Umbraco.Core /// /// Registers essential services. /// + /// + /// These services are all either created by the runtime or used to construct the runtime + /// public static void RegisterEssentials(this Composition composition, ILogger logger, IProfiler profiler, IProfilingLogger profilingLogger, IMainDom mainDom, @@ -25,8 +29,10 @@ namespace Umbraco.Core ITypeFinder typeFinder, IIOHelper ioHelper, IUmbracoVersion umbracoVersion, - IDbProviderFactoryCreator dbProviderFactoryCreator) - { + IDbProviderFactoryCreator dbProviderFactoryCreator, + IHostingEnvironment hostingEnvironment, + IBackOfficeInfo backOfficeInfo) + { composition.RegisterUnique(logger); composition.RegisterUnique(profiler); composition.RegisterUnique(profilingLogger); @@ -42,6 +48,8 @@ namespace Umbraco.Core composition.RegisterUnique(umbracoVersion); composition.RegisterUnique(dbProviderFactoryCreator); composition.RegisterUnique(factory => factory.GetInstance().BulkSqlInsertProvider); + composition.RegisterUnique(hostingEnvironment); + composition.RegisterUnique(backOfficeInfo); } } } diff --git a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs new file mode 100644 index 0000000000..646df5124d --- /dev/null +++ b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs @@ -0,0 +1,174 @@ +using System; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.FileProviders; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +namespace Umbraco.Core.Configuration +{ + public class JsonConfigManipulator : IConfigManipulator + { + private readonly IConfiguration _configuration; + + public JsonConfigManipulator(IConfiguration configuration) + { + _configuration = configuration; + } + + public string UmbracoConnectionPath { get; } = $"ConnectionStrings:{ Constants.System.UmbracoConnectionName}"; + public void RemoveConnectionString() + { + var provider = GetJsonConfigurationProvider(UmbracoConnectionPath); + + var json = GetJson(provider); + + RemoveJsonKey(json, UmbracoConnectionPath); + + SaveJson(provider, json); + } + + public void SaveConnectionString(string connectionString, string providerName) + { + var provider = GetJsonConfigurationProvider(); + + var json = GetJson(provider); + + var item = GetConnectionItem(connectionString, providerName); + + json.Merge(item, new JsonMergeSettings()); + + SaveJson(provider, json); + } + + public void SaveConfigValue(string key, object value) + { + var provider = GetJsonConfigurationProvider(); + + var json = GetJson(provider); + + JToken token = json; + foreach (var propertyName in key.Split(new[] { ':' })) + { + if (token is null) break; + token = CaseSelectPropertyValues(token, propertyName); + } + + if (token is null) return; + + var writer = new JTokenWriter(); + writer.WriteValue(value); + + token.Replace(writer.Token); + + SaveJson(provider, json); + + } + + private JToken GetConnectionItem(string connectionString, string providerName) + { + JTokenWriter writer = new JTokenWriter(); + + writer.WriteStartObject(); + writer.WritePropertyName("ConnectionStrings"); + writer.WriteStartObject(); + writer.WritePropertyName(Constants.System.UmbracoConnectionName); + writer.WriteValue(connectionString); + writer.WriteEndObject(); + writer.WriteEndObject(); + + return writer.Token; + } + + private static void RemoveJsonKey(JObject json, string key) + { + JToken token = json; + foreach (var propertyName in key.Split(new[] { ':' })) + { + token = CaseSelectPropertyValues(token, propertyName); + } + + token?.Parent?.Remove(); + } + + private static void SaveJson(JsonConfigurationProvider provider, JObject json) + { + if (provider.Source.FileProvider is PhysicalFileProvider physicalFileProvider) + { + var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path); + + using (var sw = new StreamWriter(jsonFilePath, false)) + using (var jsonTextWriter = new JsonTextWriter(sw) + { + Formatting = Formatting.Indented, + }) + { + json?.WriteTo(jsonTextWriter); + } + } + + } + + private static JObject GetJson(JsonConfigurationProvider provider) + { + if (provider.Source.FileProvider is PhysicalFileProvider physicalFileProvider) + { + var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path); + + var serializer = new JsonSerializer(); + using (var sr = new StreamReader(jsonFilePath)) + using (var jsonTextReader = new JsonTextReader(sr)) + { + return serializer.Deserialize(jsonTextReader); + } + } + + return null; + } + + + + + private JsonConfigurationProvider GetJsonConfigurationProvider(string requiredKey = null) + { + if (_configuration is IConfigurationRoot configurationRoot) + { + foreach (var provider in configurationRoot.Providers) + { + if(provider is JsonConfigurationProvider jsonConfigurationProvider) + { + if (requiredKey is null || provider.TryGet(requiredKey, out _)) + { + return jsonConfigurationProvider; + } + } + } + } + throw new InvalidOperationException("Could not find a writable json config source"); + } + + /// + /// Returns the property value when case insensative + /// + /// + /// This method is required because keys are case insensative in IConfiguration. + /// JObject[..] do not support case insensative and JObject.Property(...) do not return a new JObject. + /// + private static JToken CaseSelectPropertyValues(JToken token, string name) + { + if (token is JObject obj) + { + + foreach (var property in obj.Properties()) + { + if (name is null) + return property.Value; + if (string.Equals(property.Name, name, StringComparison.OrdinalIgnoreCase)) + return property.Value; + } + } + return null; + } + + } +} diff --git a/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index b27113a0f1..dcd539c4a2 100644 --- a/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -18,11 +18,11 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods private readonly IRuntimeState _runtimeState; private readonly ILogger _logger; private readonly IGlobalSettings _globalSettings; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; + private readonly IContentSettings _contentSettings; - public EmailNotificationMethod(ILocalizedTextService textService, IRuntimeState runtimeState, ILogger logger, IGlobalSettings globalSettings, IHealthChecks healthChecks, IUmbracoSettingsSection umbracoSettingsSection) : base(healthChecks) + 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; @@ -35,7 +35,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods _runtimeState = runtimeState; _logger = logger; _globalSettings = globalSettings; - _umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection)); + _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); } public string RecipientEmail { get; } @@ -74,7 +74,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods private MailMessage CreateMailMessage(string subject, string message) { - var to = _umbracoSettingsSection.Content.NotificationEmailAddress; + var to = _contentSettings.NotificationEmailAddress; if (string.IsNullOrWhiteSpace(subject)) subject = "Umbraco Health Check Status"; diff --git a/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/NotificationMethodBase.cs b/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/NotificationMethodBase.cs index ff6fbe2371..9c3516e712 100644 --- a/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/NotificationMethodBase.cs +++ b/src/Umbraco.Infrastructure/HealthCheck/NotificationMethods/NotificationMethodBase.cs @@ -8,7 +8,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods { public abstract class NotificationMethodBase : IHealthCheckNotificationMethod { - protected NotificationMethodBase(IHealthChecks healthCheckConfig) + protected NotificationMethodBase(IHealthChecksSettings healthCheckSettingsConfig) { var type = GetType(); var attribute = type.GetCustomAttribute(); @@ -18,7 +18,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods return; } - var notificationMethods = healthCheckConfig.NotificationSettings.NotificationMethods; + var notificationMethods = healthCheckSettingsConfig.NotificationSettings.NotificationMethods; var notificationMethod = notificationMethods[attribute.Alias]; if (notificationMethod == null) { diff --git a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs index 6a1621b229..c5f49c3e0b 100644 --- a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs @@ -13,12 +13,18 @@ namespace Umbraco.Web /// public interface IPublishedContentQuery { + + + IPublishedContent Content(int id); IPublishedContent Content(Guid id); IPublishedContent Content(Udi id); + IPublishedContent Content(object id); IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars); IEnumerable Content(IEnumerable ids); IEnumerable Content(IEnumerable ids); + + IEnumerable Content(IEnumerable ids); IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars); IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); IEnumerable ContentAtRoot(); @@ -26,7 +32,10 @@ namespace Umbraco.Web IPublishedContent Media(int id); IPublishedContent Media(Guid id); IPublishedContent Media(Udi id); + + IPublishedContent Media(object id); IEnumerable Media(IEnumerable ids); + IEnumerable Media(IEnumerable ids); IEnumerable Media(IEnumerable ids); IEnumerable MediaAtRoot(); diff --git a/src/Umbraco.Web/Install/InstallHelper.cs b/src/Umbraco.Infrastructure/Intall/InstallHelper.cs similarity index 80% rename from src/Umbraco.Web/Install/InstallHelper.cs rename to src/Umbraco.Infrastructure/Intall/InstallHelper.cs index f7344d0b0b..3825768d97 100644 --- a/src/Umbraco.Web/Install/InstallHelper.cs +++ b/src/Umbraco.Infrastructure/Intall/InstallHelper.cs @@ -3,17 +3,15 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Cookie; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Models; +using Umbraco.Net; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Serialization; using Umbraco.Core.Services; -using Umbraco.Web.Composing; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install @@ -22,25 +20,28 @@ namespace Umbraco.Web.Install { private static HttpClient _httpClient; private readonly DatabaseBuilder _databaseBuilder; - private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; private readonly IGlobalSettings _globalSettings; private readonly IUmbracoVersion _umbracoVersion; private readonly IConnectionStrings _connectionStrings; private readonly IInstallationService _installationService; private readonly ICookieManager _cookieManager; + private readonly IUserAgentProvider _userAgentProvider; + private readonly IUmbracoDatabaseFactory _umbracoDatabaseFactory; + private readonly IJsonSerializer _jsonSerializer; private InstallationType? _installationType; - public InstallHelper(IHttpContextAccessor httpContextAccessor, - DatabaseBuilder databaseBuilder, + public InstallHelper(DatabaseBuilder databaseBuilder, ILogger logger, IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion, IConnectionStrings connectionStrings, IInstallationService installationService, - ICookieManager cookieManager) + ICookieManager cookieManager, + IUserAgentProvider userAgentProvider, + IUmbracoDatabaseFactory umbracoDatabaseFactory, + IJsonSerializer jsonSerializer) { - _httpContextAccessor = httpContextAccessor; _logger = logger; _globalSettings = globalSettings; _umbracoVersion = umbracoVersion; @@ -48,6 +49,9 @@ namespace Umbraco.Web.Install _connectionStrings = connectionStrings ?? throw new ArgumentNullException(nameof(connectionStrings)); _installationService = installationService; _cookieManager = cookieManager; + _userAgentProvider = userAgentProvider; + _umbracoDatabaseFactory = umbracoDatabaseFactory; + _jsonSerializer = jsonSerializer; } public InstallationType GetInstallationType() @@ -57,11 +61,9 @@ namespace Umbraco.Web.Install public async Task InstallStatus(bool isCompleted, string errorMsg) { - - var httpContext = _httpContextAccessor.GetRequiredHttpContext(); try { - var userAgent = httpContext.Request.UserAgent; + var userAgent = _userAgentProvider.GetUserAgent(); // Check for current install Id var installId = Guid.NewGuid(); @@ -88,7 +90,7 @@ namespace Umbraco.Web.Install { // we don't have DatabaseProvider anymore... doing it differently //dbProvider = ApplicationContext.Current.DatabaseContext.DatabaseProvider.ToString(); - dbProvider = GetDbProviderString(Current.SqlContext); + dbProvider = _umbracoDatabaseFactory.SqlContext.SqlSyntax.DbProvider; } var installLog = new InstallLog(installId: installId, isUpgrade: IsBrandNewInstall == false, @@ -105,23 +107,6 @@ namespace Umbraco.Web.Install } } - internal static string GetDbProviderString(ISqlContext sqlContext) - { - var dbProvider = string.Empty; - - // we don't have DatabaseProvider anymore... - //dbProvider = ApplicationContext.Current.DatabaseContext.DatabaseProvider.ToString(); - // - // doing it differently - var syntax = sqlContext.SqlSyntax; - if (syntax is SqlCeSyntaxProvider) - dbProvider = "SqlServerCE"; - else if (syntax is SqlServerSyntaxProvider) - dbProvider = (syntax as SqlServerSyntaxProvider).ServerVersion.IsAzure ? "SqlAzure" : "SqlServer"; - - return dbProvider; - } - /// /// Checks if this is a brand new install meaning that there is no configured version and there is no configured database connection /// @@ -162,7 +147,10 @@ namespace Umbraco.Web.Install using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri)) { var response = _httpClient.SendAsync(request).Result; - packages = response.Content.ReadAsAsync>().Result.ToList(); + + + var json = response.Content.ReadAsStringAsync().Result; + packages = _jsonSerializer.Deserialize>(json).ToList(); } } catch (AggregateException ex) diff --git a/src/Umbraco.Infrastructure/Intall/InstallSteps/DatabaseInstallStep.cs b/src/Umbraco.Infrastructure/Intall/InstallSteps/DatabaseInstallStep.cs index a7b3dcc218..865df4bc02 100644 --- a/src/Umbraco.Infrastructure/Intall/InstallSteps/DatabaseInstallStep.cs +++ b/src/Umbraco.Infrastructure/Intall/InstallSteps/DatabaseInstallStep.cs @@ -19,14 +19,16 @@ namespace Umbraco.Web.Install.InstallSteps private readonly ILogger _logger; private readonly IIOHelper _ioHelper; private readonly IConnectionStrings _connectionStrings; + private readonly IConfigManipulator _configManipulator; - public DatabaseInstallStep(DatabaseBuilder databaseBuilder, IRuntimeState runtime, ILogger logger, IIOHelper ioHelper, IConnectionStrings connectionStrings) + public DatabaseInstallStep(DatabaseBuilder databaseBuilder, IRuntimeState runtime, ILogger logger, IIOHelper ioHelper, IConnectionStrings connectionStrings, IConfigManipulator configManipulator) { _databaseBuilder = databaseBuilder; _runtime = runtime; _logger = logger; _ioHelper = ioHelper; _connectionStrings = connectionStrings; + _configManipulator = configManipulator; } public override Task ExecuteAsync(object model) @@ -43,7 +45,7 @@ namespace Umbraco.Web.Install.InstallSteps if (result.RequiresUpgrade == false) { - HandleConnectionStrings(_logger, _ioHelper, _connectionStrings); + HandleConnectionStrings(_logger, _ioHelper, _connectionStrings, _configManipulator); return Task.FromResult(null); } @@ -54,7 +56,7 @@ namespace Umbraco.Web.Install.InstallSteps })); } - internal static void HandleConnectionStrings(ILogger logger, IIOHelper ioHelper, IConnectionStrings connectionStrings) + internal static void HandleConnectionStrings(ILogger logger, IIOHelper ioHelper, IConnectionStrings connectionStrings, IConfigManipulator configManipulator) { @@ -65,7 +67,7 @@ namespace Umbraco.Web.Install.InstallSteps // Remove legacy umbracoDbDsn configuration setting if it exists and connectionstring also exists if (databaseSettings != null) { - connectionStrings.RemoveConnectionString(Constants.System.UmbracoConnectionName); + configManipulator.RemoveConnectionString(); } else { diff --git a/src/Umbraco.Infrastructure/Intall/InstallSteps/DatabaseUpgradeStep.cs b/src/Umbraco.Infrastructure/Intall/InstallSteps/DatabaseUpgradeStep.cs index 3cd3c1ca56..91a718d0f4 100644 --- a/src/Umbraco.Infrastructure/Intall/InstallSteps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Infrastructure/Intall/InstallSteps/DatabaseUpgradeStep.cs @@ -23,8 +23,17 @@ namespace Umbraco.Web.Install.InstallSteps private readonly IGlobalSettings _globalSettings; private readonly IConnectionStrings _connectionStrings; private readonly IIOHelper _ioHelper; + private readonly IConfigManipulator _configManipulator; - public DatabaseUpgradeStep(DatabaseBuilder databaseBuilder, IRuntimeState runtime, ILogger logger, IUmbracoVersion umbracoVersion, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, IIOHelper ioHelper) + public DatabaseUpgradeStep( + DatabaseBuilder databaseBuilder, + IRuntimeState runtime, + ILogger logger, + IUmbracoVersion umbracoVersion, + IGlobalSettings globalSettings, + IConnectionStrings connectionStrings, + IIOHelper ioHelper, + IConfigManipulator configManipulator) { _databaseBuilder = databaseBuilder; _runtime = runtime; @@ -33,6 +42,7 @@ namespace Umbraco.Web.Install.InstallSteps _globalSettings = globalSettings; _connectionStrings = connectionStrings ?? throw new ArgumentNullException(nameof(connectionStrings)); _ioHelper = ioHelper; + _configManipulator = configManipulator; } public override Task ExecuteAsync(object model) @@ -55,7 +65,7 @@ namespace Umbraco.Web.Install.InstallSteps throw new InstallException("The database failed to upgrade. ERROR: " + result.Message); } - DatabaseInstallStep.HandleConnectionStrings(_logger, _ioHelper, _connectionStrings); + DatabaseInstallStep.HandleConnectionStrings(_logger, _ioHelper, _connectionStrings, _configManipulator); } return Task.FromResult(null); diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Infrastructure/Intall/InstallSteps/SetUmbracoVersionStep.cs similarity index 69% rename from src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs rename to src/Umbraco.Infrastructure/Intall/InstallSteps/SetUmbracoVersionStep.cs index dcf115082f..17043f18a5 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Infrastructure/Intall/InstallSteps/SetUmbracoVersionStep.cs @@ -1,44 +1,31 @@ using System.Threading.Tasks; -using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Services; -using Umbraco.Web.Cache; -using Umbraco.Web.Composing; using Umbraco.Web.Install.Models; -using Umbraco.Web.Security; - namespace Umbraco.Web.Install.InstallSteps { [InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade, - "UmbracoVersion", 50, "Installation is complete!, get ready to be redirected to your new CMS.", + "UmbracoVersion", 50, "Installation is complete! Get ready to be redirected to your new CMS.", PerformsAppRestart = true)] - internal class SetUmbracoVersionStep : InstallSetupStep + public class SetUmbracoVersionStep : InstallSetupStep { - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly InstallHelper _installHelper; private readonly IGlobalSettings _globalSettings; - private readonly IUserService _userService; private readonly IUmbracoVersion _umbracoVersion; - private readonly IIOHelper _ioHelper; - public SetUmbracoVersionStep(IHttpContextAccessor httpContextAccessor, InstallHelper installHelper, IGlobalSettings globalSettings, IUserService userService, IUmbracoVersion umbracoVersion, IIOHelper ioHelper) + public SetUmbracoVersionStep(IUmbracoContextAccessor umbracoContextAccessor, InstallHelper installHelper, IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion) { - _httpContextAccessor = httpContextAccessor; + _umbracoContextAccessor = umbracoContextAccessor; _installHelper = installHelper; _globalSettings = globalSettings; - _userService = userService; _umbracoVersion = umbracoVersion; - _ioHelper = ioHelper; } public override Task ExecuteAsync(object model) { - var security = new WebSecurity(_httpContextAccessor, _userService, _globalSettings, _ioHelper); - + var security = _umbracoContextAccessor.GetRequiredUmbracoContext().Security; if (security.IsAuthenticated() == false && _globalSettings.ConfigurationStatus.IsNullOrWhiteSpace()) { security.PerformLogin(-1); diff --git a/src/Umbraco.Web/Install/InstallSteps/StarterKitDownloadStep.cs b/src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitDownloadStep.cs similarity index 93% rename from src/Umbraco.Web/Install/InstallSteps/StarterKitDownloadStep.cs rename to src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitDownloadStep.cs index 624f5897c7..d8986cacb7 100644 --- a/src/Umbraco.Web/Install/InstallSteps/StarterKitDownloadStep.cs +++ b/src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitDownloadStep.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; -using System.Web; using Umbraco.Core.Services; using Umbraco.Core.Configuration; using Umbraco.Core.Models.Packaging; -using Umbraco.Web.Composing; +using Umbraco.Net; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps @@ -20,14 +18,16 @@ namespace Umbraco.Web.Install.InstallSteps private readonly InstallHelper _installHelper; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IUmbracoVersion _umbracoVersion; + private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly IContentService _contentService; private readonly IPackagingService _packageService; - public StarterKitDownloadStep(IContentService contentService, IPackagingService packageService, InstallHelper installHelper, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoVersion umbracoVersion) + public StarterKitDownloadStep(IContentService contentService, IPackagingService packageService, InstallHelper installHelper, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoVersion umbracoVersion, IUmbracoApplicationLifetime umbracoApplicationLifetime) { _installHelper = installHelper; _umbracoContextAccessor = umbracoContextAccessor; _umbracoVersion = umbracoVersion; + _umbracoApplicationLifetime = umbracoApplicationLifetime; _contentService = contentService; _packageService = packageService; } @@ -54,7 +54,7 @@ namespace Umbraco.Web.Install.InstallSteps var (packageFile, packageId) = await DownloadPackageFilesAsync(starterKitId.Value); - UmbracoApplication.Restart(); + _umbracoApplicationLifetime.Restart(); return new InstallSetupResult(new Dictionary { diff --git a/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs b/src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitInstallStep.cs similarity index 83% rename from src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs rename to src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitInstallStep.cs index c9688fed4c..4e14da30b7 100644 --- a/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs +++ b/src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitInstallStep.cs @@ -2,9 +2,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using System.Web; +using Umbraco.Net; using Umbraco.Core.Services; -using Umbraco.Web.Composing; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps @@ -14,13 +13,13 @@ namespace Umbraco.Web.Install.InstallSteps PerformsAppRestart = true)] internal class StarterKitInstallStep : InstallSetupStep { - private readonly IHttpContextAccessor _httContextAccessor; + private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IPackagingService _packagingService; - public StarterKitInstallStep(IHttpContextAccessor httContextAccessor, IUmbracoContextAccessor umbracoContextAccessor, IPackagingService packagingService) + public StarterKitInstallStep(IUmbracoApplicationLifetime umbracoApplicationLifetime, IUmbracoContextAccessor umbracoContextAccessor, IPackagingService packagingService) { - _httContextAccessor = httContextAccessor; + _umbracoApplicationLifetime = umbracoApplicationLifetime; _umbracoContextAccessor = umbracoContextAccessor; _packagingService = packagingService; } @@ -34,7 +33,9 @@ namespace Umbraco.Web.Install.InstallSteps InstallBusinessLogic(packageId); - UmbracoApplication.Restart(_httContextAccessor.GetRequiredHttpContext()); + _umbracoApplicationLifetime.Restart(); + + return Task.FromResult(null); } diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs index 9dde28e95a..bb77869e28 100644 --- a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs +++ b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Logging.Serilog /// public class SerilogLogger : ILogger, IDisposable { - private readonly ICoreDebug _coreDebug; + private readonly ICoreDebugSettings _coreDebugSettings; private readonly IIOHelper _ioHelper; private readonly IMarchal _marchal; @@ -26,9 +26,9 @@ namespace Umbraco.Core.Logging.Serilog /// Initialize a new instance of the class with a configuration file. /// /// - public SerilogLogger(ICoreDebug coreDebug, IIOHelper ioHelper, IMarchal marchal, FileInfo logConfigFile) + public SerilogLogger(ICoreDebugSettings coreDebugSettings, IIOHelper ioHelper, IMarchal marchal, FileInfo logConfigFile) { - _coreDebug = coreDebug; + _coreDebugSettings = coreDebugSettings; _ioHelper = ioHelper; _marchal = marchal; @@ -37,9 +37,9 @@ namespace Umbraco.Core.Logging.Serilog .CreateLogger(); } - public SerilogLogger(ICoreDebug coreDebug, IIOHelper ioHelper, IMarchal marchal, LoggerConfiguration logConfig) + public SerilogLogger(ICoreDebugSettings coreDebugSettings, IIOHelper ioHelper, IMarchal marchal, LoggerConfiguration logConfig) { - _coreDebug = coreDebug; + _coreDebugSettings = coreDebugSettings; _ioHelper = ioHelper; _marchal = marchal; @@ -51,7 +51,7 @@ namespace Umbraco.Core.Logging.Serilog /// Creates a logger with some pre-defined configuration and remainder from config file /// /// Used by UmbracoApplicationBase to get its logger. - public static SerilogLogger CreateWithDefaultConfiguration(IHostingEnvironment hostingEnvironment, ISessionIdResolver sessionIdResolver, Func requestCacheGetter, ICoreDebug coreDebug, IIOHelper ioHelper, IMarchal marchal) + public static SerilogLogger CreateWithDefaultConfiguration(IHostingEnvironment hostingEnvironment, ISessionIdResolver sessionIdResolver, Func requestCacheGetter, ICoreDebugSettings coreDebugSettings, IIOHelper ioHelper, IMarchal marchal) { var loggerConfig = new LoggerConfiguration(); loggerConfig @@ -59,7 +59,7 @@ namespace Umbraco.Core.Logging.Serilog .ReadFromConfigFile() .ReadFromUserConfigFile(); - return new SerilogLogger(coreDebug, ioHelper, marchal, loggerConfig); + return new SerilogLogger(coreDebugSettings, ioHelper, marchal, loggerConfig); } /// @@ -179,7 +179,7 @@ namespace Umbraco.Core.Logging.Serilog messageTemplate += "\r\nThe thread has been aborted, because the request has timed out."; // dump if configured, or if stacktrace contains Monitor.ReliableEnter - dump = _coreDebug.DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception); + dump = _coreDebugSettings.DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception); // dump if it is ok to dump (might have a cap on number of dump...) dump &= MiniDump.OkToDump(_ioHelper); diff --git a/src/Umbraco.Web/Macros/MacroTagParser.cs b/src/Umbraco.Infrastructure/Macros/MacroTagParser.cs similarity index 96% rename from src/Umbraco.Web/Macros/MacroTagParser.cs rename to src/Umbraco.Infrastructure/Macros/MacroTagParser.cs index 7dea3674b0..2cbd84e20a 100644 --- a/src/Umbraco.Web/Macros/MacroTagParser.cs +++ b/src/Umbraco.Infrastructure/Macros/MacroTagParser.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.Macros /// /// Parses the macro syntax in a string and renders out it's contents /// - internal class MacroTagParser + public class MacroTagParser { private static readonly Regex MacroRteContent = new Regex(@"()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); @@ -35,7 +35,7 @@ namespace Umbraco.Web.Macros /// {/div} /// /// - internal static string FormatRichTextPersistedDataForEditor(string persistedContent, IDictionary htmlAttributes) + public static string FormatRichTextPersistedDataForEditor(string persistedContent, IDictionary htmlAttributes) { return MacroPersistedFormat.Replace(persistedContent, match => { @@ -92,7 +92,7 @@ namespace Umbraco.Web.Macros /// since this is exactly how we need to persist it to the db. /// /// - internal static string FormatRichTextContentForPersistence(string rteContent) + public static string FormatRichTextContentForPersistence(string rteContent) { if (string.IsNullOrEmpty(rteContent)) { @@ -145,7 +145,7 @@ namespace Umbraco.Web.Macros /// This method simply parses the macro contents, it does not create a string or result, /// this is up to the developer calling this method to implement this with the callbacks. /// - internal static void ParseMacros( + public static void ParseMacros( string text, Action textFoundCallback, Action> macroFoundCallback ) diff --git a/src/Umbraco.Infrastructure/Manifest/DataEditorConverter.cs b/src/Umbraco.Infrastructure/Manifest/DataEditorConverter.cs index aac05def9f..9a6b37a9bb 100644 --- a/src/Umbraco.Infrastructure/Manifest/DataEditorConverter.cs +++ b/src/Umbraco.Infrastructure/Manifest/DataEditorConverter.cs @@ -140,7 +140,7 @@ namespace Umbraco.Core.Manifest private string RewriteVirtualUrl(JValue view) { - return _ioHelper.ResolveVirtualUrl(view.Value as string); + return _ioHelper.ResolveRelativeOrVirtualUrl(view.Value as string); } private void PrepareForParameterEditor(JObject jobject, DataEditor target) diff --git a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs index 2074409ec8..c6ffb9b6ef 100644 --- a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs +++ b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs @@ -195,21 +195,21 @@ namespace Umbraco.Core.Manifest // scripts and stylesheets are raw string, must process here for (var i = 0; i < manifest.Scripts.Length; i++) - manifest.Scripts[i] = _ioHelper.ResolveVirtualUrl(manifest.Scripts[i]); + manifest.Scripts[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Scripts[i]); for (var i = 0; i < manifest.Stylesheets.Length; i++) - manifest.Stylesheets[i] = _ioHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); + manifest.Stylesheets[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Stylesheets[i]); foreach (var contentApp in manifest.ContentApps) { - contentApp.View = _ioHelper.ResolveVirtualUrl(contentApp.View); + contentApp.View = _ioHelper.ResolveRelativeOrVirtualUrl(contentApp.View); } foreach (var dashboard in manifest.Dashboards) { - dashboard.View = _ioHelper.ResolveVirtualUrl(dashboard.View); + dashboard.View = _ioHelper.ResolveRelativeOrVirtualUrl(dashboard.View); } foreach (var gridEditor in manifest.GridEditors) { - gridEditor.View = _ioHelper.ResolveVirtualUrl(gridEditor.View); - gridEditor.Render = _ioHelper.ResolveVirtualUrl(gridEditor.Render); + gridEditor.View = _ioHelper.ResolveRelativeOrVirtualUrl(gridEditor.View); + gridEditor.Render = _ioHelper.ResolveRelativeOrVirtualUrl(gridEditor.Render); } // add property editors that are also parameter editors, to the parameter editors list @@ -220,11 +220,5 @@ namespace Umbraco.Core.Manifest return manifest; } - - // purely for tests - public IEnumerable ParseGridEditors(string text) - { - return _jsonSerializer.Deserialize>(text); - } } } diff --git a/src/Umbraco.Infrastructure/Media/Exif/ExifBitConverter.cs b/src/Umbraco.Infrastructure/Media/Exif/ExifBitConverter.cs index 86bd12c15e..1dcae62acd 100644 --- a/src/Umbraco.Infrastructure/Media/Exif/ExifBitConverter.cs +++ b/src/Umbraco.Infrastructure/Media/Exif/ExifBitConverter.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Media.Exif public ExifBitConverter(ByteOrder from, ByteOrder to) : base(from, to) { - ; + } #endregion diff --git a/src/Umbraco.Infrastructure/Media/Exif/ExifExtendedProperty.cs b/src/Umbraco.Infrastructure/Media/Exif/ExifExtendedProperty.cs index 63c1ce3365..8889a13e1d 100644 --- a/src/Umbraco.Infrastructure/Media/Exif/ExifExtendedProperty.cs +++ b/src/Umbraco.Infrastructure/Media/Exif/ExifExtendedProperty.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.Media.Exif public ExifEnumProperty(ExifTag tag, T value) : this(tag, value, false) { - ; + } public override ExifInterOperability Interoperability @@ -210,13 +210,13 @@ namespace Umbraco.Web.Media.Exif public ExifPointSubjectArea(ExifTag tag, ushort[] value) : base(tag, value) { - ; + } public ExifPointSubjectArea(ExifTag tag, ushort x, ushort y) - : base(tag, new ushort[] { x, y }) + : base(tag, new ushort[] {x, y}) { - ; + } } @@ -239,13 +239,13 @@ namespace Umbraco.Web.Media.Exif public ExifCircularSubjectArea(ExifTag tag, ushort[] value) : base(tag, value) { - ; + } public ExifCircularSubjectArea(ExifTag tag, ushort x, ushort y, ushort d) : base(tag, new ushort[] { x, y, d }) { - ; + } } @@ -269,13 +269,13 @@ namespace Umbraco.Web.Media.Exif public ExifRectangularSubjectArea(ExifTag tag, ushort[] value) : base(tag, value) { - ; + } public ExifRectangularSubjectArea(ExifTag tag, ushort x, ushort y, ushort w, ushort h) : base(tag, new ushort[] { x, y, w, h }) { - ; + } } @@ -303,13 +303,13 @@ namespace Umbraco.Web.Media.Exif public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value) : base(tag, value) { - ; + } public GPSLatitudeLongitude(ExifTag tag, float d, float m, float s) : base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) { - ; + } } @@ -331,13 +331,13 @@ namespace Umbraco.Web.Media.Exif public GPSTimeStamp(ExifTag tag, MathEx.UFraction32[] value) : base(tag, value) { - ; + } public GPSTimeStamp(ExifTag tag, float h, float m, float s) : base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(h), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) { - ; + } } diff --git a/src/Umbraco.Infrastructure/Media/Exif/JFIFExtendedProperty.cs b/src/Umbraco.Infrastructure/Media/Exif/JFIFExtendedProperty.cs index 94b255f4d1..24b3ac74be 100644 --- a/src/Umbraco.Infrastructure/Media/Exif/JFIFExtendedProperty.cs +++ b/src/Umbraco.Infrastructure/Media/Exif/JFIFExtendedProperty.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Media.Exif public JFIFVersion(ExifTag tag, ushort value) : base(tag, value) { - ; + } public override string ToString() diff --git a/src/Umbraco.Infrastructure/Media/Exif/JPEGSection.cs b/src/Umbraco.Infrastructure/Media/Exif/JPEGSection.cs index a1bc420fe4..78565d2bfa 100644 --- a/src/Umbraco.Infrastructure/Media/Exif/JPEGSection.cs +++ b/src/Umbraco.Infrastructure/Media/Exif/JPEGSection.cs @@ -45,7 +45,7 @@ public JPEGSection(JPEGMarker marker) : this(marker, new byte[0], new byte[0]) { - ; + } #endregion diff --git a/src/Umbraco.Infrastructure/Media/Exif/MathEx.cs b/src/Umbraco.Infrastructure/Media/Exif/MathEx.cs index 735358c40a..94cbccfbda 100644 --- a/src/Umbraco.Infrastructure/Media/Exif/MathEx.cs +++ b/src/Umbraco.Infrastructure/Media/Exif/MathEx.cs @@ -403,37 +403,37 @@ namespace Umbraco.Web.Media.Exif public Fraction32(int numerator, int denominator) : this(numerator, denominator, 0) { - ; + } public Fraction32(int numerator) : this(numerator, (int)1) { - ; + } public Fraction32(Fraction32 f) : this(f.Numerator, f.Denominator, f.Error) { - ; + } public Fraction32(float value) : this((double)value) { - ; + } public Fraction32(double value) : this(FromDouble(value)) { - ; + } public Fraction32(string s) : this(FromString(s)) { - ; + } #endregion @@ -1033,37 +1033,37 @@ namespace Umbraco.Web.Media.Exif public UFraction32(uint numerator, uint denominator) : this(numerator, denominator, 0) { - ; + } public UFraction32(uint numerator) : this(numerator, (uint)1) { - ; + } public UFraction32(UFraction32 f) : this(f.Numerator, f.Denominator, f.Error) { - ; + } public UFraction32(float value) : this((double)value) { - ; + } public UFraction32(double value) : this(FromDouble(value)) { - ; + } public UFraction32(string s) : this(FromString(s)) { - ; + } #endregion diff --git a/src/Umbraco.Infrastructure/Models/ImageProcessorImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs similarity index 65% rename from src/Umbraco.Infrastructure/Models/ImageProcessorImageUrlGenerator.cs rename to src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index f88309d7e3..9058123eb3 100644 --- a/src/Umbraco.Infrastructure/Models/ImageProcessorImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -1,11 +1,12 @@ 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 + public class ImageSharpImageUrlGenerator : IImageUrlGenerator { public string GetImageUrl(ImageUrlGenerationOptions options) { @@ -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/Media/UploadAutoFillProperties.cs b/src/Umbraco.Infrastructure/Media/UploadAutoFillProperties.cs index 4c02111ccd..665693d91f 100644 --- a/src/Umbraco.Infrastructure/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Infrastructure/Media/UploadAutoFillProperties.cs @@ -17,13 +17,13 @@ namespace Umbraco.Web.Media { private readonly IMediaFileSystem _mediaFileSystem; private readonly ILogger _logger; - private readonly IContentSection _contentSection; + private readonly IContentSettings _contentSettings; - public UploadAutoFillProperties(IMediaFileSystem mediaFileSystem, ILogger logger, IContentSection contentSection) + public UploadAutoFillProperties(IMediaFileSystem mediaFileSystem, ILogger logger, IContentSettings contentSettings) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _contentSection = contentSection ?? throw new ArgumentNullException(nameof(contentSection)); + _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); } /// @@ -68,7 +68,7 @@ namespace Umbraco.Web.Media using (var filestream = _mediaFileSystem.OpenFile(filepath)) { var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); - var size = _contentSection.IsImageFile(extension) ? (Size?)ImageHelper.GetDimensions(filestream) : null; + var size = _contentSettings.IsImageFile(extension) ? (Size?)ImageHelper.GetDimensions(filestream) : null; SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment); } } @@ -102,7 +102,7 @@ namespace Umbraco.Web.Media else { var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); - var size = _contentSection.IsImageFile(extension) ? (Size?)ImageHelper.GetDimensions(filestream) : null; + var size = _contentSettings.IsImageFile(extension) ? (Size?)ImageHelper.GetDimensions(filestream) : null; SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index 8b1e6d741b..291a4a329d 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Xml.Linq; using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Upgrade; @@ -24,10 +25,11 @@ namespace Umbraco.Core.Migrations.Install private readonly IRuntimeState _runtime; private readonly IMigrationBuilder _migrationBuilder; private readonly IKeyValueService _keyValueService; + private readonly IHostingEnvironment _hostingEnvironment; private readonly ILogger _logger; - private readonly IIOHelper _ioHelper; private readonly IUmbracoVersion _umbracoVersion; private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator; + private readonly IConfigManipulator _configManipulator; private DatabaseSchemaResult _databaseSchemaValidationResult; @@ -42,9 +44,10 @@ namespace Umbraco.Core.Migrations.Install ILogger logger, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, - IIOHelper ioHelper, + IHostingEnvironment hostingEnvironment, IUmbracoVersion umbracoVersion, - IDbProviderFactoryCreator dbProviderFactoryCreator) + IDbProviderFactoryCreator dbProviderFactoryCreator, + IConfigManipulator configManipulator) { _scopeProvider = scopeProvider; _globalSettings = globalSettings; @@ -53,9 +56,10 @@ namespace Umbraco.Core.Migrations.Install _logger = logger; _migrationBuilder = migrationBuilder; _keyValueService = keyValueService; - _ioHelper = ioHelper; + _hostingEnvironment = hostingEnvironment; _umbracoVersion = umbracoVersion; _dbProviderFactoryCreator = dbProviderFactoryCreator; + _configManipulator = configManipulator; } #region Status @@ -138,14 +142,14 @@ namespace Umbraco.Core.Migrations.Install /// public void ConfigureEmbeddedDatabaseConnection() { - ConfigureEmbeddedDatabaseConnection(_databaseFactory, _ioHelper, _logger); + ConfigureEmbeddedDatabaseConnection(_databaseFactory); } - private void ConfigureEmbeddedDatabaseConnection(IUmbracoDatabaseFactory factory, IIOHelper ioHelper, ILogger logger) + private void ConfigureEmbeddedDatabaseConnection(IUmbracoDatabaseFactory factory) { - SaveConnectionString(EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe, ioHelper, logger); + _configManipulator.SaveConnectionString(EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe); - var path = Path.Combine(ioHelper.GetRootDirectorySafe(), "App_Data", "Umbraco.sdf"); + var path = Path.Combine(_hostingEnvironment.ApplicationPhysicalPath, "App_Data", "Umbraco.sdf"); if (File.Exists(path) == false) { // this should probably be in a "using (new SqlCeEngine)" clause but not sure @@ -166,7 +170,7 @@ namespace Umbraco.Core.Migrations.Install { const string providerName = Constants.DbProviderNames.SqlServer; - SaveConnectionString(connectionString, providerName, _ioHelper, _logger); + _configManipulator.SaveConnectionString(connectionString, providerName); _databaseFactory.Configure(connectionString, providerName); } @@ -182,7 +186,7 @@ namespace Umbraco.Core.Migrations.Install { var connectionString = GetDatabaseConnectionString(server, databaseName, user, password, databaseProvider, out var providerName); - SaveConnectionString(connectionString, providerName, _ioHelper, _logger); + _configManipulator.SaveConnectionString(connectionString, providerName); _databaseFactory.Configure(connectionString, providerName); } @@ -213,7 +217,7 @@ namespace Umbraco.Core.Migrations.Install public void ConfigureIntegratedSecurityDatabaseConnection(string server, string databaseName) { var connectionString = GetIntegratedSecurityDatabaseConnectionString(server, databaseName); - SaveConnectionString(connectionString, Constants.DbProviderNames.SqlServer, _ioHelper, _logger); + _configManipulator.SaveConnectionString(connectionString, Constants.DbProviderNames.SqlServer); _databaseFactory.Configure(connectionString, Constants.DbProviderNames.SqlServer); } @@ -282,75 +286,8 @@ namespace Umbraco.Core.Migrations.Install return server.ToLower().StartsWith("tcp:".ToLower()); } - /// - /// Saves the connection string as a proper .net connection string in web.config. - /// - /// Saves the ConnectionString in the very nasty 'medium trust'-supportive way. - /// The connection string. - /// The provider name. - /// A logger. - private static void SaveConnectionString(string connectionString, string providerName, IIOHelper ioHelper, ILogger logger) - { - if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); - if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(connectionString)); - if (providerName == null) throw new ArgumentNullException(nameof(providerName)); - if (string.IsNullOrWhiteSpace(providerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(providerName)); - var fileSource = "web.config"; - var fileName = ioHelper.MapPath(ioHelper.Root +"/" + fileSource); - var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); - if (xml.Root == null) throw new Exception($"Invalid {fileSource} file (no root)."); - - var connectionStrings = xml.Root.DescendantsAndSelf("connectionStrings").FirstOrDefault(); - if (connectionStrings == null) throw new Exception($"Invalid {fileSource} file (no connection strings)."); - - // handle configSource - var configSourceAttribute = connectionStrings.Attribute("configSource"); - if (configSourceAttribute != null) - { - fileSource = configSourceAttribute.Value; - fileName = ioHelper.MapPath(ioHelper.Root + "/" + fileSource); - - if (!File.Exists(fileName)) - throw new Exception($"Invalid configSource \"{fileSource}\" (no such file)."); - - xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); - if (xml.Root == null) throw new Exception($"Invalid {fileSource} file (no root)."); - - connectionStrings = xml.Root.DescendantsAndSelf("connectionStrings").FirstOrDefault(); - if (connectionStrings == null) throw new Exception($"Invalid {fileSource} file (no connection strings)."); - } - - // create or update connection string - var setting = connectionStrings.Descendants("add").FirstOrDefault(s => s.Attribute("name")?.Value == Constants.System.UmbracoConnectionName); - if (setting == null) - { - connectionStrings.Add(new XElement("add", - new XAttribute("name", Constants.System.UmbracoConnectionName), - new XAttribute("connectionString", connectionString), - new XAttribute("providerName", providerName))); - } - else - { - AddOrUpdateAttribute(setting, "connectionString", connectionString); - AddOrUpdateAttribute(setting, "providerName", providerName); - } - - // save - logger.Info("Saving connection string to {ConfigFile}.", fileSource); - xml.Save(fileName, SaveOptions.DisableFormatting); - logger.Info("Saved connection string to {ConfigFile}.", fileSource); - } - - private static void AddOrUpdateAttribute(XElement element, string name, string value) - { - var attribute = element.Attribute(name); - if (attribute == null) - element.Add(new XAttribute(name, value)); - else - attribute.Value = value; - } #endregion 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/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs index 8a79cca403..921ba0b3d5 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs @@ -130,7 +130,7 @@ namespace Umbraco.Core.Migrations.Install if (e.Cancel == false) { - var dataCreation = new DatabaseDataCreator(_database, _logger,_umbracoVersion, _globalSettings); + var dataCreation = new DatabaseDataCreator(_database, _logger, _umbracoVersion, _globalSettings); foreach (var table in OrderedTables) CreateTable(false, table, dataCreation); } @@ -451,7 +451,7 @@ namespace Umbraco.Core.Migrations.Install //Execute the Create Table sql var created = _database.Execute(new Sql(createSql)); - _logger.Info("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql); + _logger.Info("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql); //If any statements exists for the primary key execute them here if (string.IsNullOrEmpty(createPrimaryKeySql) == false) @@ -487,11 +487,11 @@ namespace Umbraco.Core.Migrations.Install if (overwrite) { - _logger.Info("Table {TableName} was recreated", tableName); + _logger.Info("Table {TableName} was recreated", tableName); } else { - _logger.Info("New table {TableName} was created", tableName); + _logger.Info("New table {TableName} was created", tableName); } } diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs index ab9c946e05..3a91fc3369 100644 --- a/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs +++ b/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs @@ -1,8 +1,6 @@ using Umbraco.Core; -using Umbraco.Core.Cookie; using Umbraco.Core.Migrations; - namespace Umbraco.Web.Migrations.PostMigrations { /// 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/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs index 06a7f9aeb6..4ab5d386b0 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 if (string.IsNullOrEmpty(dataType.Configuration)) { config.Format = "YYYY-MM-DD"; - }; + } } catch (Exception ex) { diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/RefactorMacroColumns.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/RefactorMacroColumns.cs index 58ec0e30c2..bc3df6f584 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/RefactorMacroColumns.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/RefactorMacroColumns.cs @@ -18,11 +18,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 AddColumn("macroSource", out var sqls2); //populate the new columns with legacy data - Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = '', macroType = {(int)MacroTypes.Unknown}").Do(); - Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = {(int)MacroTypes.Unknown} WHERE macroXSLT != '' AND macroXSLT IS NOT NULL").Do(); - Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptAssembly != '' AND macroScriptAssembly IS NOT NULL").Do(); - Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptType != '' AND macroScriptType IS NOT NULL").Do(); - Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = {(int)MacroTypes.PartialView} WHERE macroPython != '' AND macroPython IS NOT NULL").Do(); + //when the macro type is PartialView, it corresponds to 7, else it is 4 for Unknown + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = '', macroType = 4").Do(); + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = 4 WHERE macroXSLT != '' AND macroXSLT IS NOT NULL").Do(); + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = 4 WHERE macroScriptAssembly != '' AND macroScriptAssembly IS NOT NULL").Do(); + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = 4 WHERE macroScriptType != '' AND macroScriptType IS NOT NULL").Do(); + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = 7 WHERE macroPython != '' AND macroPython IS NOT NULL").Do(); //now apply constraints (NOT NULL) to new table foreach (var sql in sqls1) Execute.Sql(sql).Do(); diff --git a/src/Umbraco.Infrastructure/Models/ContentEditing/UserInvite.cs b/src/Umbraco.Infrastructure/Models/ContentEditing/UserInvite.cs index f1c6cf6c04..06e4d0748c 100644 --- a/src/Umbraco.Infrastructure/Models/ContentEditing/UserInvite.cs +++ b/src/Umbraco.Infrastructure/Models/ContentEditing/UserInvite.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Models.ContentEditing if (UserGroups.Any() == false) yield return new ValidationResult("A user must be assigned to at least one group", new[] { nameof(UserGroups) }); - if (Current.Configs.Settings().Security.UsernameIsEmail == false && Username.IsNullOrWhiteSpace()) + if (Current.Configs.Security().UsernameIsEmail == false && Username.IsNullOrWhiteSpace()) yield return new ValidationResult("A username cannot be empty", new[] { nameof(Username) }); } } diff --git a/src/Umbraco.Infrastructure/Models/Macro.cs b/src/Umbraco.Infrastructure/Models/Macro.cs index 95e3477212..083c288e09 100644 --- a/src/Umbraco.Infrastructure/Models/Macro.cs +++ b/src/Umbraco.Infrastructure/Models/Macro.cs @@ -40,7 +40,7 @@ namespace Umbraco.Core.Models /// /// /// - public Macro(IShortStringHelper shortStringHelper, int id, Guid key, bool useInEditor, int cacheDuration, string @alias, string name, bool cacheByPage, bool cacheByMember, bool dontRender, string macroSource, MacroTypes macroType) + public Macro(IShortStringHelper shortStringHelper, int id, Guid key, bool useInEditor, int cacheDuration, string @alias, string name, bool cacheByPage, bool cacheByMember, bool dontRender, string macroSource) : this(shortStringHelper) { Id = id; @@ -53,7 +53,6 @@ namespace Umbraco.Core.Models CacheByMember = cacheByMember; DontRender = dontRender; MacroSource = macroSource; - MacroType = macroType; } /// @@ -69,7 +68,6 @@ namespace Umbraco.Core.Models /// public Macro(IShortStringHelper shortStringHelper, string @alias, string name, string macroSource, - MacroTypes macroType, bool cacheByPage = false, bool cacheByMember = false, bool dontRender = true, @@ -85,7 +83,6 @@ namespace Umbraco.Core.Models CacheByMember = cacheByMember; DontRender = dontRender; MacroSource = macroSource; - MacroType = macroType; } private string _alias; @@ -96,7 +93,6 @@ namespace Umbraco.Core.Models private bool _cacheByMember; private bool _dontRender; private string _macroSource; - private MacroTypes _macroType = MacroTypes.Unknown; private MacroPropertyCollection _properties; private List _addedProperties; private List _removedProperties; @@ -247,16 +243,6 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _macroSource, nameof(MacroSource)); } - /// - /// Gets or set the path to the Partial View to render - /// - [DataMember] - public MacroTypes MacroType - { - get => _macroType; - set => SetPropertyValueAndDetectChanges(value, ref _macroType, nameof(MacroType)); - } - /// /// Gets or sets a list of Macro Properties /// diff --git a/src/Umbraco.Web/Models/Mapping/CommonMapper.cs b/src/Umbraco.Infrastructure/Models/Mapping/CommonMapper.cs similarity index 72% rename from src/Umbraco.Web/Models/Mapping/CommonMapper.cs rename to src/Umbraco.Infrastructure/Models/Mapping/CommonMapper.cs index b1e511670f..2d7ee84675 100644 --- a/src/Umbraco.Web/Models/Mapping/CommonMapper.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/CommonMapper.cs @@ -1,18 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Web; -using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; -using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Trees; + using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping @@ -23,17 +20,15 @@ namespace Umbraco.Web.Models.Mapping private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private readonly ContentAppFactoryCollection _contentAppDefinitions; private readonly ILocalizedTextService _localizedTextService; - private readonly IHttpContextAccessor _httpContextAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor; public CommonMapper(IUserService userService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, - ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService, IHttpContextAccessor httpContextAccessor) + ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService) { _userService = userService; _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _contentAppDefinitions = contentAppDefinitions; _localizedTextService = localizedTextService; - _httpContextAccessor = httpContextAccessor; _umbracoContextAccessor = umbracoContextAccessor; } @@ -64,25 +59,6 @@ namespace Umbraco.Web.Models.Mapping return null; } - public string GetTreeNodeUrl(IContentBase source) - where TController : ContentTreeControllerBase - { - var httpContext = _httpContextAccessor.HttpContext; - if (httpContext == null) return null; - - var urlHelper = new UrlHelper(httpContext.Request.RequestContext); - return urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); - } - - public string GetMemberTreeNodeUrl(IContentBase source) - { - var httpContext = _httpContextAccessor.HttpContext; - if (httpContext == null) return null; - - var urlHelper = new UrlHelper(httpContext.Request.RequestContext); - return urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); - } - public IEnumerable GetContentApps(IContentBase source) { var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray(); diff --git a/src/Umbraco.Infrastructure/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Infrastructure/Models/Mapping/ContentTypeMapDefinition.cs index f7a2d6376b..2e985bef07 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/ContentTypeMapDefinition.cs @@ -10,6 +10,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core.Services; using Umbraco.Core.Exceptions; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Strings; @@ -28,13 +29,13 @@ namespace Umbraco.Web.Models.Mapping private readonly IMemberTypeService _memberTypeService; private readonly ILogger _logger; private readonly IShortStringHelper _shortStringHelper; - private readonly IIOHelper _ioHelper; private readonly IGlobalSettings _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; public ContentTypeMapDefinition(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService, IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, - ILogger logger, IShortStringHelper shortStringHelper, IIOHelper ioHelper, IGlobalSettings globalSettings) + ILogger logger, IShortStringHelper shortStringHelper, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; @@ -44,8 +45,8 @@ namespace Umbraco.Web.Models.Mapping _memberTypeService = memberTypeService; _logger = logger; _shortStringHelper = shortStringHelper; - _ioHelper = ioHelper; _globalSettings = globalSettings; + _hostingEnvironment = hostingEnvironment; } public void DefineMaps(UmbracoMapper mapper) @@ -191,7 +192,7 @@ namespace Umbraco.Web.Models.Mapping target.Icon = source.Icon; target.IconFilePath = target.IconIsClass ? string.Empty - : $"{_globalSettings.Path.EnsureEndsWith("/")}images/umbraco/{source.Icon}"; + : $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}"; target.Trashed = source.Trashed; target.Id = source.Id; @@ -204,7 +205,7 @@ namespace Umbraco.Web.Models.Mapping target.Thumbnail = source.Thumbnail; target.ThumbnailFilePath = target.ThumbnailIsClass ? string.Empty - : _ioHelper.ResolveUrl("~/umbraco/images/thumbnails/" + source.Thumbnail); + : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); target.UpdateDate = source.UpdateDate; } @@ -497,7 +498,7 @@ namespace Umbraco.Web.Models.Mapping target.Icon = source.Icon; target.IconFilePath = target.IconIsClass ? string.Empty - : $"{_globalSettings.Path.EnsureEndsWith("/")}images/umbraco/{source.Icon}"; + : $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}"; target.Id = source.Id; target.IsContainer = source.IsContainer; target.IsElement = source.IsElement; @@ -508,7 +509,7 @@ namespace Umbraco.Web.Models.Mapping target.Thumbnail = source.Thumbnail; target.ThumbnailFilePath = target.ThumbnailIsClass ? string.Empty - : _ioHelper.ResolveUrl("~/umbraco/images/thumbnails/" + source.Thumbnail); + : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); target.Udi = MapContentTypeUdi(source); target.UpdateDate = source.UpdateDate; @@ -540,7 +541,7 @@ namespace Umbraco.Web.Models.Mapping target.Icon = source.Icon; target.IconFilePath = target.IconIsClass ? string.Empty - : $"{_globalSettings.Path.EnsureEndsWith("/")}images/umbraco/{source.Icon}"; + : $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}"; target.Id = source.Id; target.IsContainer = source.IsContainer; target.IsElement = source.IsElement; @@ -551,7 +552,7 @@ namespace Umbraco.Web.Models.Mapping target.Thumbnail = source.Thumbnail; target.ThumbnailFilePath = target.ThumbnailIsClass ? string.Empty - : _ioHelper.ResolveUrl("~/umbraco/images/thumbnails/" + source.Thumbnail); + : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); target.Trashed = source.Trashed; target.Udi = source.Udi; } diff --git a/src/Umbraco.Infrastructure/Models/Mapping/DataTypeMapDefinition.cs b/src/Umbraco.Infrastructure/Models/Mapping/DataTypeMapDefinition.cs index d57809c844..0bd6d54c08 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/DataTypeMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/DataTypeMapDefinition.cs @@ -15,13 +15,13 @@ namespace Umbraco.Web.Models.Mapping { private readonly PropertyEditorCollection _propertyEditors; private readonly ILogger _logger; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; + private readonly IContentSettings _contentSettings; - public DataTypeMapDefinition(PropertyEditorCollection propertyEditors, ILogger logger, IUmbracoSettingsSection umbracoSettingsSection) + public DataTypeMapDefinition(PropertyEditorCollection propertyEditors, ILogger logger, IContentSettings contentSettings) { _propertyEditors = propertyEditors; _logger = logger; - _umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection)); + _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); } private static readonly int[] SystemIds = @@ -128,9 +128,8 @@ namespace Umbraco.Web.Models.Mapping private IEnumerable MapAvailableEditors(IDataType source, MapperContext context) { - var contentSection = _umbracoSettingsSection.Content; var properties = _propertyEditors - .Where(x => !x.IsDeprecated || contentSection.ShowDeprecatedPropertyEditors || source.EditorAlias == x.Alias) + .Where(x => !x.IsDeprecated || _contentSettings.ShowDeprecatedPropertyEditors || source.EditorAlias == x.Alias) .OrderBy(x => x.Name); return context.MapEnumerable(properties); } diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Infrastructure/Models/Mapping/EntityMapDefinition.cs similarity index 96% rename from src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs rename to src/Umbraco.Infrastructure/Models/Mapping/EntityMapDefinition.cs index c454f19ce7..ba7b5cddf2 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/EntityMapDefinition.cs @@ -11,7 +11,7 @@ using Umbraco.Examine; namespace Umbraco.Web.Models.Mapping { - internal class EntityMapDefinition : IMapDefinition + public class EntityMapDefinition : IMapDefinition { public void DefineMaps(UmbracoMapper mapper) { @@ -175,6 +175,12 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Values.ContainsKey(UmbracoExamineFieldNames.NodeNameFieldName) ? source.Values[UmbracoExamineFieldNames.NodeNameFieldName] : "[no name]"; + var culture = context.GetCulture(); + if(culture.IsNullOrWhiteSpace() == false) + { + target.Name = source.Values.ContainsKey($"nodeName_{culture}") ? source.Values[$"nodeName_{culture}"] : target.Name; + } + if (source.Values.TryGetValue(UmbracoExamineFieldNames.UmbracoFileFieldName, out var umbracoFile)) { if (umbracoFile != null) @@ -236,7 +242,7 @@ namespace Umbraco.Web.Models.Mapping return memberEntity.ContentTypeIcon.IfNullOrWhiteSpace(Constants.Icons.Member); case IContentEntitySlim contentEntity: // NOTE: this case covers both content and media entities - return contentEntity.ContentTypeIcon; + return contentEntity.ContentTypeIcon; } return null; 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/MediaExtensions.cs b/src/Umbraco.Infrastructure/Models/MediaExtensions.cs index 41bcfdbc88..ffcc2c2a92 100644 --- a/src/Umbraco.Infrastructure/Models/MediaExtensions.cs +++ b/src/Umbraco.Infrastructure/Models/MediaExtensions.cs @@ -27,9 +27,9 @@ namespace Umbraco.Core.Models /// /// Gets the urls of a media item. /// - public static string[] GetUrls(this IMedia media, IContentSection contentSection, MediaUrlGeneratorCollection mediaUrlGenerators) + public static string[] GetUrls(this IMedia media, IContentSettings contentSettings, MediaUrlGeneratorCollection mediaUrlGenerators) { - return contentSection.ImageAutoFillProperties + return contentSettings.ImageAutoFillProperties .Select(field => media.GetUrl(field.Alias, mediaUrlGenerators)) .Where(link => string.IsNullOrWhiteSpace(link) == false) .ToArray(); diff --git a/src/Umbraco.Infrastructure/Models/Property.cs b/src/Umbraco.Infrastructure/Models/Property.cs index 36efbad404..798f84cf6f 100644 --- a/src/Umbraco.Infrastructure/Models/Property.cs +++ b/src/Umbraco.Infrastructure/Models/Property.cs @@ -98,7 +98,7 @@ namespace Umbraco.Core.Models /// /// Represents a property value. /// - public class PropertyValue : IPropertyValue + public class PropertyValue : IPropertyValue, IDeepCloneable, IEquatable { // TODO: Either we allow change tracking at this class level, or we add some special change tracking collections to the Property // class to deal with change tracking which variants have changed @@ -143,6 +143,32 @@ namespace Umbraco.Core.Models /// public IPropertyValue Clone() => new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; + + public object DeepClone() => Clone(); + + public override bool Equals(object obj) + { + return Equals(obj as PropertyValue); + } + + public bool Equals(PropertyValue other) + { + return other != null && + _culture == other._culture && + _segment == other._segment && + EqualityComparer.Default.Equals(EditedValue, other.EditedValue) && + EqualityComparer.Default.Equals(PublishedValue, other.PublishedValue); + } + + public override int GetHashCode() + { + var hashCode = 1885328050; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_culture); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_segment); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(EditedValue); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(PublishedValue); + return hashCode; + } } private static readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( diff --git a/src/Umbraco.Infrastructure/Models/PropertyType.cs b/src/Umbraco.Infrastructure/Models/PropertyType.cs index a7ac63f70f..8b15fdfac4 100644 --- a/src/Umbraco.Infrastructure/Models/PropertyType.cs +++ b/src/Umbraco.Infrastructure/Models/PropertyType.cs @@ -168,6 +168,7 @@ namespace Umbraco.Core.Models /// [DataMember] + [DoNotClone] public Lazy PropertyGroupId { get => _propertyGroupId; 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/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs index 1699365dfd..6b1aa96e69 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs @@ -1133,7 +1133,6 @@ namespace Umbraco.Core.Packaging { var macroName = macroElement.Element("name").Value; var macroAlias = macroElement.Element("alias").Value; - var macroType = Enum.Parse(macroElement.Element("macroType").Value); var macroSource = macroElement.Element("macroSource").Value; //Following xml elements are treated as nullable properties @@ -1169,7 +1168,7 @@ namespace Umbraco.Core.Packaging } var existingMacro = _macroService.GetByAlias(macroAlias) as Macro; - var macro = existingMacro ?? new Macro(_shortStringHelper, macroAlias, macroName, macroSource, macroType, + var macro = existingMacro ?? new Macro(_shortStringHelper, macroAlias, macroName, macroSource, cacheByPage, cacheByMember, dontRender, useInEditor, cacheDuration); var properties = macroElement.Element("properties"); diff --git a/src/Umbraco.Infrastructure/Packaging/PackageInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageInstallation.cs index e307802606..cf31b3ea52 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageInstallation.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Xml.Linq; -using Umbraco.Core.IO; -using Umbraco.Core.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.Models.Packaging; -using Umbraco.Core.Services; namespace Umbraco.Core.Packaging { @@ -27,18 +24,17 @@ namespace Umbraco.Core.Packaging /// /// /// - /// - /// The root folder of the application - /// + /// public PackageInstallation(PackageDataInstallation packageDataInstallation, PackageFileInstallation packageFileInstallation, CompiledPackageXmlParser parser, IPackageActionRunner packageActionRunner, - DirectoryInfo applicationRootFolder) + IHostingEnvironment hostingEnvironment) { + _packageExtraction = new PackageExtraction(); _packageFileInstallation = packageFileInstallation ?? throw new ArgumentNullException(nameof(packageFileInstallation)); _packageDataInstallation = packageDataInstallation ?? throw new ArgumentNullException(nameof(packageDataInstallation)); _parser = parser ?? throw new ArgumentNullException(nameof(parser)); _packageActionRunner = packageActionRunner ?? throw new ArgumentNullException(nameof(packageActionRunner)); - _applicationRootFolder = applicationRootFolder ?? throw new ArgumentNullException(nameof(applicationRootFolder)); + _applicationRootFolder = new DirectoryInfo(hostingEnvironment.ApplicationPhysicalPath); } public CompiledPackage ReadPackage(FileInfo packageFile) 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/Factories/MacroFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/MacroFactory.cs index 25cde62c75..5d0cbc9c34 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/MacroFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/MacroFactory.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.Factories { public static IMacro BuildEntity(IShortStringHelper shortStringHelper, MacroDto dto) { - var model = new Macro(shortStringHelper, dto.Id, dto.UniqueId, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.MacroSource, (MacroTypes)dto.MacroType); + var model = new Macro(shortStringHelper, dto.Id, dto.UniqueId, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.MacroSource); try { @@ -45,7 +45,7 @@ namespace Umbraco.Core.Persistence.Factories RefreshRate = entity.CacheDuration, UseInEditor = entity.UseInEditor, MacroPropertyDtos = BuildPropertyDtos(entity), - MacroType = (int)entity.MacroType + MacroType = 7 //PartialView }; if (entity.HasIdentity) diff --git a/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs b/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs index 23ef0bfda5..5806bb90ec 100644 --- a/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs +++ b/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs @@ -1,9 +1,9 @@ using System.Data.Common; -using StackExchange.Profiling.Internal; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence { + public interface IDbProviderFactoryCreator { DbProviderFactory CreateFactory(); diff --git a/src/Umbraco.Infrastructure/Persistence/LocalDb.cs b/src/Umbraco.Infrastructure/Persistence/LocalDb.cs index 55d6565344..4ec233e17f 100644 --- a/src/Umbraco.Infrastructure/Persistence/LocalDb.cs +++ b/src/Umbraco.Infrastructure/Persistence/LocalDb.cs @@ -253,6 +253,11 @@ namespace Umbraco.Core.Persistence _masterCstr = $@"Server=(localdb)\{instanceName};Integrated Security=True;"; } + public static string GetConnectionString(string instanceName, string databaseName) + { + return $@"Server=(localdb)\{instanceName};Integrated Security=True;Database={databaseName};"; + } + /// /// Gets a LocalDb connection string. /// diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/MacroMapper.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/MacroMapper.cs index 6a0d803052..f6f7de8347 100644 --- a/src/Umbraco.Infrastructure/Persistence/Mappers/MacroMapper.cs +++ b/src/Umbraco.Infrastructure/Persistence/Mappers/MacroMapper.cs @@ -19,7 +19,6 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(Macro.Alias), nameof(MacroDto.Alias)); DefineMap(nameof(Macro.CacheByPage), nameof(MacroDto.CacheByPage)); DefineMap(nameof(Macro.CacheByMember), nameof(MacroDto.CachePersonalized)); - DefineMap(nameof(Macro.MacroType), nameof(MacroDto.MacroType)); DefineMap(nameof(Macro.DontRender), nameof(MacroDto.DontRender)); DefineMap(nameof(Macro.Name), nameof(MacroDto.Name)); DefineMap(nameof(Macro.CacheDuration), nameof(MacroDto.RefreshRate)); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs index c3f9f03ec0..3d006914a2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs @@ -653,10 +653,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var entity = new MediaEntitySlim(); BuildContentEntity(entity, dto); - if (dto is MediaEntityDto contentDto) + // fill in the media info + if (dto is MediaEntityDto mediaEntityDto) { - // fill in the media info - entity.MediaPath = contentDto.MediaPath; + entity.MediaPath = mediaEntityDto.MediaPath; + } + else if (dto is GenericContentEntityDto genericContentEntityDto) + { + entity.MediaPath = genericContentEntityDto.MediaPath; } return entity; 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/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs index 6e5dcc6b76..ef5a41b21d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs @@ -158,7 +158,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IMacro entity) { entity.UpdatingEntity(); -; var dto = MacroFactory.BuildDto(entity); Database.Update(dto); 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/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs index 3db94b026e..c02329aac4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs @@ -219,7 +219,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //Save updated entity to db template.UpdateDate = DateTime.Now; - ; var dto = TemplateFactory.BuildDto(template, NodeObjectTypeId, templateDto.PrimaryKey); Database.Update(dto.NodeDto); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs index 07cfbb05a8..863f3dc455 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -319,7 +319,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private void PersistAllowedSections(IUserGroup entity) { - var userGroup = (UserGroup) entity; + var userGroup = entity; // First delete all Database.Delete("WHERE UserGroupId = @UserGroupId", new { UserGroupId = userGroup.Id }); 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/SqlServerDbProviderFactoryCreator.cs b/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs new file mode 100644 index 0000000000..9c2c6273c2 --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs @@ -0,0 +1,55 @@ +using System; +using System.Data.Common; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence +{ + public class SqlServerDbProviderFactoryCreator : IDbProviderFactoryCreator + { + private readonly string _defaultProviderName; + private readonly Func _getFactory; + + public SqlServerDbProviderFactoryCreator(string defaultProviderName, Func getFactory) + { + _defaultProviderName = defaultProviderName; + _getFactory = getFactory; + } + + public DbProviderFactory CreateFactory() => CreateFactory(_defaultProviderName); + + public DbProviderFactory CreateFactory(string providerName) + { + if (string.IsNullOrEmpty(providerName)) return null; + return _getFactory(providerName); + } + + // gets the sql syntax provider that corresponds, from attribute + public ISqlSyntaxProvider GetSqlSyntaxProvider(string providerName) + { + return providerName switch + { + Constants.DbProviderNames.SqlCe => throw new NotSupportedException("SqlCe is not supported"), + Constants.DbProviderNames.SqlServer => new SqlServerSyntaxProvider(), + _ => throw new InvalidOperationException($"Unknown provider name \"{providerName}\""), + }; + } + + public IBulkSqlInsertProvider CreateBulkSqlInsertProvider(string providerName) + { + switch (providerName) + { + case Constants.DbProviderNames.SqlCe: + throw new NotSupportedException("SqlCe is not supported"); + case Constants.DbProviderNames.SqlServer: + return new SqlServerBulkSqlInsertProvider(); + default: + return new BasicBulkSqlInsertProvider(); + } + } + + public void CreateDatabase() + { + throw new NotSupportedException("Embedded databases are not supported"); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 7ae001bf24..58f9a4b68a 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -77,12 +77,13 @@ namespace Umbraco.Core.Persistence.SqlSyntax string ConvertIntegerToOrderableString { get; } string ConvertDateToOrderableString { get; } string ConvertDecimalToOrderableString { get; } - + /// /// Returns the default isolation level for the database /// IsolationLevel DefaultIsolationLevel { get; } + string DbProvider { get; } IEnumerable GetTablesInSchema(IDatabase db); IEnumerable GetColumnsInSchema(IDatabase db); diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index a478c88412..8d3a41e5bf 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -175,6 +175,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax return items.Select(x => new Tuple(x.TableName, x.ColumnName, x.Name, x.Definition)); } + public override string DbProvider => ServerVersion.IsAzure ? "SqlAzure" : "SqlServer"; + public override IEnumerable GetTablesInSchema(IDatabase db) { var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index b2e03df96e..3df97c0b4f 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -202,6 +202,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax } public abstract IsolationLevel DefaultIsolationLevel { get; } + public abstract string DbProvider { get; } public virtual IEnumerable GetTablesInSchema(IDatabase db) { diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs index ea3d603f95..a4b4afbe25 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs @@ -28,6 +28,7 @@ namespace Umbraco.Core.Persistence private readonly RetryPolicy _connectionRetryPolicy; private readonly RetryPolicy _commandRetryPolicy; private readonly Guid _instanceGuid = Guid.NewGuid(); + private List _commands; #region Ctor @@ -162,6 +163,14 @@ namespace Umbraco.Core.Persistence /// public int SqlCount { get; private set; } + internal bool LogCommands + { + get => _commands != null; + set => _commands = value ? new List() : null; + } + + internal IEnumerable Commands => _commands; + public int BulkInsertRecords(IEnumerable records) { return _bulkSqlInsertProvider.BulkInsertRecords(this, records); @@ -267,9 +276,43 @@ namespace Umbraco.Core.Persistence if (_enableCount) SqlCount++; + _commands?.Add(new CommandInfo(cmd)); + base.OnExecutedCommand(cmd); } #endregion + + // used for tracking commands + public class CommandInfo + { + public CommandInfo(IDbCommand cmd) + { + Text = cmd.CommandText; + var parameters = new List(); + foreach (IDbDataParameter parameter in cmd.Parameters) parameters.Add(new ParameterInfo(parameter)); + Parameters = parameters.ToArray(); + } + + public string Text { get; } + public ParameterInfo[] Parameters { get; } + } + + // used for tracking commands + public class ParameterInfo + { + public ParameterInfo(IDbDataParameter parameter) + { + Name = parameter.ParameterName; + Value = parameter.Value; + DbType = parameter.DbType; + Size = parameter.Size; + } + + public string Name { get; } + public object Value { get; } + public DbType DbType { get; } + public int Size { get; } + } } } 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/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs index 9474e02d38..837fe6cba3 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs @@ -27,8 +27,8 @@ namespace Umbraco.Core.Persistence // TODO: this class needs not be disposable! internal class UmbracoDatabaseFactory : DisposableObjectSlim, IUmbracoDatabaseFactory { - private readonly Configs _configs; private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator; + private readonly IGlobalSettings _globalSettings; private readonly Lazy _mappers; private readonly ILogger _logger; @@ -36,7 +36,6 @@ namespace Umbraco.Core.Persistence private DatabaseFactory _npocoDatabaseFactory; private IPocoDataFactory _pocoDataFactory; - private string _connectionString; private string _providerName; private DatabaseType _databaseType; private ISqlSyntaxProvider _sqlSyntax; @@ -71,27 +70,27 @@ namespace Umbraco.Core.Persistence /// Initializes a new instance of the . /// /// Used by core runtime. - public UmbracoDatabaseFactory(ILogger logger, Lazy mappers, Configs configs, IDbProviderFactoryCreator dbProviderFactoryCreator) - : this(Constants.System.UmbracoConnectionName, logger, mappers, configs, dbProviderFactoryCreator) + public UmbracoDatabaseFactory(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, Lazy mappers,IDbProviderFactoryCreator dbProviderFactoryCreator) + : this(logger, globalSettings, connectionStrings, Constants.System.UmbracoConnectionName, mappers, dbProviderFactoryCreator) { - _configs = configs; + } /// /// Initializes a new instance of the . /// /// Used by the other ctor and in tests. - public UmbracoDatabaseFactory(string connectionStringName, ILogger logger, Lazy mappers, Configs configs, IDbProviderFactoryCreator dbProviderFactoryCreator) + public UmbracoDatabaseFactory(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, string connectionStringName, Lazy mappers, IDbProviderFactoryCreator dbProviderFactoryCreator) { if (connectionStringName == null) throw new ArgumentNullException(nameof(connectionStringName)); if (string.IsNullOrWhiteSpace(connectionStringName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(connectionStringName)); + _globalSettings = globalSettings; _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); - _configs = configs; _dbProviderFactoryCreator = dbProviderFactoryCreator ?? throw new ArgumentNullException(nameof(dbProviderFactoryCreator)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - var settings = configs.ConnectionStrings()[connectionStringName]; + var settings = connectionStrings[connectionStringName]; if (settings == null) { @@ -116,7 +115,7 @@ namespace Umbraco.Core.Persistence /// Initializes a new instance of the . /// /// Used in tests. - public UmbracoDatabaseFactory(string connectionString, string providerName, ILogger logger, Lazy mappers, IDbProviderFactoryCreator dbProviderFactoryCreator) + public UmbracoDatabaseFactory(ILogger logger, string connectionString, string providerName, Lazy mappers, IDbProviderFactoryCreator dbProviderFactoryCreator) { _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -140,7 +139,7 @@ namespace Umbraco.Core.Persistence { lock (_lock) { - return !_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace(); + return !ConnectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace(); } } } @@ -149,25 +148,25 @@ namespace Umbraco.Core.Persistence public bool Initialized => Volatile.Read(ref _initialized); /// - public string ConnectionString => _connectionString; + public string ConnectionString { get; private set; } /// public bool CanConnect => // actually tries to connect to the database (regardless of configured/initialized) - !_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace() && - DbConnectionExtensions.IsConnectionAvailable(_connectionString, DbProviderFactory); + !ConnectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace() && + DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DbProviderFactory); private void UpdateSqlServerDatabaseType() { // replace NPoco database type by a more efficient one - var setting = _configs.Global().DatabaseFactoryServerVersion; + var setting = _globalSettings.DatabaseFactoryServerVersion; var fromSettings = false; if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.") || !Enum.TryParse(setting.Substring("SqlServer.".Length), out var versionName, true)) { - versionName = ((SqlServerSyntaxProvider) _sqlSyntax).GetSetVersion(_connectionString, _providerName, _logger).ProductVersionName; + versionName = ((SqlServerSyntaxProvider) _sqlSyntax).GetSetVersion(ConnectionString, _providerName, _logger).ProductVersionName; } else { @@ -231,7 +230,7 @@ namespace Umbraco.Core.Persistence if (Volatile.Read(ref _initialized)) throw new InvalidOperationException("Already initialized."); - _connectionString = connectionString; + ConnectionString = connectionString; _providerName = providerName; } @@ -247,18 +246,19 @@ namespace Umbraco.Core.Persistence { _logger.Debug("Initializing."); - if (_connectionString.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper connection string."); + if (ConnectionString.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper connection string."); if (_providerName.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper provider name."); if (DbProviderFactory == null) throw new Exception($"Can't find a provider factory for provider name \"{_providerName}\"."); // cannot initialize without being able to talk to the database - if (!DbConnectionExtensions.IsConnectionAvailable(_connectionString, DbProviderFactory)) + // TODO: Why not? + if (!DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DbProviderFactory)) throw new Exception("Cannot connect to the database."); - _connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionString); - _commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionString); + _connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(ConnectionString); + _commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(ConnectionString); _databaseType = DatabaseType.Resolve(DbProviderFactory.GetType().Name, _providerName); @@ -311,7 +311,7 @@ namespace Umbraco.Core.Persistence // method used by NPoco's UmbracoDatabaseFactory to actually create the database instance private UmbracoDatabase CreateDatabaseInstance() { - return new UmbracoDatabase(_connectionString, SqlContext, DbProviderFactory, _logger, _bulkSqlInsertProvider, _connectionRetryPolicy, _commandRetryPolicy); + return new UmbracoDatabase(ConnectionString, SqlContext, DbProviderFactory, _logger, _bulkSqlInsertProvider, _connectionRetryPolicy, _commandRetryPolicy); } protected override void DisposeResources() diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Infrastructure/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index 0e56e0030e..0efa42929a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.PropertyEditors ConfigurationField field; - var attributeView = ioHelper.ResolveVirtualUrl(attribute.View); + var attributeView = ioHelper.ResolveRelativeOrVirtualUrl(attribute.View); // if the field does not have its own type, use the base type if (attribute.Type == null) { diff --git a/src/Umbraco.Infrastructure/PropertyEditors/DataEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/DataEditor.cs index afdcd65ad1..95bc898ea6 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/DataEditor.cs @@ -119,7 +119,7 @@ namespace Umbraco.Core.PropertyEditors /// Technically, it could be cached by datatype but let's keep things /// simple enough for now. /// - public IDataValueEditor GetValueEditor(object configuration) + public virtual IDataValueEditor GetValueEditor(object configuration) { // if an explicit value editor has been set (by the manifest parser) // then return it, and ignore the configuration, which is going to be diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs index 8c820c497c..698fcb10b3 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs @@ -22,23 +22,21 @@ namespace Umbraco.Web.PropertyEditors public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator { private readonly IMediaFileSystem _mediaFileSystem; - private readonly IContentSection _contentSection; + private readonly IContentSettings _contentSettings; private readonly UploadAutoFillProperties _uploadAutoFillProperties; private readonly IDataTypeService _dataTypeService; private readonly ILocalizationService _localizationService; private readonly ILocalizedTextService _localizedTextService; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; - public FileUploadPropertyEditor(ILogger logger, IMediaFileSystem mediaFileSystem, IContentSection contentSection, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IUmbracoSettingsSection umbracoSettingsSection) + public FileUploadPropertyEditor(ILogger logger, IMediaFileSystem mediaFileSystem, IContentSettings contentSettings, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper) : base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); - _contentSection = contentSection; + _contentSettings = contentSettings; _dataTypeService = dataTypeService; _localizationService = localizationService; _localizedTextService = localizedTextService; - _uploadAutoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, contentSection); - _umbracoSettingsSection = umbracoSettingsSection; + _uploadAutoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, contentSettings); } /// @@ -47,8 +45,8 @@ namespace Umbraco.Web.PropertyEditors /// The corresponding property value editor. protected override IDataValueEditor CreateValueEditor() { - var editor = new FileUploadPropertyValueEditor(Attribute, _mediaFileSystem, _dataTypeService, _localizationService, _localizedTextService, ShortStringHelper, _umbracoSettingsSection); - editor.Validators.Add(new UploadFileTypeValidator(_localizedTextService, _umbracoSettingsSection)); + var editor = new FileUploadPropertyValueEditor(Attribute, _mediaFileSystem, _dataTypeService, _localizationService, _localizedTextService, ShortStringHelper, _contentSettings); + editor.Validators.Add(new UploadFileTypeValidator(_localizedTextService, _contentSettings)); return editor; } @@ -179,7 +177,7 @@ namespace Umbraco.Web.PropertyEditors foreach (var property in properties) { - var autoFillConfig = _contentSection.GetConfig(property.Alias); + var autoFillConfig = _contentSettings.GetConfig(property.Alias); if (autoFillConfig == null) continue; foreach (var pvalue in property.Values) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs index 97b5ef20f5..e45896551c 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -16,13 +16,13 @@ namespace Umbraco.Web.PropertyEditors internal class FileUploadPropertyValueEditor : DataValueEditor { private readonly IMediaFileSystem _mediaFileSystem; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; + private readonly IContentSettings _contentSettings; - public FileUploadPropertyValueEditor(DataEditorAttribute attribute, IMediaFileSystem mediaFileSystem, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IUmbracoSettingsSection umbracoSettingsSection) + public FileUploadPropertyValueEditor(DataEditorAttribute attribute, IMediaFileSystem mediaFileSystem, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IContentSettings contentSettings) : base(dataTypeService, localizationService, localizedTextService, shortStringHelper, attribute) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); - _umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection)); + _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); } /// @@ -101,7 +101,7 @@ namespace Umbraco.Web.PropertyEditors { // process the file // no file, invalid file, reject change - if (UploadFileTypeValidator.IsValidFileExtension(file.FileName, _umbracoSettingsSection) == false) + if (UploadFileTypeValidator.IsValidFileExtension(file.FileName, _contentSettings) == false) return null; // get the filepath diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs similarity index 96% rename from src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs rename to src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs index d76e637b66..1a8e4524c7 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs @@ -1,12 +1,11 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Web.Composing; 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; @@ -64,7 +63,7 @@ namespace Umbraco.Web.PropertyEditors /// Overridden to ensure that the value is validated /// /// - protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute,_umbracoContextAccessor, DataTypeService, LocalizationService, LocalizedTextService, _imageSourceParser, _pastedImages, _localLinkParser, ShortStringHelper, _imageUrlGenerator); + protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, DataTypeService, LocalizationService, LocalizedTextService, _imageSourceParser, _pastedImages, _localLinkParser, ShortStringHelper, _imageUrlGenerator); protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(_ioHelper); diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyIndexValueFactory.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs similarity index 97% rename from src/Umbraco.Web/PropertyEditors/GridPropertyIndexValueFactory.cs rename to src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs index 00afcf19cc..195764fbbf 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyIndexValueFactory.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs @@ -1,18 +1,17 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Xml; using Umbraco.Examine; namespace Umbraco.Web.PropertyEditors { - using System.Collections.Generic; - using System.Linq; - using Umbraco.Core.Models; - /// /// Parses the grid value into indexable values /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index 045a0fb3d2..586a120609 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -29,17 +29,16 @@ namespace Umbraco.Web.PropertyEditors public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator { private readonly IMediaFileSystem _mediaFileSystem; - private readonly IContentSection _contentSettings; + private readonly IContentSettings _contentSettings; private readonly IDataTypeService _dataTypeService; private readonly ILocalizationService _localizationService; private readonly IIOHelper _ioHelper; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; private readonly UploadAutoFillProperties _autoFillProperties; /// /// Initializes a new instance of the class. /// - public ImageCropperPropertyEditor(ILogger logger, IMediaFileSystem mediaFileSystem, IContentSection contentSettings, IDataTypeService dataTypeService, ILocalizationService localizationService, IIOHelper ioHelper, IShortStringHelper shortStringHelper, ILocalizedTextService localizedTextService, IUmbracoSettingsSection umbracoSettingsSection) + public ImageCropperPropertyEditor(ILogger logger, IMediaFileSystem mediaFileSystem, IContentSettings contentSettings, IDataTypeService dataTypeService, ILocalizationService localizationService, IIOHelper ioHelper, IShortStringHelper shortStringHelper, ILocalizedTextService localizedTextService) : base(logger, dataTypeService, localizationService, localizedTextService,shortStringHelper) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); @@ -47,7 +46,6 @@ namespace Umbraco.Web.PropertyEditors _dataTypeService = dataTypeService; _localizationService = localizationService; _ioHelper = ioHelper; - _umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection)); // TODO: inject? _autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, _contentSettings); @@ -68,7 +66,7 @@ namespace Umbraco.Web.PropertyEditors /// Creates the corresponding property value editor. /// /// The corresponding property value editor. - protected override IDataValueEditor CreateValueEditor() => new ImageCropperPropertyValueEditor(Attribute, Logger, _mediaFileSystem, DataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, _umbracoSettingsSection); + protected override IDataValueEditor CreateValueEditor() => new ImageCropperPropertyValueEditor(Attribute, Logger, _mediaFileSystem, DataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, _contentSettings); /// /// Creates the corresponding preValue editor. diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs index 9919dda47d..1c7c8b922a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -22,14 +22,14 @@ namespace Umbraco.Web.PropertyEditors { private readonly ILogger _logger; private readonly IMediaFileSystem _mediaFileSystem; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; + private readonly IContentSettings _contentSettings; - public ImageCropperPropertyValueEditor(DataEditorAttribute attribute, ILogger logger, IMediaFileSystem mediaFileSystem, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IUmbracoSettingsSection umbracoSettingsSection) + public ImageCropperPropertyValueEditor(DataEditorAttribute attribute, ILogger logger, IMediaFileSystem mediaFileSystem, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IContentSettings contentSettings) : base(dataTypeService, localizationService, localizedTextService, shortStringHelper, attribute) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); - _umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection)); + _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); } /// @@ -146,7 +146,7 @@ namespace Umbraco.Web.PropertyEditors { // process the file // no file, invalid file, reject change - if (UploadFileTypeValidator.IsValidFileExtension(file.FileName, _umbracoSettingsSection) == false) + if (UploadFileTypeValidator.IsValidFileExtension(file.FileName, _contentSettings) == false) return null; // get the filepath diff --git a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs similarity index 92% rename from src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs rename to src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs index 4b956bbe5b..38b4611c1e 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs @@ -1,14 +1,16 @@ -using HtmlAgilityPack; -using System; +using System; using System.Collections.Generic; using System.IO; +using HtmlAgilityPack; 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; +using Umbraco.Web.Routing; using Umbraco.Web.Templates; namespace Umbraco.Web.PropertyEditors @@ -22,10 +24,11 @@ namespace Umbraco.Web.PropertyEditors private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private readonly IMediaFileSystem _mediaFileSystem; private readonly IShortStringHelper _shortStringHelper; + private readonly IPublishedUrlProvider _publishedUrlProvider; const string TemporaryImageDataAttribute = "data-tmpimg"; - public RichTextEditorPastedImages(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IIOHelper ioHelper, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper) + public RichTextEditorPastedImages(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IIOHelper ioHelper, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper, IPublishedUrlProvider publishedUrlProvider) { _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -34,6 +37,7 @@ namespace Umbraco.Web.PropertyEditors _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider ?? throw new ArgumentNullException(nameof(contentTypeBaseServiceProvider)); _mediaFileSystem = mediaFileSystem; _shortStringHelper = shortStringHelper; + _publishedUrlProvider = publishedUrlProvider; } /// @@ -43,7 +47,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - internal string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IImageUrlGenerator imageUrlGenerator) + public string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IImageUrlGenerator imageUrlGenerator) { // Find all img's that has data-tmpimg attribute // Use HTML Agility Pack - https://html-agility-pack.net @@ -108,7 +112,7 @@ namespace Umbraco.Web.PropertyEditors if (mediaTyped == null) throw new PanicException($"Could not find media by id {udi.Guid} or there was no UmbracoContext available."); - var location = mediaTyped.Url(); + var location = mediaTyped.Url(_publishedUrlProvider); // Find the width & height attributes as we need to set the imageprocessor QueryString var width = img.GetAttributeValue("width", int.MinValue); diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs similarity index 99% rename from src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs rename to src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 32076af7af..2f283b2709 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using Umbraco.Core; -using Umbraco.Web.Composing; 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 7168ce194a..b64c52648a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs @@ -16,12 +16,12 @@ namespace Umbraco.Web.PropertyEditors internal class UploadFileTypeValidator : IValueValidator { private readonly ILocalizedTextService _localizedTextService; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; + private readonly IContentSettings _contentSettings; - public UploadFileTypeValidator(ILocalizedTextService localizedTextService, IUmbracoSettingsSection umbracoSettingsSection) + public UploadFileTypeValidator(ILocalizedTextService localizedTextService, IContentSettings contentSettings) { _localizedTextService = localizedTextService; - _umbracoSettingsSection = umbracoSettingsSection; + _contentSettings = contentSettings; } public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) @@ -46,7 +46,7 @@ namespace Umbraco.Web.PropertyEditors foreach (string filename in fileNames) { - if (IsValidFileExtension(filename, _umbracoSettingsSection) == false) + if (IsValidFileExtension(filename, _contentSettings) == false) { //we only store a single value for this editor so the 'member' or 'field' // we'll associate this error with will simply be called 'value' @@ -55,11 +55,11 @@ namespace Umbraco.Web.PropertyEditors } } - internal static bool IsValidFileExtension(string fileName, IUmbracoSettingsSection umbracoSettingsSection) + internal static bool IsValidFileExtension(string fileName, IContentSettings contentSettings) { if (fileName.IndexOf('.') <= 0) return false; - var extension = new FileInfo(fileName).Extension.TrimStart("."); - return umbracoSettingsSection.Content.IsFileAllowedForUpload(extension); + 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/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs index 64e4312b3f..0043eeed72 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -48,7 +48,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters value = new ImageCropperValue { Src = sourceString }; } - value.ApplyConfiguration(propertyType.DataType.ConfigurationAs()); + value?.ApplyConfiguration(propertyType.DataType.ConfigurationAs()); return value; } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs similarity index 98% rename from src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs rename to src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index 60857fd76c..a55f73980b 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -1,13 +1,13 @@ -using System.Text; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Web.Templates; -using System.Linq; +using System.Linq; +using System.Text; using HtmlAgilityPack; -using Umbraco.Web.Macros; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Strings; +using Umbraco.Web.Macros; +using Umbraco.Web.Templates; namespace Umbraco.Web.PropertyEditors.ValueConverters { @@ -61,7 +61,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters macroAlias, umbracoContext.PublishedRequest?.PublishedContent, //needs to be explicitly casted to Dictionary - macroAttributes.ConvertTo(x => (string)x, x => x)).GetAsText())); + macroAttributes.ConvertTo(x => (string)x, x => x)).Text)); return sb.ToString(); } diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs index f35cea085d..e995850a1f 100644 --- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs @@ -14,35 +14,85 @@ using Umbraco.Web.PublishedCache; namespace Umbraco.Web { /// - /// A class used to query for published content, media items + /// A class used to query for published content, media items /// public class PublishedContentQuery : IPublishedContentQuery { + private readonly IExamineManager _examineManager; private readonly IPublishedSnapshot _publishedSnapshot; private readonly IVariationContextAccessor _variationContextAccessor; - private readonly IExamineManager _examineManager; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor, IExamineManager examineManager) + public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, + IVariationContextAccessor variationContextAccessor, IExamineManager examineManager) { _publishedSnapshot = publishedSnapshot ?? throw new ArgumentNullException(nameof(publishedSnapshot)); - _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); + _variationContextAccessor = variationContextAccessor ?? + throw new ArgumentNullException(nameof(variationContextAccessor)); _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); } + #region Convert Helpers + + private static bool ConvertIdObjectToInt(object id, out int intId) + { + switch (id) + { + case string s: + return int.TryParse(s, out intId); + + case int i: + intId = i; + return true; + + default: + intId = default; + return false; + } + } + + private static bool ConvertIdObjectToGuid(object id, out Guid guidId) + { + switch (id) + { + case string s: + return Guid.TryParse(s, out guidId); + + case Guid g: + guidId = g; + return true; + + default: + guidId = default; + return false; + } + } + private static bool ConvertIdObjectToUdi(object id, out Udi guidId) + { + switch (id) + { + case string s: + return UdiParser.TryParse(s, out guidId); + + case Udi u: + guidId = u; + return true; + + default: + guidId = default; + return false; + } + } + + #endregion + #region Content - public IPublishedContent Content(int id) - { - return ItemById(id, _publishedSnapshot.Content); - } + public IPublishedContent Content(int id) => ItemById(id, _publishedSnapshot.Content); - public IPublishedContent Content(Guid id) - { - return ItemById(id, _publishedSnapshot.Content); - } + public IPublishedContent Content(Guid id) => ItemById(id, _publishedSnapshot.Content); public IPublishedContent Content(Udi id) { @@ -50,49 +100,45 @@ namespace Umbraco.Web return ItemById(udi.Guid, _publishedSnapshot.Content); } - public IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars) + public IPublishedContent Content(object id) { - return ItemByXPath(xpath, vars, _publishedSnapshot.Content); + if (ConvertIdObjectToInt(id, out var intId)) + return Content(intId); + if (ConvertIdObjectToGuid(id, out var guidId)) + return Content(guidId); + if (ConvertIdObjectToUdi(id, out var udiId)) + return Content(udiId); + return null; } - public IEnumerable Content(IEnumerable ids) - { - return ItemsByIds(_publishedSnapshot.Content, ids); - } + public IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars) => + ItemByXPath(xpath, vars, _publishedSnapshot.Content); - public IEnumerable Content(IEnumerable ids) - { - return ItemsByIds(_publishedSnapshot.Content, ids); - } + public IEnumerable Content(IEnumerable ids) => + ItemsByIds(_publishedSnapshot.Content, ids); - public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) - { - return ItemsByXPath(xpath, vars, _publishedSnapshot.Content); - } + public IEnumerable Content(IEnumerable ids) => + ItemsByIds(_publishedSnapshot.Content, ids); - public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) + public IEnumerable Content(IEnumerable ids) { - return ItemsByXPath(xpath, vars, _publishedSnapshot.Content); + return ids.Select(Content).WhereNotNull(); } + public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) => + ItemsByXPath(xpath, vars, _publishedSnapshot.Content); - public IEnumerable ContentAtRoot() - { - return ItemsAtRoot(_publishedSnapshot.Content); - } + public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) => + ItemsByXPath(xpath, vars, _publishedSnapshot.Content); + + public IEnumerable ContentAtRoot() => ItemsAtRoot(_publishedSnapshot.Content); #endregion #region Media - public IPublishedContent Media(int id) - { - return ItemById(id, _publishedSnapshot.Media); - } + public IPublishedContent Media(int id) => ItemById(id, _publishedSnapshot.Media); - public IPublishedContent Media(Guid id) - { - return ItemById(id, _publishedSnapshot.Media); - } + public IPublishedContent Media(Guid id) => ItemById(id, _publishedSnapshot.Media); public IPublishedContent Media(Udi id) { @@ -100,21 +146,26 @@ namespace Umbraco.Web return ItemById(udi.Guid, _publishedSnapshot.Media); } - public IEnumerable Media(IEnumerable ids) + public IPublishedContent Media(object id) { - return ItemsByIds(_publishedSnapshot.Media, ids); + if (ConvertIdObjectToInt(id, out var intId)) + return Media(intId); + if (ConvertIdObjectToGuid(id, out var guidId)) + return Media(guidId); + if (ConvertIdObjectToUdi(id, out var udiId)) + return Media(udiId); + return null; } - public IEnumerable Media(IEnumerable ids) + public IEnumerable Media(IEnumerable ids) => ItemsByIds(_publishedSnapshot.Media, ids); + public IEnumerable Media(IEnumerable ids) { - return ItemsByIds(_publishedSnapshot.Media, ids); + return ids.Select(Media).WhereNotNull(); } - public IEnumerable MediaAtRoot() - { - return ItemsAtRoot(_publishedSnapshot.Media); - } + public IEnumerable Media(IEnumerable ids) => ItemsByIds(_publishedSnapshot.Media, ids); + public IEnumerable MediaAtRoot() => ItemsAtRoot(_publishedSnapshot.Media); #endregion @@ -155,44 +206,45 @@ namespace Umbraco.Web return ids.Select(eachId => ItemById(eachId, cache)).WhereNotNull(); } - private static IEnumerable ItemsByXPath(string xpath, XPathVariable[] vars, IPublishedCache cache) + private static IEnumerable ItemsByXPath(string xpath, XPathVariable[] vars, + IPublishedCache cache) { var doc = cache.GetByXPath(xpath, vars); return doc; } - private static IEnumerable ItemsByXPath(XPathExpression xpath, XPathVariable[] vars, IPublishedCache cache) + private static IEnumerable ItemsByXPath(XPathExpression xpath, XPathVariable[] vars, + IPublishedCache cache) { var doc = cache.GetByXPath(xpath, vars); return doc; } - private static IEnumerable ItemsAtRoot(IPublishedCache cache) - { - return cache.GetAtRoot(); - } + private static IEnumerable ItemsAtRoot(IPublishedCache cache) => cache.GetAtRoot(); #endregion #region Search /// - public IEnumerable Search(string term, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName) - { - return Search(term, 0, 0, out _, culture, indexName); - } + public IEnumerable Search(string term, string culture = "*", + string indexName = Constants.UmbracoIndexes.ExternalIndexName) => + Search(term, 0, 0, out _, culture, indexName); /// - public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName) + public IEnumerable Search(string term, int skip, int take, out long totalRecords, + string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName) { if (skip < 0) { - throw new ArgumentOutOfRangeException(nameof(skip), skip, "The value must be greater than or equal to zero."); + throw new ArgumentOutOfRangeException(nameof(skip), skip, + "The value must be greater than or equal to zero."); } if (take < 0) { - throw new ArgumentOutOfRangeException(nameof(take), take, "The value must be greater than or equal to zero."); + throw new ArgumentOutOfRangeException(nameof(take), take, + "The value must be greater than or equal to zero."); } if (string.IsNullOrEmpty(indexName)) @@ -202,7 +254,8 @@ namespace Umbraco.Web if (!_examineManager.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex)) { - throw new InvalidOperationException($"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}"); + throw new InvalidOperationException( + $"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}"); } var query = umbIndex.GetSearcher().CreateQuery(IndexTypes.Content); @@ -216,13 +269,16 @@ namespace Umbraco.Web else if (string.IsNullOrWhiteSpace(culture)) { // Only search invariant - queryExecutor = query.Field(UmbracoExamineFieldNames.VariesByCultureFieldName, "n") // Must not vary by culture + queryExecutor = query + .Field(UmbracoExamineFieldNames.VariesByCultureFieldName, "n") // Must not vary by culture .And().ManagedQuery(term); } else { // Only search the specified culture - var fields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); // Get all index fields suffixed with the culture name supplied + var fields = + umbIndex.GetCultureAndInvariantFields(culture) + .ToArray(); // Get all index fields suffixed with the culture name supplied queryExecutor = query.ManagedQuery(term, fields); } @@ -232,26 +288,28 @@ namespace Umbraco.Web totalRecords = results.TotalItemCount; - return new CultureContextualSearchResults(results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture); + return new CultureContextualSearchResults( + results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, + culture); } /// - public IEnumerable Search(IQueryExecutor query) - { - return Search(query, 0, 0, out _); - } + public IEnumerable Search(IQueryExecutor query) => Search(query, 0, 0, out _); /// - public IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords) + public IEnumerable Search(IQueryExecutor query, int skip, int take, + out long totalRecords) { if (skip < 0) { - throw new ArgumentOutOfRangeException(nameof(skip), skip, "The value must be greater than or equal to zero."); + throw new ArgumentOutOfRangeException(nameof(skip), skip, + "The value must be greater than or equal to zero."); } if (take < 0) { - throw new ArgumentOutOfRangeException(nameof(take), take, "The value must be greater than or equal to zero."); + throw new ArgumentOutOfRangeException(nameof(take), take, + "The value must be greater than or equal to zero."); } var results = skip == 0 && take == 0 @@ -264,15 +322,17 @@ namespace Umbraco.Web } /// - /// This is used to contextualize the values in the search results when enumerating over them so that the correct culture values are used + /// This is used to contextualize the values in the search results when enumerating over them so that the correct + /// culture values are used /// private class CultureContextualSearchResults : IEnumerable { - private readonly IEnumerable _wrapped; - private readonly IVariationContextAccessor _variationContextAccessor; private readonly string _culture; + private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IEnumerable _wrapped; - public CultureContextualSearchResults(IEnumerable wrapped, IVariationContextAccessor variationContextAccessor, string culture) + public CultureContextualSearchResults(IEnumerable wrapped, + IVariationContextAccessor variationContextAccessor, string culture) { _wrapped = wrapped; _variationContextAccessor = variationContextAccessor; @@ -287,24 +347,23 @@ namespace Umbraco.Web _variationContextAccessor.VariationContext = new VariationContext(_culture); //now the IPublishedContent returned will be contextualized to the culture specified and will be reset when the enumerator is disposed - return new CultureContextualSearchResultsEnumerator(_wrapped.GetEnumerator(), _variationContextAccessor, originalContext); + return new CultureContextualSearchResultsEnumerator(_wrapped.GetEnumerator(), _variationContextAccessor, + originalContext); } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// - /// Resets the variation context when this is disposed + /// Resets the variation context when this is disposed /// private class CultureContextualSearchResultsEnumerator : IEnumerator { - private readonly IEnumerator _wrapped; - private readonly IVariationContextAccessor _variationContextAccessor; private readonly VariationContext _originalContext; + private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IEnumerator _wrapped; - public CultureContextualSearchResultsEnumerator(IEnumerator wrapped, IVariationContextAccessor variationContextAccessor, VariationContext originalContext) + public CultureContextualSearchResultsEnumerator(IEnumerator wrapped, + IVariationContextAccessor variationContextAccessor, VariationContext originalContext) { _wrapped = wrapped; _variationContextAccessor = variationContextAccessor; @@ -318,10 +377,7 @@ namespace Umbraco.Web _variationContextAccessor.VariationContext = _originalContext; } - public bool MoveNext() - { - return _wrapped.MoveNext(); - } + public bool MoveNext() => _wrapped.MoveNext(); public void Reset() { diff --git a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs index f8e21982e0..ac8d0980c4 100644 --- a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs @@ -16,14 +16,14 @@ namespace Umbraco.Web.Routing { private readonly ILogger _logger; private readonly IEntityService _entityService; - private readonly IContentSection _contentConfigSection; + private readonly IContentSettings _contentConfigSettings; private readonly IExamineManager _examineManager; - public ContentFinderByConfigured404(ILogger logger, IEntityService entityService, IContentSection contentConfigSection, IExamineManager examineManager) + public ContentFinderByConfigured404(ILogger logger, IEntityService entityService, IContentSettings contentConfigSettings, IExamineManager examineManager) { _logger = logger; _entityService = entityService; - _contentConfigSection = contentConfigSection; + _contentConfigSettings = contentConfigSettings; _examineManager = examineManager; } @@ -63,7 +63,7 @@ namespace Umbraco.Web.Routing } var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( - _contentConfigSection.Error404Collection.ToArray(), + _contentConfigSettings.Error404Collection.ToArray(), _entityService, new PublishedContentQuery(frequest.UmbracoContext.PublishedSnapshot, frequest.UmbracoContext.VariationContextAccessor, _examineManager), errorCulture); diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs similarity index 89% rename from src/Umbraco.Web/Routing/RedirectTrackingComponent.cs rename to src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs index dffb956b1a..f9256b3692 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Web.PublishedCache; @@ -23,21 +24,24 @@ namespace Umbraco.Web.Routing { private const string _eventStateKey = "Umbraco.Web.Redirects.RedirectTrackingEventHandler"; - private readonly IUmbracoSettingsSection _umbracoSettings; + + private readonly IWebRoutingSettings _webRoutingSettings; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IRedirectUrlService _redirectUrlService; + private readonly IVariationContextAccessor _variationContextAccessor; - public RedirectTrackingComponent(IUmbracoSettingsSection umbracoSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService) + public RedirectTrackingComponent(IWebRoutingSettings webRoutingSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService, IVariationContextAccessor variationContextAccessor) { - _umbracoSettings = umbracoSettings; + _webRoutingSettings = webRoutingSettings; _publishedSnapshotAccessor = publishedSnapshotAccessor; _redirectUrlService = redirectUrlService; + _variationContextAccessor = variationContextAccessor; } public void Initialize() { // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (_umbracoSettings.WebRouting.DisableRedirectUrlTracking) return; + if (_webRoutingSettings.DisableRedirectUrlTracking) return; ContentService.Publishing += ContentService_Publishing; ContentService.Published += ContentService_Published; @@ -99,12 +103,12 @@ namespace Umbraco.Web.Routing { var contentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content; var entityContent = contentCache.GetById(entity.Id); - if (entityContent == null) return; + if (entityContent == null) return; - // get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures) + // get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures) var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray() ?? new[] { (string)null }; - foreach (var x in entityContent.DescendantsOrSelf()) + foreach (var x in entityContent.DescendantsOrSelf(_variationContextAccessor)) { // if this entity defines specific cultures, use those instead of the default ones var cultures = x.Cultures.Any() ? x.Cultures.Keys : defaultCultures; diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComposer.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs similarity index 100% rename from src/Umbraco.Web/Routing/RedirectTrackingComposer.cs rename to src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs index 2586fcd9f5..1ea08e3118 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -1,14 +1,18 @@ using System; +using Examine; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Composing.CompositionExtensions; 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.Events; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; +using Umbraco.Core.Media; using Umbraco.Core.Migrations; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Migrations.PostMigrations; @@ -16,20 +20,36 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; +using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Scoping; using Umbraco.Core.Serialization; 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.Examine; +using Umbraco.Infrastructure.Media; using Umbraco.Web; -using Umbraco.Web.Migrations.PostMigrations; +using Umbraco.Web.Actions; +using Umbraco.Web.Cache; +using Umbraco.Web.ContentApps; +using Umbraco.Web.Editors; +using Umbraco.Web.Features; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.HealthCheck.NotificationMethods; using Umbraco.Web.Install; -using Umbraco.Web.Trees; +using Umbraco.Web.Macros; +using Umbraco.Web.Media.EmbedProviders; +using Umbraco.Web.Migrations.PostMigrations; +using Umbraco.Web.Models.PublishedContent; using Umbraco.Web.PropertyEditors; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Search; +using Umbraco.Web.Sections; using Umbraco.Web.Services; +using Umbraco.Web.Templates; +using Umbraco.Web.Trees; using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator; namespace Umbraco.Core.Runtime @@ -44,7 +64,6 @@ namespace Umbraco.Core.Runtime // composers composition - .ComposeConfiguration() .ComposeRepositories() .ComposeServices() .ComposeCoreMappingProfiles() @@ -118,10 +137,11 @@ namespace Umbraco.Core.Runtime // project composition.RegisterUnique(factory => new DatabaseServerMessenger( - factory.GetInstance(), + factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), + factory.GetInstance(), true, new DatabaseServerMessengerOptions(), factory.GetInstance(), factory.GetInstance() @@ -139,7 +159,7 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); composition.RegisterUnique(factory - => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); + => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); composition.UrlSegmentProviders() .Append(); @@ -170,6 +190,171 @@ namespace Umbraco.Core.Runtime // will be injected in controllers when needed to invoke rest endpoints on Our composition.RegisterUnique(); composition.RegisterUnique(); + + // Grid config is not a real config file as we know them + composition.RegisterUnique(); + + // Config manipulator + composition.RegisterUnique(); + + + // register the http context and umbraco context accessors + // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when + // we have no http context, eg when booting Umbraco or in background threads, so instead + // let's use an hybrid accessor that can fall back to a ThreadStatic context. + composition.RegisterUnique(); + + // register the umbraco context factory + // composition.RegisterUnique(); + composition.RegisterUnique(); + + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + + // both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be + // discovered when CoreBootManager configures the converters. We HAVE to remove one of them + // here because there cannot be two converters for one property editor - and we want the full + // RteMacroRenderingValueConverter that converts macros, etc. So remove TinyMceValueConverter. + // (the limited one, defined in Core, is there for tests) - same for others + composition.PropertyValueConverters() + .Remove() + .Remove() + .Remove(); + + composition.UrlProviders() + .Append() + .Append(); + + composition.MediaUrlProviders() + .Append(); + + composition.RegisterUnique(); + + // register properties fallback + composition.RegisterUnique(); + + composition.RegisterUnique(); + + composition.RegisterUnique(); + + composition.Actions() + .Add(() => composition.TypeLoader.GetTypes()); + + composition.EditorValidators() + .Add(() => composition.TypeLoader.GetTypes()); + + + composition.TourFilters(); + + // replace with web implementation + composition.RegisterUnique(); + + // register OEmbed providers - no type scanning - all explicit opt-in of adding types + // note: IEmbedProvider is not IDiscoverable - think about it if going for type scanning + composition.OEmbedProviders() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + // register back office sections in the order we want them rendered + composition.Sections() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + // register known content apps + composition.ContentApps() + .Append() + .Append() + .Append(); + + // register published router + composition.RegisterUnique(); + + // register *all* checks, except those marked [HideFromTypeFinder] of course + composition.HealthChecks() + .Add(() => composition.TypeLoader.GetTypes()); + + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetTypes()); + + composition.RegisterUnique(); + + composition.ContentFinders() + // all built-in finders in the correct order, + // devs can then modify this list on application startup + .Append() + .Append() + .Append() + //.Append() // disabled, this is an odd finder + .Append() + .Append(); + + composition.Register(Lifetime.Request); + + composition.SearchableTrees() + .Add(() => composition.TypeLoader.GetTypes()); + + // replace some services + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + + composition.RegisterUnique(); + + // register distributed cache + composition.RegisterUnique(f => new DistributedCache(f.GetInstance(), f.GetInstance())); + + + composition.Register(Lifetime.Request); + + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + + composition.RegisterUnique(); + composition.Register(factory => + { + var umbCtx = factory.GetInstance(); + return new PublishedContentQuery(umbCtx.UmbracoContext.PublishedSnapshot, factory.GetInstance(), factory.GetInstance()); + }, Lifetime.Request); + + + composition.RegisterUnique(); + + // register the http context and umbraco context accessors + // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when + // we have no http context, eg when booting Umbraco or in background threads, so instead + // let's use an hybrid accessor that can fall back to a ThreadStatic context. + composition.RegisterUnique(); + + // register accessors for cultures + composition.RegisterUnique(); + + + + } } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 8e4401495d..92e47771a6 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -11,7 +11,6 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Sync; namespace Umbraco.Core.Runtime { @@ -24,9 +23,10 @@ namespace Umbraco.Core.Runtime { private ComponentCollection _components; private IFactory _factory; - private RuntimeState _state; + private readonly RuntimeState _state; private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker; - + private readonly IGlobalSettings _globalSettings; + private readonly IConnectionStrings _connectionStrings; public CoreRuntime( Configs configs, @@ -38,7 +38,8 @@ namespace Umbraco.Core.Runtime IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, IDbProviderFactoryCreator dbProviderFactoryCreator, - IMainDom mainDom) + IMainDom mainDom, + ITypeFinder typeFinder) { IOHelper = ioHelper; Configs = configs; @@ -52,15 +53,16 @@ namespace Umbraco.Core.Runtime Logger = logger; MainDom = mainDom; + TypeFinder = typeFinder; + + _globalSettings = Configs.Global(); + _connectionStrings = configs.ConnectionStrings(); + // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' // as the second one captures the current value (null) and therefore fails - _state = new RuntimeState(Logger, - Configs.Settings(), Configs.Global(), - new Lazy(() => mainDom), - new Lazy(() => _factory.GetInstance()), - UmbracoVersion,HostingEnvironment, BackOfficeInfo) + _state = new RuntimeState(Logger, Configs.Global(), UmbracoVersion, BackOfficeInfo) { Level = RuntimeLevel.Boot }; @@ -72,13 +74,13 @@ namespace Umbraco.Core.Runtime protected ILogger Logger { get; } protected IBackOfficeInfo BackOfficeInfo { get; } + public IDbProviderFactoryCreator DbProviderFactoryCreator { get; } - //public IBulkSqlInsertProvider BulkSqlInsertProvider { get; } /// /// Gets the profiler. /// - protected IProfiler Profiler { get; set; } + protected IProfiler Profiler { get; } /// /// Gets the profiling logger. @@ -88,7 +90,7 @@ namespace Umbraco.Core.Runtime /// /// Gets the /// - protected ITypeFinder TypeFinder { get; private set; } + protected ITypeFinder TypeFinder { get; } /// /// Gets the @@ -101,19 +103,16 @@ namespace Umbraco.Core.Runtime /// public IRuntimeState State => _state; - public IMainDom MainDom { get; private set; } + public IMainDom MainDom { get; } /// - public virtual IFactory Boot(IRegister register) + public virtual IFactory Configure(IRegister register) { + if (register is null) throw new ArgumentNullException(nameof(register)); + // create and register the essential services // ie the bare minimum required to boot - - TypeFinder = GetTypeFinder(); - if (TypeFinder == null) - throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null"); - // the boot loader boots using a container scope, so anything that is PerScope will // be disposed after the boot loader has booted, and anything else will remain. // note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else @@ -135,25 +134,24 @@ namespace Umbraco.Core.Runtime // application environment ConfigureUnhandledException(); - ConfigureApplicationRootPath(); - - Boot(register, timer); + return _factory = Configure(register, timer); } - - return _factory; } /// - /// Boots the runtime within a timer. + /// Configure the runtime within a timer. /// - protected virtual IFactory Boot(IRegister register, DisposableTimer timer) + private IFactory Configure(IRegister register, DisposableTimer timer) { + if (register is null) throw new ArgumentNullException(nameof(register)); + if (timer is null) throw new ArgumentNullException(nameof(timer)); + Composition composition = null; + IFactory factory = null; try { - // throws if not full-trust - _umbracoBootPermissionChecker.ThrowIfNotPermissions(); + // run handlers RuntimeOptions.DoRuntimeBoot(ProfilingLogger); @@ -165,33 +163,14 @@ namespace Umbraco.Core.Runtime var databaseFactory = GetDatabaseFactory(); // type finder/loader - var typeLoader = new TypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger); - - // runtime state - // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' - // as the second one captures the current value (null) and therefore fails - _state = new RuntimeState(Logger, - Configs.Settings(), Configs.Global(), - new Lazy(() => _factory.GetInstance()), - new Lazy(() => _factory.GetInstance()), - UmbracoVersion, HostingEnvironment, BackOfficeInfo) - { - Level = RuntimeLevel.Boot - }; + var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger); // create the composition composition = new Composition(register, typeLoader, ProfilingLogger, _state, Configs, IOHelper, appCaches); - composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator); + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator, HostingEnvironment, BackOfficeInfo); - // run handlers - RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); - - // register runtime-level services - // there should be none, really - this is here "just in case" - Compose(composition); - - // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate - AcquireMainDom(MainDom); + // register ourselves (TODO: Should we put this in RegisterEssentials?) + composition.Register(_ => this, Lifetime.Singleton); // determine our runtime level DetermineRuntimeLevel(databaseFactory, ProfilingLogger); @@ -209,13 +188,7 @@ namespace Umbraco.Core.Runtime composers.Compose(); // create the factory - _factory = composition.CreateFactory(); - - // create & initialize the components - _components = _factory.GetInstance(); - _components.Initialize(); - - + factory = composition.CreateFactory(); } catch (Exception e) { @@ -232,11 +205,11 @@ namespace Umbraco.Core.Runtime // if something goes wrong above, we may end up with no factory // meaning nothing can get the runtime state, etc - so let's try // to make sure we have a factory - if (_factory == null) + if (factory == null) { try { - _factory = composition?.CreateFactory(); + factory = composition?.CreateFactory(); } catch { /* yea */ } } @@ -250,12 +223,27 @@ namespace Umbraco.Core.Runtime // throw a BootFailedException for every requests. } - return _factory; + return factory; } - private IUmbracoVersion GetUmbracoVersion(IGlobalSettings globalSettings) + public void Start() { - return new UmbracoVersion(globalSettings); + // throws if not full-trust + _umbracoBootPermissionChecker.ThrowIfNotPermissions(); + + // run handlers + RuntimeOptions.DoRuntimeEssentials(_factory); + + var hostingEnvironmentLifetime = _factory.TryGetInstance(); + if (hostingEnvironmentLifetime == null) + throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(Start)}"); + + // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate + AcquireMainDom(MainDom, _factory.GetInstance()); + + // create & initialize the components + _components = _factory.GetInstance(); + _components.Initialize(); } protected virtual void ConfigureUnhandledException() @@ -275,20 +263,13 @@ namespace Umbraco.Core.Runtime }; } - protected virtual void ConfigureApplicationRootPath() - { - var path = GetApplicationRootPath(); - if (string.IsNullOrWhiteSpace(path) == false) - IOHelper.SetRootDirectory(path); - } - - private bool AcquireMainDom(IMainDom mainDom) + private bool AcquireMainDom(IMainDom mainDom, IApplicationShutdownRegistry applicationShutdownRegistry) { using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) { try { - return mainDom.IsMainDom; + return mainDom.Acquire(applicationShutdownRegistry); } catch { @@ -347,12 +328,6 @@ namespace Umbraco.Core.Runtime _components?.Terminate(); } - /// - /// Composes the runtime. - /// - public virtual void Compose(Composition composition) - { - } #region Getters @@ -364,14 +339,6 @@ namespace Umbraco.Core.Runtime protected virtual IEnumerable GetComposerTypes(TypeLoader typeLoader) => typeLoader.GetTypes(); - /// - /// Gets a - /// - /// - protected virtual ITypeFinder GetTypeFinder() - => new TypeFinder(Logger); - - /// /// Gets the application caches. /// @@ -382,13 +349,19 @@ namespace Umbraco.Core.Runtime // is overridden by the web runtime return new AppCaches( - new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)), + new DeepCloneAppCache(new ObjectCacheAppCache()), NoAppCache.Instance, - new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)))); + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); } - // by default, returns null, meaning that Umbraco should auto-detect the application root path. - // override and return the absolute path to the Umbraco site/solution, if needed + /// + /// Returns the application path of the site/solution + /// + /// + /// + /// By default is null which means it's not running in any virtual folder. If the site is running in a virtual folder, this + /// can be overridden and the virtual path returned (i.e. /mysite/) + /// protected virtual string GetApplicationRootPath() => null; @@ -397,9 +370,10 @@ namespace Umbraco.Core.Runtime /// /// This is strictly internal, for tests only. protected internal virtual IUmbracoDatabaseFactory GetDatabaseFactory() - => new UmbracoDatabaseFactory(Logger, new Lazy(() => _factory.GetInstance()), Configs, DbProviderFactoryCreator); + => new UmbracoDatabaseFactory(Logger, _globalSettings, _connectionStrings, new Lazy(() => _factory.GetInstance()), DbProviderFactoryCreator); #endregion + } } diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs index 4e1feb221a..85d915b433 100644 --- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs @@ -28,17 +28,17 @@ namespace Umbraco.Core.Runtime private bool _hasError; private object _locker = new object(); - public SqlMainDomLock(ILogger logger, Configs configs, IDbProviderFactoryCreator dbProviderFactoryCreator) + public SqlMainDomLock(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator) { // unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer _lockId = Guid.NewGuid().ToString(); _logger = logger; - _dbFactory = new UmbracoDatabaseFactory( + _dbFactory = new UmbracoDatabaseFactory(_logger, + globalSettings, + connectionStrings, Constants.System.UmbracoConnectionName, - _logger, new Lazy(() => new MapperCollection(Enumerable.Empty())), - configs, dbProviderFactoryCreator - ); + dbProviderFactoryCreator); } public async Task AcquireLockAsync(int millisecondsTimeout) @@ -324,7 +324,7 @@ namespace Umbraco.Core.Runtime { Key = MainDomKey, Value = id, - Updated = DateTime.Now + UpdateDate = DateTime.Now }); } diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs similarity index 64% rename from src/Umbraco.Web/Runtime/WebRuntime.cs rename to src/Umbraco.Infrastructure/Runtime/WebRuntime.cs index 0fc35af2f2..fc2a019023 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs @@ -1,6 +1,4 @@ -using System; -using System.Web; -using Umbraco.Core; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; @@ -9,10 +7,6 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; -using Umbraco.Web.Cache; -using Umbraco.Web.Composing; -using Umbraco.Web.Logging; -using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Web.Runtime { @@ -22,15 +16,12 @@ namespace Umbraco.Web.Runtime /// On top of CoreRuntime, handles all of the web-related runtime aspects of Umbraco. public class WebRuntime : CoreRuntime { - private readonly UmbracoApplicationBase _umbracoApplication; - private BuildManagerTypeFinder _typeFinder; + private readonly IRequestCache _requestCache; /// /// Initializes a new instance of the class. /// - /// public WebRuntime( - UmbracoApplicationBase umbracoApplication, Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, @@ -39,32 +30,17 @@ namespace Umbraco.Web.Runtime IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, IDbProviderFactoryCreator dbProviderFactoryCreator, - IMainDom mainDom): - base(configs, umbracoVersion, ioHelper, logger, profiler ,new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom) + IMainDom mainDom, + ITypeFinder typeFinder, + IRequestCache requestCache, + IUmbracoBootPermissionChecker umbracoBootPermissionChecker): + base(configs, umbracoVersion, ioHelper, logger, profiler ,umbracoBootPermissionChecker, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder) { - _umbracoApplication = umbracoApplication; - - Profiler = GetWebProfiler(); - } - - private IProfiler GetWebProfiler() - { - // create and start asap to profile boot - if (!State.Debug) - { - // should let it be null, that's how MiniProfiler is meant to work, - // but our own IProfiler expects an instance so let's get one - return new VoidProfiler(); - } - - var webProfiler = new WebProfiler(); - webProfiler.Start(); - - return webProfiler; + _requestCache = requestCache; } /// - public override IFactory Boot(IRegister register) + public override IFactory Configure(IRegister register) { var profilingLogger = new ProfilingLogger(Logger, Profiler); @@ -81,7 +57,7 @@ namespace Umbraco.Web.Runtime NetworkHelper.MachineName); Logger.Debug("Runtime: {Runtime}", GetType().FullName); - var factory = Current.Factory = base.Boot(register); + var factory = base.Configure(register); // now (and only now) is the time to switch over to perWebRequest scopes. // up until that point we may not have a request, and scoped services would @@ -97,18 +73,16 @@ namespace Umbraco.Web.Runtime #region Getters - protected override ITypeFinder GetTypeFinder() => _typeFinder ?? (_typeFinder = new BuildManagerTypeFinder(IOHelper, HostingEnvironment, Logger, new BuildManagerTypeFinder.TypeFinderConfig())); - protected override AppCaches GetAppCaches() => new AppCaches( // we need to have the dep clone runtime cache provider to ensure // all entities are cached properly (cloned in and cloned out) - new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache, TypeFinder)), + new DeepCloneAppCache(new ObjectCacheAppCache()), // we need request based cache when running in web-based context - new HttpRequestAppCache(() => HttpContext.Current?.Items, TypeFinder), + _requestCache, new IsolatedCaches(type => // we need to have the dep clone runtime cache provider to ensure // all entities are cached properly (cloned in and cloned out) - new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)))); + new DeepCloneAppCache(new ObjectCacheAppCache()))); #endregion } diff --git a/src/Umbraco.Infrastructure/RuntimeOptions.cs b/src/Umbraco.Infrastructure/RuntimeOptions.cs index 23abd474a4..562a7e3c5c 100644 --- a/src/Umbraco.Infrastructure/RuntimeOptions.cs +++ b/src/Umbraco.Infrastructure/RuntimeOptions.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core public static class RuntimeOptions { private static List> _onBoot; - private static List> _onEssentials; + private static List> _onEssentials; /// /// Executes the RuntimeBoot handlers. @@ -33,13 +33,13 @@ namespace Umbraco.Core /// /// Executes the RuntimeEssentials handlers. /// - internal static void DoRuntimeEssentials(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory) + internal static void DoRuntimeEssentials(IFactory factory) { if (_onEssentials== null) return; foreach (var action in _onEssentials) - action(composition, appCaches, typeLoader, databaseFactory); + action(factory); } /// @@ -64,10 +64,10 @@ namespace Umbraco.Core /// essential things (AppCaches, a TypeLoader, and a database factory) but /// before anything else. /// - public static void OnRuntimeEssentials(Action action) + public static void OnRuntimeEssentials(Action action) { if (_onEssentials == null) - _onEssentials = new List>(); + _onEssentials = new List>(); _onEssentials.Add(action); } } diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs index 5952e73e62..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 @@ -20,50 +20,24 @@ namespace Umbraco.Core public class RuntimeState : IRuntimeState { private readonly ILogger _logger; - private readonly IUmbracoSettingsSection _settings; private readonly IGlobalSettings _globalSettings; private readonly ConcurrentHashSet _applicationUrls = new ConcurrentHashSet(); - private readonly Lazy _mainDom; - private readonly Lazy _serverRegistrar; private readonly IUmbracoVersion _umbracoVersion; - private readonly IHostingEnvironment _hostingEnvironment; private readonly IBackOfficeInfo _backOfficeInfo; /// /// Initializes a new instance of the class. /// - public RuntimeState(ILogger logger, IUmbracoSettingsSection settings, IGlobalSettings globalSettings, - Lazy mainDom, Lazy serverRegistrar, IUmbracoVersion umbracoVersion, - IHostingEnvironment hostingEnvironment, + public RuntimeState(ILogger logger, IGlobalSettings globalSettings, + IUmbracoVersion umbracoVersion, IBackOfficeInfo backOfficeInfo) { _logger = logger; - _settings = settings; _globalSettings = globalSettings; - _mainDom = mainDom; - _serverRegistrar = serverRegistrar; _umbracoVersion = umbracoVersion; - _hostingEnvironment = hostingEnvironment; _backOfficeInfo = backOfficeInfo; - - ApplicationVirtualPath = _hostingEnvironment.ApplicationVirtualPath; } - /// - /// Gets the server registrar. - /// - /// - /// This is NOT exposed in the interface. - /// - private IServerRegistrar ServerRegistrar => _serverRegistrar.Value; - - /// - /// Gets the application MainDom. - /// - /// - /// This is NOT exposed in the interface as MainDom is internal. - /// - public IMainDom MainDom => _mainDom.Value; /// public Version Version => _umbracoVersion.Current; @@ -74,26 +48,14 @@ namespace Umbraco.Core /// public SemVersion SemanticVersion => _umbracoVersion.SemanticVersion; - /// - public bool Debug => _hostingEnvironment.IsDebugMode; - - /// - public bool IsMainDom => MainDom.IsMainDom; - - /// - public ServerRole ServerRole => ServerRegistrar.GetCurrentServerRole(); - /// public Uri ApplicationUrl { get; private set; } /// - public string ApplicationVirtualPath { get; } + public string CurrentMigrationState { get; private set; } /// - public string CurrentMigrationState { get; internal set; } - - /// - public string FinalMigrationState { get; internal set; } + public string FinalMigrationState { get; private set; } /// public RuntimeLevel Level { get; internal set; } = RuntimeLevel.Unknown; @@ -255,7 +217,7 @@ namespace Umbraco.Core Reason = RuntimeLevelReason.UpgradeMigrations; } - protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + private bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) { var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion, _globalSettings)); var stateValueKey = upgrader.StateValueKey; @@ -263,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/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Infrastructure/Scheduling/BackgroundTaskRunner.cs index 0b5b81319f..2cda289591 100644 --- a/src/Umbraco.Infrastructure/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Infrastructure/Scheduling/BackgroundTaskRunner.cs @@ -81,7 +81,7 @@ namespace Umbraco.Web.Scheduling private readonly string _logPrefix; private readonly BackgroundTaskRunnerOptions _options; private readonly ILogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingEnvironment; private readonly object _locker = new object(); private readonly BufferBlock _tasks = new BufferBlock(new DataflowBlockOptions()); @@ -105,7 +105,7 @@ namespace Umbraco.Web.Scheduling /// A logger. /// The hosting environment /// An optional main domain hook. - public BackgroundTaskRunner(ILogger logger, IHostingEnvironment hostingEnvironment, MainDomHook hook = null) + public BackgroundTaskRunner(ILogger logger, IApplicationShutdownRegistry hostingEnvironment, MainDomHook hook = null) : this(typeof(T).FullName, new BackgroundTaskRunnerOptions(), logger, hostingEnvironment, hook) { } @@ -116,7 +116,7 @@ namespace Umbraco.Web.Scheduling /// A logger. /// The hosting environment /// An optional main domain hook. - public BackgroundTaskRunner(string name, ILogger logger, IHostingEnvironment hostingEnvironment, MainDomHook hook = null) + public BackgroundTaskRunner(string name, ILogger logger, IApplicationShutdownRegistry hostingEnvironment, MainDomHook hook = null) : this(name, new BackgroundTaskRunnerOptions(), logger, hostingEnvironment, hook) { } @@ -127,7 +127,7 @@ namespace Umbraco.Web.Scheduling /// A logger. /// The hosting environment /// An optional main domain hook. - public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger logger, IHostingEnvironment hostingEnvironment, MainDomHook hook = null) + public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger logger, IApplicationShutdownRegistry hostingEnvironment, MainDomHook hook = null) : this(typeof(T).FullName, options, logger, hostingEnvironment, hook) { } @@ -139,7 +139,7 @@ namespace Umbraco.Web.Scheduling /// A logger. /// The hosting environment /// An optional main domain hook. - public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger logger, IHostingEnvironment hostingEnvironment, MainDomHook hook = null) + public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger logger, IApplicationShutdownRegistry hostingEnvironment, MainDomHook hook = null) { _options = options ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); diff --git a/src/Umbraco.Infrastructure/Scheduling/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/Scheduling/HealthCheckNotifier.cs index 00bbba8bb8..e7692b851a 100644 --- a/src/Umbraco.Infrastructure/Scheduling/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/Scheduling/HealthCheckNotifier.cs @@ -11,22 +11,27 @@ namespace Umbraco.Web.Scheduling { public class HealthCheckNotifier : RecurringTaskBase { - private readonly IRuntimeState _runtimeState; + private readonly IMainDom _mainDom; private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckNotificationMethodCollection _notifications; private readonly IProfilingLogger _logger; - private readonly IHealthChecks _healthChecksConfig; + private readonly IHealthChecksSettings _healthChecksSettingsConfig; + private readonly IServerRegistrar _serverRegistrar; + private readonly IRuntimeState _runtimeState; public HealthCheckNotifier(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, - IRuntimeState runtimeState, IProfilingLogger logger, IHealthChecks healthChecksConfig) + IMainDom mainDom, IProfilingLogger logger, IHealthChecksSettings healthChecksSettingsConfig, IServerRegistrar serverRegistrar, + IRuntimeState runtimeState) : base(runner, delayMilliseconds, periodMilliseconds) { _healthChecks = healthChecks; _notifications = notifications; - _runtimeState = runtimeState; + _mainDom = mainDom; _logger = logger; - _healthChecksConfig = healthChecksConfig; + _healthChecksSettingsConfig = healthChecksSettingsConfig; + _serverRegistrar = serverRegistrar; + _runtimeState = runtimeState; } public override async Task PerformRunAsync(CancellationToken token) @@ -34,7 +39,7 @@ namespace Umbraco.Web.Scheduling if (_runtimeState.Level != RuntimeLevel.Run) return true; // repeat... - switch (_runtimeState.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.Debug("Does not run on replica servers."); @@ -45,7 +50,7 @@ namespace Umbraco.Web.Scheduling } // ensure we do not run if not main domain, but do NOT lock it - if (_runtimeState.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down @@ -53,7 +58,7 @@ namespace Umbraco.Web.Scheduling using (_logger.DebugDuration("Health checks executing", "Health checks complete")) { - var healthCheckConfig = _healthChecksConfig; + var healthCheckConfig = _healthChecksSettingsConfig; // Don't notify for any checks that are disabled, nor for any disabled // just for notifications diff --git a/src/Umbraco.Infrastructure/Scheduling/LogScrubber.cs b/src/Umbraco.Infrastructure/Scheduling/LogScrubber.cs index e624c4e591..aaf09dbe8f 100644 --- a/src/Umbraco.Infrastructure/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Infrastructure/Scheduling/LogScrubber.cs @@ -11,17 +11,19 @@ namespace Umbraco.Web.Scheduling public class LogScrubber : RecurringTaskBase { - private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; + private readonly IServerRegistrar _serverRegistrar; private readonly IAuditService _auditService; - private readonly IUmbracoSettingsSection _settings; + private readonly ILoggingSettings _settings; private readonly IProfilingLogger _logger; private readonly IScopeProvider _scopeProvider; public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IRuntimeState runtime, IAuditService auditService, IUmbracoSettingsSection settings, IScopeProvider scopeProvider, IProfilingLogger logger) + IMainDom mainDom, IServerRegistrar serverRegistrar, IAuditService auditService, ILoggingSettings settings, IScopeProvider scopeProvider, IProfilingLogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { - _runtime = runtime; + _mainDom = mainDom; + _serverRegistrar = serverRegistrar; _auditService = auditService; _settings = settings; _scopeProvider = scopeProvider; @@ -29,13 +31,13 @@ namespace Umbraco.Web.Scheduling } // maximum age, in minutes - private int GetLogScrubbingMaximumAge(IUmbracoSettingsSection settings) + private int GetLogScrubbingMaximumAge(ILoggingSettings settings) { var maximumAge = 24 * 60; // 24 hours, in minutes try { - if (settings.Logging.MaxLogAge > -1) - maximumAge = settings.Logging.MaxLogAge; + if (settings.MaxLogAge > -1) + maximumAge = settings.MaxLogAge; } catch (Exception ex) { @@ -45,7 +47,7 @@ namespace Umbraco.Web.Scheduling } - public static int GetLogScrubbingInterval(IUmbracoSettingsSection settings, ILogger logger) + public static int GetLogScrubbingInterval() { const int interval = 4 * 60 * 60 * 1000; // 4 hours, in milliseconds return interval; @@ -53,7 +55,7 @@ namespace Umbraco.Web.Scheduling public override bool PerformRun() { - switch (_runtime.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.Debug("Does not run on replica servers."); @@ -64,7 +66,7 @@ namespace Umbraco.Web.Scheduling } // ensure we do not run if not main domain, but do NOT lock it - if (_runtime.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down diff --git a/src/Umbraco.Infrastructure/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/Scheduling/ScheduledPublishing.cs index b074704033..fea16999fd 100644 --- a/src/Umbraco.Infrastructure/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/Scheduling/ScheduledPublishing.cs @@ -10,16 +10,20 @@ namespace Umbraco.Web.Scheduling public class ScheduledPublishing : RecurringTaskBase { private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; + private readonly IServerRegistrar _serverRegistrar; private readonly IContentService _contentService; private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly ILogger _logger; private readonly IServerMessenger _serverMessenger; public ScheduledPublishing(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IRuntimeState runtime, IContentService contentService, IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger) + IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar, IContentService contentService, IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger) : base(runner, delayMilliseconds, periodMilliseconds) { _runtime = runtime; + _mainDom = mainDom; + _serverRegistrar = serverRegistrar; _contentService = contentService; _umbracoContextFactory = umbracoContextFactory; _logger = logger; @@ -31,7 +35,7 @@ namespace Umbraco.Web.Scheduling if (Suspendable.ScheduledPublishing.CanRun == false) return true; // repeat, later - switch (_runtime.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.Debug("Does not run on replica servers."); @@ -42,7 +46,7 @@ namespace Umbraco.Web.Scheduling } // ensure we do not run if not main domain, but do NOT lock it - if (_runtime.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down diff --git a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs similarity index 72% rename from src/Umbraco.Web/Scheduling/SchedulerComponent.cs rename to src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs index 061f42b9ba..3c58cbbde7 100644 --- a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs @@ -25,22 +25,25 @@ namespace Umbraco.Web.Scheduling private const int OneHourMilliseconds = 3600000; private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; + private readonly IServerRegistrar _serverRegistrar; private readonly IContentService _contentService; private readonly IAuditService _auditService; private readonly IProfilingLogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingEnvironment; private readonly IScopeProvider _scopeProvider; private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckNotificationMethodCollection _notifications; private readonly IUmbracoContextFactory _umbracoContextFactory; - private readonly IHealthChecks _healthChecksConfig; - private readonly IUmbracoSettingsSection _umbracoSettingsSection; + private readonly IHealthChecksSettings _healthChecksSettingsConfig; private readonly IIOHelper _ioHelper; private readonly IServerMessenger _serverMessenger; + private readonly IRequestAccessor _requestAccessor; + private readonly ILoggingSettings _loggingSettings; + private readonly IKeepAliveSettings _keepAliveSettings; private BackgroundTaskRunner _keepAliveRunner; private BackgroundTaskRunner _publishingRunner; - private BackgroundTaskRunner _tasksRunner; private BackgroundTaskRunner _scrubberRunner; private BackgroundTaskRunner _fileCleanupRunner; private BackgroundTaskRunner _healthCheckRunner; @@ -49,14 +52,17 @@ namespace Umbraco.Web.Scheduling private object _locker = new object(); private IBackgroundTask[] _tasks; - public SchedulerComponent(IRuntimeState runtime, + public SchedulerComponent(IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar, IContentService contentService, IAuditService auditService, HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, IScopeProvider scopeProvider, IUmbracoContextFactory umbracoContextFactory, IProfilingLogger logger, - IHostingEnvironment hostingEnvironment, IHealthChecks healthChecksConfig, - IUmbracoSettingsSection umbracoSettingsSection, IIOHelper ioHelper, IServerMessenger serverMessenger) + IApplicationShutdownRegistry hostingEnvironment, IHealthChecksSettings healthChecksSettingsConfig, + IIOHelper ioHelper, IServerMessenger serverMessenger, IRequestAccessor requestAccessor, + ILoggingSettings loggingSettings, IKeepAliveSettings keepAliveSettings) { _runtime = runtime; + _mainDom = mainDom; + _serverRegistrar = serverRegistrar; _contentService = contentService; _auditService = auditService; _scopeProvider = scopeProvider; @@ -66,10 +72,12 @@ namespace Umbraco.Web.Scheduling _healthChecks = healthChecks; _notifications = notifications; - _healthChecksConfig = healthChecksConfig ?? throw new ArgumentNullException(nameof(healthChecksConfig)); - _umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection)); + _healthChecksSettingsConfig = healthChecksSettingsConfig ?? throw new ArgumentNullException(nameof(healthChecksSettingsConfig)); _ioHelper = ioHelper; _serverMessenger = serverMessenger; + _requestAccessor = requestAccessor; + _loggingSettings = loggingSettings; + _keepAliveSettings = keepAliveSettings; } public void Initialize() @@ -77,13 +85,12 @@ namespace Umbraco.Web.Scheduling // backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly _keepAliveRunner = new BackgroundTaskRunner("KeepAlive", _logger, _hostingEnvironment); _publishingRunner = new BackgroundTaskRunner("ScheduledPublishing", _logger, _hostingEnvironment); - _tasksRunner = new BackgroundTaskRunner("ScheduledTasks", _logger, _hostingEnvironment); _scrubberRunner = new BackgroundTaskRunner("LogScrubber", _logger, _hostingEnvironment); _fileCleanupRunner = new BackgroundTaskRunner("TempFileCleanup", _logger, _hostingEnvironment); _healthCheckRunner = new BackgroundTaskRunner("HealthCheckNotifier", _logger, _hostingEnvironment); // we will start the whole process when a successful request is made - UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce; + _requestAccessor.RouteAttempt += RegisterBackgroundTasksOnce; } public void Terminate() @@ -97,7 +104,7 @@ namespace Umbraco.Web.Scheduling { case EnsureRoutableOutcome.IsRoutable: case EnsureRoutableOutcome.NotDocumentRequest: - UmbracoModule.RouteAttempt -= RegisterBackgroundTasksOnce; + _requestAccessor.RouteAttempt -= RegisterBackgroundTasksOnce; RegisterBackgroundTasks(); break; } @@ -108,20 +115,19 @@ namespace Umbraco.Web.Scheduling LazyInitializer.EnsureInitialized(ref _tasks, ref _started, ref _locker, () => { _logger.Debug("Initializing the scheduler"); - var settings = _umbracoSettingsSection; var tasks = new List(); - if (settings.KeepAlive.DisableKeepAliveTask == false) + if (_keepAliveSettings.DisableKeepAliveTask == false) { - tasks.Add(RegisterKeepAlive(settings.KeepAlive)); + tasks.Add(RegisterKeepAlive(_keepAliveSettings)); } tasks.Add(RegisterScheduledPublishing()); - tasks.Add(RegisterLogScrubber(settings)); + tasks.Add(RegisterLogScrubber(_loggingSettings)); tasks.Add(RegisterTempFileCleanup()); - var healthCheckConfig = _healthChecksConfig; + var healthCheckConfig = _healthChecksSettingsConfig; if (healthCheckConfig.NotificationSettings.Enabled) tasks.Add(RegisterHealthCheckNotifier(healthCheckConfig, _healthChecks, _notifications, _logger)); @@ -129,11 +135,11 @@ namespace Umbraco.Web.Scheduling }); } - private IBackgroundTask RegisterKeepAlive(IKeepAliveSection keepAliveSection) + private IBackgroundTask RegisterKeepAlive(IKeepAliveSettings keepAliveSettings) { // ping/keepalive // on all servers - var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, keepAliveSection, _logger); + var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, _mainDom, keepAliveSettings, _logger, _serverRegistrar); _keepAliveRunner.TryAdd(task); return task; } @@ -142,42 +148,42 @@ namespace Umbraco.Web.Scheduling { // scheduled publishing/unpublishing // install on all, will only run on non-replica servers - var task = new ScheduledPublishing(_publishingRunner, DefaultDelayMilliseconds, OneMinuteMilliseconds, _runtime, _contentService, _umbracoContextFactory, _logger, _serverMessenger); + var task = new ScheduledPublishing(_publishingRunner, DefaultDelayMilliseconds, OneMinuteMilliseconds, _runtime, _mainDom, _serverRegistrar, _contentService, _umbracoContextFactory, _logger, _serverMessenger); _publishingRunner.TryAdd(task); return task; } - private IBackgroundTask RegisterHealthCheckNotifier(IHealthChecks healthCheckConfig, + private IBackgroundTask RegisterHealthCheckNotifier(IHealthChecksSettings healthCheckSettingsConfig, HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, IProfilingLogger logger) { // If first run time not set, start with just small delay after application start int delayInMilliseconds; - if (string.IsNullOrEmpty(healthCheckConfig.NotificationSettings.FirstRunTime)) + if (string.IsNullOrEmpty(healthCheckSettingsConfig.NotificationSettings.FirstRunTime)) { delayInMilliseconds = DefaultDelayMilliseconds; } else { // Otherwise start at scheduled time - delayInMilliseconds = DateTime.Now.PeriodicMinutesFrom(healthCheckConfig.NotificationSettings.FirstRunTime) * 60 * 1000; + delayInMilliseconds = DateTime.Now.PeriodicMinutesFrom(healthCheckSettingsConfig.NotificationSettings.FirstRunTime) * 60 * 1000; if (delayInMilliseconds < DefaultDelayMilliseconds) { delayInMilliseconds = DefaultDelayMilliseconds; } } - var periodInMilliseconds = healthCheckConfig.NotificationSettings.PeriodInHours * 60 * 60 * 1000; - var task = new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, healthChecks, notifications, _runtime, logger, _healthChecksConfig); + var periodInMilliseconds = healthCheckSettingsConfig.NotificationSettings.PeriodInHours * 60 * 60 * 1000; + var task = new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, healthChecks, notifications, _mainDom, logger, _healthChecksSettingsConfig, _serverRegistrar, _runtime); _healthCheckRunner.TryAdd(task); return task; } - private IBackgroundTask RegisterLogScrubber(IUmbracoSettingsSection settings) + private IBackgroundTask RegisterLogScrubber(ILoggingSettings settings) { // log scrubbing // install on all, will only run on non-replica servers - var task = new LogScrubber(_scrubberRunner, DefaultDelayMilliseconds, LogScrubber.GetLogScrubbingInterval(settings, _logger), _runtime, _auditService, settings, _scopeProvider, _logger); + var task = new LogScrubber(_scrubberRunner, DefaultDelayMilliseconds, LogScrubber.GetLogScrubbingInterval(), _mainDom, _serverRegistrar, _auditService, settings, _scopeProvider, _logger); _scrubberRunner.TryAdd(task); return task; } @@ -189,7 +195,7 @@ namespace Umbraco.Web.Scheduling var task = new TempFileCleanup(_fileCleanupRunner, DefaultDelayMilliseconds, OneHourMilliseconds, new[] { new DirectoryInfo(_ioHelper.MapPath(Constants.SystemDirectories.TempFileUploads)) }, TimeSpan.FromDays(1), //files that are over a day old - _runtime, _logger); + _mainDom, _logger); _scrubberRunner.TryAdd(task); return task; } diff --git a/src/Umbraco.Web/Scheduling/SchedulerComposer.cs b/src/Umbraco.Infrastructure/Scheduling/SchedulerComposer.cs similarity index 100% rename from src/Umbraco.Web/Scheduling/SchedulerComposer.cs rename to src/Umbraco.Infrastructure/Scheduling/SchedulerComposer.cs diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 8f7a0bf958..3b17ae876d 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Scoping internal class Scope : IScope { private readonly ScopeProvider _scopeProvider; - private readonly ICoreDebug _coreDebug; + private readonly ICoreDebugSettings _coreDebugSettings; private readonly IMediaFileSystem _mediaFileSystem; private readonly ILogger _logger; private readonly ITypeFinder _typeFinder; @@ -39,7 +39,7 @@ namespace Umbraco.Core.Scoping // initializes a new scope private Scope(ScopeProvider scopeProvider, - ICoreDebug coreDebug, + ICoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, IScopeContext scopeContext, bool detachable, IsolationLevel isolationLevel = IsolationLevel.Unspecified, @@ -50,7 +50,7 @@ namespace Umbraco.Core.Scoping bool autoComplete = false) { _scopeProvider = scopeProvider; - _coreDebug = coreDebug; + _coreDebugSettings = coreDebugSettings; _mediaFileSystem = mediaFileSystem; _logger = logger; _typeFinder = typeFinder; @@ -118,7 +118,7 @@ namespace Umbraco.Core.Scoping // initializes a new scope public Scope(ScopeProvider scopeProvider, - ICoreDebug coreDebug, + ICoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, bool detachable, IScopeContext scopeContext, IsolationLevel isolationLevel = IsolationLevel.Unspecified, @@ -127,12 +127,12 @@ namespace Umbraco.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebug, mediaFileSystem, logger, typeFinder, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } // initializes a new scope in a nested scopes chain, with its parent public Scope(ScopeProvider scopeProvider, - ICoreDebug coreDebug, + ICoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, IsolationLevel isolationLevel = IsolationLevel.Unspecified, @@ -141,7 +141,7 @@ namespace Umbraco.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebug, mediaFileSystem, logger, typeFinder, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } public Guid InstanceId { get; } = Guid.NewGuid(); @@ -188,7 +188,7 @@ namespace Umbraco.Core.Scoping if (ParentScope != null) return ParentScope.IsolatedCaches; return _isolatedCaches ?? (_isolatedCaches - = new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache(_typeFinder)))); + = new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); } } @@ -494,9 +494,9 @@ namespace Umbraco.Core.Scoping private static bool? _logUncompletedScopes; // caching config - // true if Umbraco.CoreDebug.LogUncompletedScope appSetting is set to "true" + // true if Umbraco.CoreDebugSettings.LogUncompletedScope appSetting is set to "true" private bool LogUncompletedScopes => (_logUncompletedScopes - ?? (_logUncompletedScopes = _coreDebug.LogUncompletedScopes)).Value; + ?? (_logUncompletedScopes = _coreDebugSettings.LogUncompletedScopes)).Value; /// public void ReadLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.ReadLock(Database, lockIds); diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 0dba73b55b..610f308b96 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -26,14 +26,14 @@ namespace Umbraco.Core.Scoping private readonly ITypeFinder _typeFinder; private readonly IRequestCache _requestCache; private readonly FileSystems _fileSystems; - private readonly ICoreDebug _coreDebug; + private readonly ICoreDebugSettings _coreDebugSettings; private readonly IMediaFileSystem _mediaFileSystem; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ICoreDebug coreDebug, IMediaFileSystem mediaFileSystem, ILogger logger, ITypeFinder typeFinder, IRequestCache requestCache) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ICoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ITypeFinder typeFinder, IRequestCache requestCache) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; - _coreDebug = coreDebug; + _coreDebugSettings = coreDebugSettings; _mediaFileSystem = mediaFileSystem; _logger = logger; _typeFinder = typeFinder; @@ -255,7 +255,7 @@ namespace Umbraco.Core.Scoping IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null) { - return new Scope(this, _coreDebug, _mediaFileSystem, _logger, _typeFinder, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); + return new Scope(this, _coreDebugSettings, _mediaFileSystem, _logger, _typeFinder, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); } /// @@ -311,13 +311,13 @@ namespace Umbraco.Core.Scoping { var ambientContext = AmbientContext; var newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _coreDebug, _mediaFileSystem, _logger, _typeFinder, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _logger, _typeFinder, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! SetAmbient(scope, newContext ?? ambientContext); return scope; } - var nested = new Scope(this, _coreDebug, _mediaFileSystem, _logger, _typeFinder, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _logger, _typeFinder, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); SetAmbient(nested, AmbientContext); return nested; } diff --git a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs index 2c964a2723..1946e2041b 100644 --- a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs @@ -18,10 +18,10 @@ namespace Umbraco.Web.Search private readonly IndexRebuilder _indexRebuilder; private readonly IMainDom _mainDom; private readonly IProfilingLogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingEnvironment; private static BackgroundTaskRunner _rebuildOnStartupRunner; - public BackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IHostingEnvironment hostingEnvironment, IndexRebuilder indexRebuilder) + public BackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IApplicationShutdownRegistry hostingEnvironment, IndexRebuilder indexRebuilder) { _mainDom = mainDom; _logger = logger; @@ -32,8 +32,6 @@ namespace Umbraco.Web.Search /// /// Called to rebuild empty indexes on startup /// - /// - /// /// /// public void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0) diff --git a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs index 11b91ffc90..ecaf7354ca 100644 --- a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Examine; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; using Umbraco.Web.Routing; using Umbraco.Web.Trees; @@ -50,6 +51,7 @@ namespace Umbraco.Web.Search /// /// /// + /// /// /// /// A starting point for the search, generally a node id, but for members this is a member type alias @@ -62,7 +64,7 @@ namespace Umbraco.Web.Search string query, UmbracoEntityTypes entityType, int pageSize, - long pageIndex, out long totalFound, string searchFrom = null, bool ignoreUserStartNodes = false) + long pageIndex, out long totalFound, string culture = null, string searchFrom = null, bool ignoreUserStartNodes = false) { var pagedResult = _backOfficeExamineSearcher.Search(query, entityType, pageSize, pageIndex, out totalFound, searchFrom, ignoreUserStartNodes); @@ -73,7 +75,7 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Media: return MediaFromSearchResults(pagedResult); case UmbracoEntityTypes.Document: - return ContentFromSearchResults(pagedResult); + return ContentFromSearchResults(pagedResult, culture); default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); } @@ -145,14 +147,20 @@ namespace Umbraco.Web.Search /// Returns a collection of entities for content based on search results /// /// + /// /// - private IEnumerable ContentFromSearchResults(IEnumerable results) + private IEnumerable ContentFromSearchResults(IEnumerable results, string culture = null) { var defaultLang = _languageService.GetDefaultLanguageIsoCode(); foreach (var result in results) { - var entity = _mapper.Map(result); + var entity = _mapper.Map(result, context => { + if(culture != null) { + context.SetCulture(culture); + } + } + ); var intId = entity.Id.TryConvertTo(); if (intId.Success) @@ -160,7 +168,7 @@ namespace Umbraco.Web.Search //if it varies by culture, return the default language URL if (result.Values.TryGetValue(UmbracoExamineFieldNames.VariesByCultureFieldName, out var varies) && varies == "y") { - entity.AdditionalData["Url"] = _publishedUrlProvider.GetUrl(intId.Result, culture: defaultLang); + entity.AdditionalData["Url"] = _publishedUrlProvider.GetUrl(intId.Result, culture: culture ?? defaultLang); } else { diff --git a/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs index e8fea4c0e1..d69297eb57 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs @@ -415,7 +415,6 @@ namespace Umbraco.Core.Services.Implement var xml = new XElement("macro"); xml.Add(new XElement("name", macro.Name)); xml.Add(new XElement("alias", macro.Alias)); - xml.Add(new XElement("macroType", macro.MacroType)); xml.Add(new XElement("macroSource", macro.MacroSource)); xml.Add(new XElement("useInEditor", macro.UseInEditor.ToString())); xml.Add(new XElement("dontRender", macro.DontRender.ToString())); 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/NotificationService.cs b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs index c8b8e617c9..d6c30b24c6 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs @@ -28,16 +28,16 @@ namespace Umbraco.Core.Services.Implement private readonly ILocalizationService _localizationService; private readonly INotificationsRepository _notificationsRepository; private readonly IGlobalSettings _globalSettings; - private readonly IContentSection _contentSection; + private readonly IContentSettings _contentSettings; private readonly ILogger _logger; private readonly IIOHelper _ioHelper; public NotificationService(IScopeProvider provider, IUserService userService, IContentService contentService, ILocalizationService localizationService, - ILogger logger, IIOHelper ioHelper, INotificationsRepository notificationsRepository, IGlobalSettings globalSettings, IContentSection contentSection) + ILogger logger, IIOHelper ioHelper, INotificationsRepository notificationsRepository, IGlobalSettings globalSettings, IContentSettings contentSettings) { _notificationsRepository = notificationsRepository; _globalSettings = globalSettings; - _contentSection = contentSection; + _contentSettings = contentSettings; _uowProvider = provider ?? throw new ArgumentNullException(nameof(provider)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); @@ -302,7 +302,7 @@ namespace Umbraco.Core.Services.Implement if (content.ContentType.VariesByNothing()) { - if (!_contentSection.DisableHtmlEmail) + if (!_contentSettings.DisableHtmlEmail) { //create the HTML summary for invariant content @@ -344,7 +344,7 @@ namespace Umbraco.Core.Services.Implement { //it's variant, so detect what cultures have changed - if (!_contentSection.DisableHtmlEmail) + if (!_contentSettings.DisableHtmlEmail) { //Create the HTML based summary (ul of culture names) @@ -406,13 +406,13 @@ namespace Umbraco.Core.Services.Implement summary.ToString()); // create the mail message - var mail = new MailMessage(_contentSection.NotificationEmailAddress, mailingUser.Email); + var mail = new MailMessage(_contentSettings.NotificationEmailAddress, mailingUser.Email); // populate the message mail.Subject = createSubject((mailingUser, subjectVars)); - if (_contentSection.DisableHtmlEmail) + if (_contentSettings.DisableHtmlEmail) { mail.IsBodyHtml = false; mail.Body = createBody((user: mailingUser, body: bodyVars, false)); 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 f09e917e94..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; } /// @@ -1179,7 +1177,7 @@ namespace Umbraco.Core.Services.Implement /// /// Occurs before Save /// - internal static event TypedEventHandler> SavingUserGroup; + public static event TypedEventHandler> SavingUserGroup; /// /// Occurs after Save diff --git a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs index 948304e4e4..c915013162 100644 --- a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs @@ -26,12 +26,13 @@ namespace Umbraco.Core.Sync // but only processes instructions coming from remote servers, // thus ensuring that instructions run only once // - public class DatabaseServerMessenger : ServerMessengerBase + public class DatabaseServerMessenger : ServerMessengerBase, IDatabaseServerMessenger { - private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; private readonly ManualResetEvent _syncIdle; private readonly object _locko = new object(); private readonly IProfilingLogger _profilingLogger; + private readonly IServerRegistrar _serverRegistrar; private readonly IHostingEnvironment _hostingEnvironment; private readonly CacheRefresherCollection _cacheRefreshers; private readonly ISqlContext _sqlContext; @@ -46,14 +47,15 @@ namespace Umbraco.Core.Sync public DatabaseServerMessengerOptions Options { get; } public DatabaseServerMessenger( - IRuntimeState runtime, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, + IMainDom mainDom, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IServerRegistrar serverRegistrar, bool distributedEnabled, DatabaseServerMessengerOptions options, IHostingEnvironment hostingEnvironment, CacheRefresherCollection cacheRefreshers) : base(distributedEnabled) { ScopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); _sqlContext = sqlContext; - _runtime = runtime; + _mainDom = mainDom; _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); + _serverRegistrar = serverRegistrar; _hostingEnvironment = hostingEnvironment; _cacheRefreshers = cacheRefreshers; Logger = proflog; @@ -126,11 +128,7 @@ namespace Umbraco.Core.Sync const int weight = 10; - //TODO Why do we have interface if we expect to be exact type!!!? - // if (!(_runtime is RuntimeState runtime)) - // throw new NotSupportedException($"Unsupported IRuntimeState implementation {_runtime.GetType().FullName}, expecting {typeof(RuntimeState).FullName}."); - - var registered = _runtime.MainDom.Register( + var registered = _mainDom.Register( () => { lock (_locko) @@ -266,7 +264,7 @@ namespace Umbraco.Core.Sync _lastPruned = _lastSync; - switch (_runtime.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Single: case ServerRole.Master: diff --git a/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs b/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs index 3b47cabbaf..b65254b181 100644 --- a/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs +++ b/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs @@ -4,7 +4,6 @@ using System.Linq; using Newtonsoft.Json; using Umbraco.Composing; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; namespace Umbraco.Core.Sync diff --git a/src/Umbraco.Infrastructure/Trees/TreeNode.cs b/src/Umbraco.Infrastructure/Trees/TreeNode.cs index cc130b1b97..1b6945bcdd 100644 --- a/src/Umbraco.Infrastructure/Trees/TreeNode.cs +++ b/src/Umbraco.Infrastructure/Trees/TreeNode.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Composing; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Trees @@ -112,7 +113,8 @@ namespace Umbraco.Web.Models.Trees return Current.IOHelper.ResolveUrl("~" + Icon.TrimStart('~')); //legacy icon path - return string.Format("{0}images/umbraco/{1}", Current.Configs.Global().Path.EnsureEndsWith("/"), Icon); + var backOfficePath = Current.Configs.Global().GetUmbracoMvcArea(Current.HostingEnvironment); + return string.Format("{0}images/umbraco/{1}", backOfficePath.EnsureEndsWith("/"), Icon); } } diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index 41fd2bfe75..a3f8953ac3 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -6,10 +6,16 @@ - + + + + + + + @@ -37,10 +43,23 @@ + + True + True + Resources.resx + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + @@ -54,6 +73,12 @@ <_Parameter1>Umbraco.Tests.Benchmarks + + <_Parameter1>Umbraco.Tests.Integration + + + <_Parameter1>Umbraco.Tests.Common + diff --git a/src/Umbraco.Infrastructure/WebAssets/BackOfficeJavaScriptInitializer.cs b/src/Umbraco.Infrastructure/WebAssets/BackOfficeJavaScriptInitializer.cs new file mode 100644 index 0000000000..18279e35ba --- /dev/null +++ b/src/Umbraco.Infrastructure/WebAssets/BackOfficeJavaScriptInitializer.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; + +namespace Umbraco.Web.WebAssets +{ + /// + /// Creates a JavaScript block to initialize the back office + /// + public class BackOfficeJavaScriptInitializer + { + // deal with javascript functions inside of json (not a supported json syntax) + private const string PrefixJavaScriptObject = "@@@@"; + private static readonly Regex JsFunctionParser = new Regex($"(\"{PrefixJavaScriptObject}(.*?)\")+", + RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled); + + // replace tokens in the js main + private static readonly Regex Token = new Regex("(\"##\\w+?##\")", RegexOptions.Compiled); + + + /// + /// Gets the JS initialization script to boot the back office application + /// + /// + /// + /// The angular module name to boot + /// + /// + /// + /// + public static string GetJavascriptInitialization(IEnumerable scripts, string angularModule, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) + { + var jarray = new StringBuilder(); + jarray.AppendLine("["); + var first = true; + foreach (var file in scripts) + { + if (first) first = false; + else jarray.AppendLine(","); + jarray.Append("\""); + jarray.Append(file); + jarray.Append("\""); + + } + jarray.Append("]"); + + return WriteScript(jarray.ToString(), hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath), angularModule); + } + + /// + /// Parses the JsResources.Main and replaces the replacement tokens accordingly + /// + /// + /// + /// + /// + internal static string WriteScript(string scripts, string umbracoPath, string angularModule) + { + var count = 0; + var replacements = new[] { scripts, umbracoPath, angularModule }; + // replace, catering for the special syntax when we have + // js function() objects contained in the json + + return Token.Replace(Resources.Main, match => + { + var replacement = replacements[count++]; + return JsFunctionParser.Replace(replacement, "$2"); + }); + } + + } +} diff --git a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs new file mode 100644 index 0000000000..fcb27f7189 --- /dev/null +++ b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Core.Manifest; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.WebAssets; + +namespace Umbraco.Web.WebAssets +{ + public class BackOfficeWebAssets + { + public const string UmbracoPreviewJsBundleName = "umbraco-preview-js"; + public const string UmbracoPreviewCssBundleName = "umbraco-preview-css"; + public const string UmbracoCssBundleName = "umbraco-backoffice-css"; + public const string UmbracoInitCssBundleName = "umbraco-backoffice-init-css"; + public const string UmbracoJsBundleName = "umbraco-backoffice-js"; + public const string UmbracoTinyMceJsBundleName = "umbraco-tinymce-js"; + public const string UmbracoUpgradeCssBundleName = "umbraco-authorize-upgrade-css"; + + private readonly IRuntimeMinifier _runtimeMinifier; + private readonly IManifestParser _parser; + private readonly IGlobalSettings _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly PropertyEditorCollection _propertyEditorCollection; + + public BackOfficeWebAssets( + IRuntimeMinifier runtimeMinifier, + IManifestParser parser, + PropertyEditorCollection propertyEditorCollection, + IHostingEnvironment hostingEnvironment, + IGlobalSettings globalSettings) + { + _runtimeMinifier = runtimeMinifier; + _parser = parser; + _propertyEditorCollection = propertyEditorCollection; + _hostingEnvironment = hostingEnvironment; + _globalSettings = globalSettings; + } + + public void CreateBundles() + { + // Create bundles + + _runtimeMinifier.CreateCssBundle(UmbracoInitCssBundleName, + FormatPaths("lib/bootstrap-social/bootstrap-social.css", + "assets/css/umbraco.css", + "lib/font-awesome/css/font-awesome.min.css")); + + _runtimeMinifier.CreateCssBundle(UmbracoUpgradeCssBundleName, + FormatPaths("assets/css/umbraco.css", + "lib/bootstrap-social/bootstrap-social.css", + "lib/font-awesome/css/font-awesome.min.css")); + + _runtimeMinifier.CreateCssBundle(UmbracoPreviewCssBundleName, + FormatPaths("assets/css/canvasdesigner.css")); + + _runtimeMinifier.CreateJsBundle(UmbracoPreviewJsBundleName, + FormatPaths(GetScriptsForPreview())); + + _runtimeMinifier.CreateJsBundle(UmbracoTinyMceJsBundleName, + FormatPaths(GetScriptsForTinyMce())); + + var propertyEditorAssets = ScanPropertyEditors() + .GroupBy(x => x.AssetType) + .ToDictionary(x => x.Key, x => x.Select(c => c.FilePath)); + + _runtimeMinifier.CreateJsBundle( + UmbracoJsBundleName, + FormatPaths( + GetScriptsForBackoffice( + propertyEditorAssets.TryGetValue(AssetType.Javascript, out var scripts) ? scripts : Enumerable.Empty()))); + + _runtimeMinifier.CreateCssBundle( + UmbracoCssBundleName, + FormatPaths( + GetStylesheetsForBackoffice( + propertyEditorAssets.TryGetValue(AssetType.Css, out var styles) ? styles : Enumerable.Empty()))); + } + + /// + /// Returns scripts used to load the back office + /// + /// + private string[] GetScriptsForBackoffice(IEnumerable propertyEditorScripts) + { + var umbracoInit = GetInitBackOfficeScripts(); + var scripts = new HashSet(); + foreach (var script in umbracoInit) + scripts.Add(script); + foreach (var script in _parser.Manifest.Scripts) + scripts.Add(script); + foreach (var script in propertyEditorScripts) + scripts.Add(script); + + return scripts.ToArray(); + } + + /// + /// Returns the list of scripts for back office initialization + /// + /// + private IEnumerable GetInitBackOfficeScripts() + { + var resources = JsonConvert.DeserializeObject(Resources.JsInitialize); + return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString()); + } + + /// + /// Returns stylesheets used to load the back office + /// + /// + private string[] GetStylesheetsForBackoffice(IEnumerable propertyEditorStyles) + { + var stylesheets = new HashSet(); + + foreach (var script in _parser.Manifest.Stylesheets) + stylesheets.Add(script); + foreach (var stylesheet in propertyEditorStyles) + stylesheets.Add(stylesheet); + + return stylesheets.ToArray(); + } + + /// + /// Returns the scripts used for tinymce + /// + /// + private string[] GetScriptsForTinyMce() + { + var resources = JsonConvert.DeserializeObject(Resources.TinyMceInitialize); + return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString()).ToArray(); + } + + /// + /// Returns the scripts used for preview + /// + /// + private string[] GetScriptsForPreview() + { + var resources = JsonConvert.DeserializeObject(Resources.PreviewInitialize); + return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString()).ToArray(); + } + + /// + /// Re-format asset paths to be absolute paths + /// + /// + /// + private string[] FormatPaths(params string[] assets) + { + var umbracoPath = _globalSettings.GetUmbracoMvcArea(_hostingEnvironment); + + return assets + .Where(x => x.IsNullOrWhiteSpace() == false) + .Select(x => !x.StartsWith("/") && Uri.IsWellFormedUriString(x, UriKind.Relative) + // most declarations with be made relative to the /umbraco folder, so things + // like lib/blah/blah.js so we need to turn them into absolutes here + ? umbracoPath.EnsureStartsWith('/').TrimEnd("/") + x.EnsureStartsWith('/') + : x).ToArray(); + } + + /// + /// Returns the web asset paths to load for property editors that have the attribute applied + /// + /// + private IEnumerable ScanPropertyEditors() + { + return _propertyEditorCollection + .SelectMany(x => x.GetType().GetCustomAttributes(false)); + } + } +} diff --git a/src/Umbraco.Web/JavaScript/JsInitialize.js b/src/Umbraco.Infrastructure/WebAssets/JsInitialize.js similarity index 100% rename from src/Umbraco.Web/JavaScript/JsInitialize.js rename to src/Umbraco.Infrastructure/WebAssets/JsInitialize.js diff --git a/src/Umbraco.Web/JavaScript/Main.js b/src/Umbraco.Infrastructure/WebAssets/Main.js similarity index 100% rename from src/Umbraco.Web/JavaScript/Main.js rename to src/Umbraco.Infrastructure/WebAssets/Main.js diff --git a/src/Umbraco.Infrastructure/WebAssets/PreviewInitialize.js b/src/Umbraco.Infrastructure/WebAssets/PreviewInitialize.js new file mode 100644 index 0000000000..764e354d5f --- /dev/null +++ b/src/Umbraco.Infrastructure/WebAssets/PreviewInitialize.js @@ -0,0 +1,14 @@ +[ + 'lib/jquery/jquery.min.js', + 'lib/angular/angular.js', + 'lib/underscore/underscore-min.js', + 'lib/umbraco/Extensions.js', + 'js/app.js', + 'js/umbraco.resources.js', + 'js/umbraco.services.js', + 'js/umbraco.interceptors.js', + 'ServerVariables', + 'lib/signalr/jquery.signalR.js', + 'BackOffice/signalr/hubs', + 'js/umbraco.preview.js' +] diff --git a/src/Umbraco.Infrastructure/WebAssets/PropertyEditorAssetAttribute.cs b/src/Umbraco.Infrastructure/WebAssets/PropertyEditorAssetAttribute.cs new file mode 100644 index 0000000000..74bb792653 --- /dev/null +++ b/src/Umbraco.Infrastructure/WebAssets/PropertyEditorAssetAttribute.cs @@ -0,0 +1,24 @@ +using System; +using Umbraco.Core.WebAssets; + +namespace Umbraco.Web.WebAssets +{ + /// + /// Indicates that the property editor requires this asset be loaded when the back office is loaded + /// + /// + /// This wraps a CDF asset + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class PropertyEditorAssetAttribute : Attribute + { + public AssetType AssetType { get; } + public string FilePath { get; } + + public PropertyEditorAssetAttribute(AssetType assetType, string filePath) + { + AssetType = assetType; + FilePath = filePath; + } + } +} diff --git a/src/Umbraco.Web/JavaScript/Resources.Designer.cs b/src/Umbraco.Infrastructure/WebAssets/Resources.Designer.cs similarity index 85% rename from src/Umbraco.Web/JavaScript/Resources.Designer.cs rename to src/Umbraco.Infrastructure/WebAssets/Resources.Designer.cs index f11839f6ca..f48386c570 100644 --- a/src/Umbraco.Web/JavaScript/Resources.Designer.cs +++ b/src/Umbraco.Infrastructure/WebAssets/Resources.Designer.cs @@ -8,7 +8,8 @@ // //------------------------------------------------------------------------------ -namespace Umbraco.Web.JavaScript { +namespace Umbraco.Web.WebAssets +{ using System; @@ -19,7 +20,7 @@ namespace Umbraco.Web.JavaScript { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -39,7 +40,7 @@ namespace Umbraco.Web.JavaScript { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Umbraco.Web.JavaScript.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Umbraco.Infrastructure.WebAssets.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; @@ -76,9 +77,9 @@ namespace Umbraco.Web.JavaScript { /// /// 'lib/angular-route/angular-route.js', /// 'lib/angular-cookies/angular-cookies.js', + /// 'lib/angular-aria/angular-aria.min.js', /// 'lib/angular-touch/angular-touch.js', - /// 'lib/angular-sanitize/angular-sanitize.js', - /// 'lib/an [rest of string was truncated]";. + /// 'lib/angula [rest of string was truncated]";. /// internal static string JsInitialize { get { @@ -149,9 +150,18 @@ namespace Umbraco.Web.JavaScript { /// /// Looks up a localized string similar to [ - /// '../lib/tinymce/tinymce.min.js', - ///] - ///. + /// 'lib/tinymce/tinymce.min.js', + /// + /// 'lib/tinymce/plugins/paste/plugin.min.js', + /// 'lib/tinymce/plugins/anchor/plugin.min.js', + /// 'lib/tinymce/plugins/charmap/plugin.min.js', + /// 'lib/tinymce/plugins/table/plugin.min.js', + /// 'lib/tinymce/plugins/lists/plugin.min.js', + /// 'lib/tinymce/plugins/advlist/plugin.min.js', + /// 'lib/tinymce/plugins/hr/plugin.min.js', + /// 'lib/tinymce/plugins/autolink/plugin.min.js', + /// 'lib/tinymce/plugins/directionality/plugin.min.js', + /// 'lib/tinymce/plugins/t [rest of string was truncated]";. /// internal static string TinyMceInitialize { get { diff --git a/src/Umbraco.Infrastructure/WebAssets/Resources.resx b/src/Umbraco.Infrastructure/WebAssets/Resources.resx new file mode 100644 index 0000000000..476acc461b --- /dev/null +++ b/src/Umbraco.Infrastructure/WebAssets/Resources.resx @@ -0,0 +1,37 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + JsInitialize.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + Main.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + + PreviewInitialize.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + ServerVariables.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + TinyMceInitialize.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + diff --git a/src/Umbraco.Infrastructure/WebAssets/RuntimeMinifierExtensions.cs b/src/Umbraco.Infrastructure/WebAssets/RuntimeMinifierExtensions.cs new file mode 100644 index 0000000000..072afa5816 --- /dev/null +++ b/src/Umbraco.Infrastructure/WebAssets/RuntimeMinifierExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Core.WebAssets; + +namespace Umbraco.Web.WebAssets +{ + public static class RuntimeMinifierExtensions + { + /// + /// Returns the JavaScript to load the back office's assets + /// + /// + public static async Task GetScriptForLoadingBackOfficeAsync(this IRuntimeMinifier minifier, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) + { + var files = await minifier.GetAssetPathsAsync(BackOfficeWebAssets.UmbracoJsBundleName); + var result = BackOfficeJavaScriptInitializer.GetJavascriptInitialization(files, "umbraco", globalSettings, hostingEnvironment); + result += await GetStylesheetInitializationAsync(minifier); + + return result; + } + + /// + /// Gets the back office css bundle paths and formats a JS call to lazy load them + /// + private static async Task GetStylesheetInitializationAsync(IRuntimeMinifier minifier) + { + var files = await minifier.GetAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName); + var sb = new StringBuilder(); + foreach (var file in files) + sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file); + return sb.ToString(); + } + + } +} diff --git a/src/Umbraco.Web/JavaScript/ServerVariables.js b/src/Umbraco.Infrastructure/WebAssets/ServerVariables.js similarity index 100% rename from src/Umbraco.Web/JavaScript/ServerVariables.js rename to src/Umbraco.Infrastructure/WebAssets/ServerVariables.js diff --git a/src/Umbraco.Web/JavaScript/ServerVariablesParser.cs b/src/Umbraco.Infrastructure/WebAssets/ServerVariablesParser.cs similarity index 86% rename from src/Umbraco.Web/JavaScript/ServerVariablesParser.cs rename to src/Umbraco.Infrastructure/WebAssets/ServerVariablesParser.cs index 8f27f58143..618c3d7703 100644 --- a/src/Umbraco.Web/JavaScript/ServerVariablesParser.cs +++ b/src/Umbraco.Infrastructure/WebAssets/ServerVariablesParser.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Newtonsoft.Json.Linq; -namespace Umbraco.Web.JavaScript +namespace Umbraco.Web.WebAssets { public class ServerVariablesParser { @@ -13,7 +13,7 @@ namespace Umbraco.Web.JavaScript internal const string Token = "##Variables##"; - internal static string Parse(Dictionary items) + public static string Parse(Dictionary items) { var vars = Resources.ServerVariables; diff --git a/src/Umbraco.Web/JavaScript/TinyMceInitialize.js b/src/Umbraco.Infrastructure/WebAssets/TinyMceInitialize.js similarity index 100% rename from src/Umbraco.Web/JavaScript/TinyMceInitialize.js rename to src/Umbraco.Infrastructure/WebAssets/TinyMceInitialize.js diff --git a/src/Umbraco.Infrastructure/WebAssets/WebAssetsComponent.cs b/src/Umbraco.Infrastructure/WebAssets/WebAssetsComponent.cs new file mode 100644 index 0000000000..54ba82a1fc --- /dev/null +++ b/src/Umbraco.Infrastructure/WebAssets/WebAssetsComponent.cs @@ -0,0 +1,25 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Web.WebAssets +{ + public sealed class WebAssetsComponent : IComponent + { + private readonly BackOfficeWebAssets _backOfficeWebAssets; + + public WebAssetsComponent(BackOfficeWebAssets backOfficeWebAssets) + { + _backOfficeWebAssets = backOfficeWebAssets; + } + + public void Initialize() + { + // TODO: This will eagerly scan types but we don't really want that, however it works for now. + // We don't actually have to change Smidge or anything, all we have to do is postpone this call for when the first request on the website arrives. + _backOfficeWebAssets.CreateBundles(); + } + + public void Terminate() + { + } + } +} diff --git a/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs b/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs new file mode 100644 index 0000000000..bea056c69c --- /dev/null +++ b/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs @@ -0,0 +1,14 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.WebAssets +{ + public sealed class WebAssetsComposer : ComponentComposer + { + public override void Compose(Composition composition) + { + base.Compose(composition); + composition.RegisterUnique(); + } + } +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs index 1fdb64c62a..75affe09e7 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs @@ -1,4 +1,4 @@ -using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Core.Configuration; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.ModelsBuilder.Embedded.BackOffice diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs index 15ca2cca24..1e96e64df8 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using Umbraco.Core.Configuration; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs index 25ddc838e8..6e22313474 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs @@ -1,5 +1,6 @@ using System.Text; -using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Configuration; +using Umbraco.Core.Configuration; namespace Umbraco.ModelsBuilder.Embedded.BackOffice { diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs index 9dc1ea6c20..fcd42908e7 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs @@ -1,4 +1,4 @@ -using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Core.Configuration; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.ModelsBuilder.Embedded.BackOffice diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs index 8d0a98eeab..2e249eed4d 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs @@ -1,4 +1,4 @@ -using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Core.Configuration; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.ModelsBuilder.Embedded.BackOffice diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs index 1d9de265e9..17b694de56 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs @@ -3,9 +3,10 @@ using System.Net; using System.Net.Http; using System.Runtime.Serialization; using System.Web.Hosting; +using Umbraco.Configuration; +using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; using Umbraco.ModelsBuilder.Embedded.Building; -using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Editors; using Umbraco.Web.WebApi.Filters; diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs index ffd56d4312..f64e5ed1ce 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Core; +using Umbraco.Core.Configuration; namespace Umbraco.ModelsBuilder.Embedded.Building { @@ -18,6 +19,8 @@ namespace Umbraco.ModelsBuilder.Embedded.Building internal abstract class Builder { + + private readonly IList _typeModels; protected Dictionary ModelsMap { get; } = new Dictionary(); @@ -209,7 +212,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building // use configured else fallback to default return string.IsNullOrWhiteSpace(Config.ModelsNamespace) - ? ModelsBuilderConfig.DefaultModelsNamespace + ? Constants.ModelsBuilder.DefaultModelsNamespace : Config.ModelsNamespace; } diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs index 8a3bc5a5b5..648a2e76fa 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs @@ -1,6 +1,7 @@ using System.IO; using System.Text; -using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; namespace Umbraco.ModelsBuilder.Embedded.Building { @@ -9,20 +10,23 @@ namespace Umbraco.ModelsBuilder.Embedded.Building private readonly UmbracoServices _umbracoService; private readonly IModelsBuilderConfig _config; private readonly OutOfDateModelsStatus _outOfDateModels; + private readonly IIOHelper _ioHelper; - public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config, OutOfDateModelsStatus outOfDateModels) + public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config, OutOfDateModelsStatus outOfDateModels, IIOHelper ioHelper) { _umbracoService = umbracoService; _config = config; _outOfDateModels = outOfDateModels; + _ioHelper = ioHelper; } internal void GenerateModels() { - if (!Directory.Exists(_config.ModelsDirectory)) - Directory.CreateDirectory(_config.ModelsDirectory); + var modelsDirectory = _config.ModelsDirectoryAbsolute(_ioHelper); + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); - foreach (var file in Directory.GetFiles(_config.ModelsDirectory, "*.generated.cs")) + foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) File.Delete(file); var typeModels = _umbracoService.GetAllTypes(); @@ -33,7 +37,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building { var sb = new StringBuilder(); builder.Generate(sb, typeModel); - var filename = Path.Combine(_config.ModelsDirectory, typeModel.ClrName + ".generated.cs"); + var filename = Path.Combine(modelsDirectory, typeModel.ClrName + ".generated.cs"); File.WriteAllText(filename, sb.ToString()); } diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs index d1190a0374..723ee10f35 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Core.Configuration; namespace Umbraco.ModelsBuilder.Embedded.Building { @@ -94,7 +94,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building // private static void WriteGeneratedCodeAttribute(StringBuilder sb, string tabs) { - sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder\", \"{1}\")]\n", tabs, ApiVersion.Current.Version); + sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder.Embedded\", \"{1}\")]\n", tabs, ApiVersion.Current.Version); } private void WriteContentType(StringBuilder sb, TypeModel type) diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs index a93df97806..0ffad1c5bc 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs @@ -14,7 +14,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building sb.Append("// \n"); sb.Append("// This code was generated by a tool.\n"); sb.Append("//\n"); - sb.AppendFormat("// Umbraco.ModelsBuilder v{0}\n", ApiVersion.Current.Version); + sb.AppendFormat("// Umbraco.ModelsBuilder.Embedded v{0}\n", ApiVersion.Current.Version); sb.Append("//\n"); sb.Append("// Changes to this file will be lost if the code is regenerated.\n"); sb.Append("// \n"); diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index d856cae1e7..32cfd3057e 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -4,16 +4,17 @@ using System.Reflection; using System.Web; using System.Web.Mvc; using System.Web.Routing; +using Umbraco.Configuration; +using Umbraco.Core.Configuration; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.ModelsBuilder.Embedded.BackOffice; -using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web; -using Umbraco.Web.JavaScript; using Umbraco.Web.Mvc; +using Umbraco.Web.WebAssets; namespace Umbraco.ModelsBuilder.Embedded.Compose { diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index bb8a3f7e18..e582301740 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -1,12 +1,11 @@ using System.Linq; using System.Reflection; +using Umbraco.Core.Configuration; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder.Embedded.Building; -using Umbraco.ModelsBuilder.Embedded.Configuration; -using Umbraco.Web; namespace Umbraco.ModelsBuilder.Embedded.Compose { @@ -20,23 +19,19 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose { var isLegacyModelsBuilderInstalled = IsLegacyModelsBuilderInstalled(); - - composition.Configs.Add(() => new ModelsBuilderConfig(composition.IOHelper)); - if (isLegacyModelsBuilderInstalled) { ComposeForLegacyModelsBuilder(composition); return; } - composition.Components().Append(); composition.Register(Lifetime.Singleton); composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); - + if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive) ComposeForLiveModels(composition); else if (composition.Configs.ModelsBuilder().EnableFactory) diff --git a/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs index d634547a49..d625c754c5 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs @@ -1,5 +1,4 @@ using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Embedded.Configuration; namespace Umbraco.ModelsBuilder.Embedded { diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs index d4b4636563..61d39cd373 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs @@ -1,10 +1,10 @@ using System; using System.Threading; -using System.Web.Hosting; +using Umbraco.Core.Configuration; +using Umbraco.Configuration; using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.ModelsBuilder.Embedded.Building; -using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Cache; namespace Umbraco.ModelsBuilder.Embedded diff --git a/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs index a692f633a5..f8f6e8c7bc 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs @@ -1,17 +1,20 @@ using System; using System.IO; using System.Text; -using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; namespace Umbraco.ModelsBuilder.Embedded { public sealed class ModelsGenerationError { private readonly IModelsBuilderConfig _config; + private readonly IIOHelper _ioHelper; - public ModelsGenerationError(IModelsBuilderConfig config) + public ModelsGenerationError(IModelsBuilderConfig config, IIOHelper ioHelper) { _config = config; + _ioHelper = ioHelper; } public void Clear() @@ -56,7 +59,7 @@ namespace Umbraco.ModelsBuilder.Embedded private string GetErrFile() { - var modelsDirectory = _config.ModelsDirectory; + var modelsDirectory = _config.ModelsDirectoryAbsolute(_ioHelper); if (!Directory.Exists(modelsDirectory)) return null; diff --git a/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs index 5425c31c77..b8105eeef2 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs @@ -1,5 +1,6 @@ using System.IO; -using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Web.Cache; namespace Umbraco.ModelsBuilder.Embedded @@ -7,10 +8,12 @@ namespace Umbraco.ModelsBuilder.Embedded public sealed class OutOfDateModelsStatus { private readonly IModelsBuilderConfig _config; + private readonly IIOHelper _ioHelper; - public OutOfDateModelsStatus(IModelsBuilderConfig config) + public OutOfDateModelsStatus(IModelsBuilderConfig config, IIOHelper ioHelper) { _config = config; + _ioHelper = ioHelper; } internal void Install() @@ -25,7 +28,7 @@ namespace Umbraco.ModelsBuilder.Embedded private string GetFlagPath() { - var modelsDirectory = _config.ModelsDirectory; + var modelsDirectory = _config.ModelsDirectoryAbsolute(_ioHelper); if (!Directory.Exists(modelsDirectory)) Directory.CreateDirectory(modelsDirectory); return Path.Combine(modelsDirectory, "ood.flag"); diff --git a/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs index 68c149adde..5fa17d3c77 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs @@ -2,7 +2,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("Umbraco.ModelsBuilder")] +[assembly: AssemblyTitle("Umbraco.ModelsBuilder.Embedded")] [assembly: AssemblyDescription("Umbraco ModelsBuilder")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyProduct("Umbraco CMS")] diff --git a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs index fe28817cba..3cfefa77ce 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs @@ -11,12 +11,13 @@ using System.Threading; using System.Web; using System.Web.Compilation; using System.Web.WebPages.Razor; +using Umbraco.Core.Configuration; using Umbraco.Core; using Umbraco.Core.Hosting; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder.Embedded.Building; -using Umbraco.ModelsBuilder.Embedded.Configuration; using File = System.IO.File; namespace Umbraco.ModelsBuilder.Embedded @@ -41,31 +42,39 @@ namespace Umbraco.ModelsBuilder.Embedded private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" }; private readonly IModelsBuilderConfig _config; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingLifetime; + private readonly IIOHelper _ioHelper; private readonly ModelsGenerationError _errors; - public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, IModelsBuilderConfig config, IHostingEnvironment hostingEnvironment) + public PureLiveModelFactory( + Lazy umbracoServices, + IProfilingLogger logger, + IModelsBuilderConfig config, + IHostingEnvironment hostingEnvironment, + IApplicationShutdownRegistry hostingLifetime, + IIOHelper ioHelper) { _umbracoServices = umbracoServices; _logger = logger; _config = config; - _hostingEnvironment = hostingEnvironment; - _errors = new ModelsGenerationError(config); + _hostingLifetime = hostingLifetime; + _ioHelper = ioHelper; + _errors = new ModelsGenerationError(config, ioHelper); _ver = 1; // zero is for when we had no version _skipver = -1; // nothing to skip RazorBuildProvider.CodeGenerationStarted += RazorBuildProvider_CodeGenerationStarted; - if (!_hostingEnvironment.IsHosted) return; + if (!hostingEnvironment.IsHosted) return; - var modelsDirectory = _config.ModelsDirectory; + var modelsDirectory = _config.ModelsDirectoryAbsolute(_ioHelper); if (!Directory.Exists(modelsDirectory)) Directory.CreateDirectory(modelsDirectory); // BEWARE! if the watcher is not properly released then for some reason the // BuildManager will start confusing types - using a 'registered object' here // though we should probably plug into Umbraco's MainDom - which is internal - _hostingEnvironment.RegisterObject(this); + _hostingLifetime.RegisterObject(this); _watcher = new FileSystemWatcher(modelsDirectory); _watcher.Changed += WatcherOnChanged; _watcher.EnableRaisingEvents = true; @@ -208,7 +217,7 @@ namespace Umbraco.ModelsBuilder.Embedded _hasModels = false; _pendingRebuild = true; - var modelsDirectory = _config.ModelsDirectory; + var modelsDirectory = _config.ModelsDirectoryAbsolute(_ioHelper); if (!Directory.Exists(modelsDirectory)) Directory.CreateDirectory(modelsDirectory); @@ -330,7 +339,7 @@ namespace Umbraco.ModelsBuilder.Embedded private Assembly GetModelsAssembly(bool forceRebuild) { - var modelsDirectory = _config.ModelsDirectory; + var modelsDirectory = _config.ModelsDirectoryAbsolute(_ioHelper); if (!Directory.Exists(modelsDirectory)) Directory.CreateDirectory(modelsDirectory); @@ -552,7 +561,7 @@ namespace Umbraco.ModelsBuilder.Embedded private string GenerateModelsCode(IList typeModels) { - var modelsDirectory = _config.ModelsDirectory; + var modelsDirectory = _config.ModelsDirectoryAbsolute(_ioHelper); if (!Directory.Exists(modelsDirectory)) Directory.CreateDirectory(modelsDirectory); @@ -669,7 +678,7 @@ namespace Umbraco.ModelsBuilder.Embedded { _watcher.EnableRaisingEvents = false; _watcher.Dispose(); - _hostingEnvironment.UnregisterObject(this); + _hostingLifetime.UnregisterObject(this); } #endregion diff --git a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index 83667bdbd8..a03862326d 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -59,10 +59,6 @@ - - - - @@ -109,10 +105,6 @@ {3ae7bf57-966b-45a5-910a-954d7c554441} Umbraco.Infrastructure - - {9b95eef7-63fe-4432-8c63-166be9c1a929} - Umbraco.Web.BackOffice - {651e1350-91b6-44b7-bd60-7207006d7003} Umbraco.Web @@ -123,6 +115,5 @@ 5.2.7 - \ No newline at end of file diff --git a/src/Umbraco.Persistance.SqlCe/SqlCeSyntaxProvider.cs b/src/Umbraco.Persistance.SqlCe/SqlCeSyntaxProvider.cs index 2ed0fb878c..7abcc60c86 100644 --- a/src/Umbraco.Persistance.SqlCe/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Persistance.SqlCe/SqlCeSyntaxProvider.cs @@ -55,6 +55,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax } public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.RepeatableRead; + public override string DbProvider => "SqlServerCE"; public override string FormatColumnRename(string tableName, string oldName, string newName) { 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/DataSource/BTree.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs index 910c0ca737..ad7cc72eec 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs @@ -1,12 +1,13 @@ using System.Configuration; using CSharpTest.Net.Collections; using CSharpTest.Net.Serialization; +using Umbraco.Core.Configuration; namespace Umbraco.Web.PublishedCache.NuCache.DataSource { internal class BTree { - public static BPlusTree GetTree(string filepath, bool exists) + public static BPlusTree GetTree(string filepath, bool exists, INuCacheSettings settings) { var keySerializer = new PrimitiveSerializer(); var valueSerializer = new ContentNodeKitSerializer(); @@ -19,7 +20,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource CachePolicy = CachePolicy.None, // default is 4096, min 2^9 = 512, max 2^16 = 64K - FileBlockSize = GetBlockSize(), + FileBlockSize = GetBlockSize(settings), // other options? }; @@ -32,11 +33,11 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource return tree; } - private static int GetBlockSize() + private static int GetBlockSize(INuCacheSettings settings) { var blockSize = 4096; - var appSetting = ConfigurationManager.AppSettings["Umbraco.Web.PublishedCache.NuCache.BTree.BlockSize"]; + var appSetting = settings.BTreeBlockSize; if (appSetting == null) return blockSize; 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.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 68b7ee596a..09f486b5d9 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -49,10 +49,10 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IPublishedModelFactory _publishedModelFactory; private readonly IDefaultCultureAccessor _defaultCultureAccessor; private readonly UrlSegmentProviderCollection _urlSegmentProviders; - private readonly ITypeFinder _typeFinder; private readonly IHostingEnvironment _hostingEnvironment; private readonly IShortStringHelper _shortStringHelper; private readonly IIOHelper _ioHelper; + private readonly INuCacheSettings _config; // volatile because we read it with no lock private volatile bool _isReady; @@ -87,10 +87,10 @@ namespace Umbraco.Web.PublishedCache.NuCache IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory, UrlSegmentProviderCollection urlSegmentProviders, - ITypeFinder typeFinder, IHostingEnvironment hostingEnvironment, IShortStringHelper shortStringHelper, - IIOHelper ioHelper) + IIOHelper ioHelper, + INuCacheSettings config) : base(publishedSnapshotAccessor, variationContextAccessor) { //if (Interlocked.Increment(ref _singletonCheck) > 1) @@ -107,10 +107,10 @@ namespace Umbraco.Web.PublishedCache.NuCache _defaultCultureAccessor = defaultCultureAccessor; _globalSettings = globalSettings; _urlSegmentProviders = urlSegmentProviders; - _typeFinder = typeFinder; _hostingEnvironment = hostingEnvironment; _shortStringHelper = shortStringHelper; _ioHelper = ioHelper; + _config = config; // we need an Xml serializer here so that the member cache can support XPath, // for members this is done by navigating the serialized-to-xml member @@ -197,8 +197,8 @@ namespace Umbraco.Web.PublishedCache.NuCache _localMediaDbExists = File.Exists(localMediaDbPath); // if both local databases exist then GetTree will open them, else new databases will be created - _localContentDb = BTree.GetTree(localContentDbPath, _localContentDbExists); - _localMediaDb = BTree.GetTree(localMediaDbPath, _localMediaDbExists); + _localContentDb = BTree.GetTree(localContentDbPath, _localContentDbExists, _config); + _localMediaDb = BTree.GetTree(localMediaDbPath, _localMediaDbExists, _config); _logger.Info("Registered with MainDom, localContentDbExists? {LocalContentDbExists}, localMediaDbExists? {LocalMediaDbExists}", _localContentDbExists, _localMediaDbExists); } @@ -1204,7 +1204,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentGen = contentSnap.Gen; _mediaGen = mediaSnap.Gen; _domainGen = domainSnap.Gen; - elementsCache = _elementsCache = new FastDictionaryAppCache(_typeFinder); + elementsCache = _elementsCache = new FastDictionaryAppCache(); } } @@ -1309,7 +1309,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var member = args.Entity; // refresh the edited data - OnRepositoryRefreshed(db, member, true); + OnRepositoryRefreshed(db, member, false); } private void OnRepositoryRefreshed(IUmbracoDatabase db, IContentBase content, bool published) diff --git a/src/Umbraco.TestData/UmbracoTestDataController.cs b/src/Umbraco.TestData/UmbracoTestDataController.cs index 522d4f6a40..09f9177982 100644 --- a/src/Umbraco.TestData/UmbracoTestDataController.cs +++ b/src/Umbraco.TestData/UmbracoTestDataController.cs @@ -33,8 +33,8 @@ namespace Umbraco.TestData private readonly PropertyEditorCollection _propertyEditors; private readonly IShortStringHelper _shortStringHelper; - public UmbracoTestDataController(IScopeProvider scopeProvider, PropertyEditorCollection propertyEditors, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper) - : base(umbracoContextAccessor, databaseFactory, services, appCaches, logger, profilingLogger, umbracoHelper) + public UmbracoTestDataController(IScopeProvider scopeProvider, PropertyEditorCollection propertyEditors, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, IShortStringHelper shortStringHelper) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, logger, profilingLogger) { _scopeProvider = scopeProvider; _propertyEditors = propertyEditors; @@ -223,7 +223,7 @@ namespace Umbraco.TestData { var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias); content.SetValue("review", faker.Rant.Review()); - content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); ; + content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); Services.ContentService.Save(content); diff --git a/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs new file mode 100644 index 0000000000..1571f63500 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs @@ -0,0 +1,32 @@ +using BenchmarkDotNet.Attributes; +using System; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Tests.Benchmarks.Config; + +namespace Umbraco.Tests.Benchmarks +{ + [MediumRunJob] + [MemoryDiagnoser] + public class TypeFinderBenchmarks + { + + [Benchmark(Baseline = true)] + public void WithGetReferencingAssembliesCheck() + { + var typeFinder1 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash()); + var found = typeFinder1.FindClassesOfType().Count(); + } + + [Benchmark] + public void WithoutGetReferencingAssembliesCheck() + { + var typeFinder2 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash()); + typeFinder2.QueryWithReferencingAssemblies = false; + var found = typeFinder2.FindClassesOfType().Count(); + } + + + } +} diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 7566d8ab85..84ec535b9d 100644 --- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -61,6 +61,7 @@ + diff --git a/src/Umbraco.Tests.Common/Assertions.cs b/src/Umbraco.Tests.Common/Assertions.cs new file mode 100644 index 0000000000..0f99a6a091 --- /dev/null +++ b/src/Umbraco.Tests.Common/Assertions.cs @@ -0,0 +1,35 @@ +using LightInject; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Umbraco.Tests.Common.Composing; + +namespace Umbraco.Tests.Common +{ + public class Assertions + { + public static void AssertContainer(ServiceContainer container, bool reportOnly = false) + { + var results = container.Validate().ToList(); + foreach (var resultGroup in results.GroupBy(x => x.Severity).OrderBy(x => x.Key)) + { + Console.WriteLine($"{resultGroup.Key}: {resultGroup.Count()}"); + } + + foreach (var resultGroup in results.GroupBy(x => x.Severity).OrderBy(x => x.Key)) + { + foreach (var result in resultGroup) + { + Console.WriteLine(); + Console.Write(result.ToText()); + } + } + + if (!reportOnly) + Assert.AreEqual(0, results.Count); + } + + } +} diff --git a/src/Umbraco.Tests.Common/Builders/BuilderBase.cs b/src/Umbraco.Tests.Common/Builders/BuilderBase.cs new file mode 100644 index 0000000000..d8fc048d1b --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/BuilderBase.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders +{ + public abstract class BuilderBase + { + public abstract T Build(); + } +} diff --git a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs new file mode 100644 index 0000000000..fcd9691e1f --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Tests.Common.Builders +{ + public abstract class ChildBuilderBase : BuilderBase + { + private readonly TParent _parentBuilder; + + protected ChildBuilderBase(TParent parentBuilder) + { + _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 new file mode 100644 index 0000000000..3e0b9b96a9 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Tests.Common.Builders +{ + public class ConfigurationEditorBuilder : ChildBuilderBase + { + private IDictionary _defaultConfiguration; + + + public ConfigurationEditorBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + + public ConfigurationEditorBuilder WithDefaultConfiguration(IDictionary defaultConfiguration) + { + _defaultConfiguration = defaultConfiguration; + return this; + } + + public override IConfigurationEditor Build() + { + var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + + return new ConfigurationEditor() + { + DefaultConfiguration = defaultConfiguration, + }; + } + + } +} diff --git a/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs new file mode 100644 index 0000000000..b3c3ff1b7e --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using Moq; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; + +namespace Umbraco.Tests.Common.Builders +{ + public class DataEditorBuilder : ChildBuilderBase + { + private readonly ConfigurationEditorBuilder> _explicitConfigurationEditorBuilder; + private readonly DataValueEditorBuilder> _explicitValueEditorBuilder; + private IDictionary _defaultConfiguration; + + public DataEditorBuilder(TParent parentBuilder) : base(parentBuilder) + { + _explicitConfigurationEditorBuilder = new ConfigurationEditorBuilder>(this); + _explicitValueEditorBuilder = new DataValueEditorBuilder>(this); + } + + public DataEditorBuilder WithDefaultConfiguration(IDictionary defaultConfiguration) + { + _defaultConfiguration = defaultConfiguration; + return this; + } + + public ConfigurationEditorBuilder> AddExplicitConfigurationEditorBuilder() => + _explicitConfigurationEditorBuilder; + + public DataValueEditorBuilder> AddExplicitValueEditorBuilder() => + _explicitValueEditorBuilder; + + public override IDataEditor Build() + { + var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + var explicitConfigurationEditor = _explicitConfigurationEditorBuilder.Build(); + var explicitValueEditor = _explicitValueEditorBuilder.Build(); + + return new DataEditor( + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of() + ) + { + DefaultConfiguration = defaultConfiguration, + ExplicitConfigurationEditor = explicitConfigurationEditor, + ExplicitValueEditor = explicitValueEditor + }; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs new file mode 100644 index 0000000000..de7b8d9a97 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs @@ -0,0 +1,166 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class DataTypeBuilder + : BuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreatorIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder, + IWithNameBuilder, + IWithParentIdBuilder, + IWithTrashedBuilder, + IWithLevelBuilder, + IWithPathBuilder, + IWithSortOrderBuilder + { + private readonly DataEditorBuilder _dataEditorBuilder; + private int? _id; + private int? _parentId; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private DateTime? _deleteDate; + private string _name; + private bool? _trashed; + // private object _configuration; + private int? _level; + private string _path; + private int? _creatorId; + private ValueStorageType? _databaseType; + private int? _sortOrder; + + public DataTypeBuilder() + { + _dataEditorBuilder = new DataEditorBuilder(this); + } + + // public DataTypeBuilder WithConfiguration(object configuration) + // { + // _configuration = configuration; + // return this; + // } + + public DataTypeBuilder WithDatabaseType(ValueStorageType databaseType) + { + _databaseType = databaseType; + return this; + } + + public DataEditorBuilder AddEditor() + { + return _dataEditorBuilder; + } + + public override DataType Build() + { + var editor = _dataEditorBuilder.Build(); + var parentId = _parentId ?? -1; + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; + var name = _name ?? Guid.NewGuid().ToString(); + // var configuration = _configuration ?? editor.GetConfigurationEditor().DefaultConfigurationObject; + var level = _level ?? 0; + var path = _path ?? string.Empty; + var creatorId = _creatorId ?? 1; + var databaseType = _databaseType ?? ValueStorageType.Ntext; + var sortOrder = _sortOrder ?? 0; + + return new DataType(editor, parentId) + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate, + Name = name, + Trashed = _trashed ?? false, + Level = level, + Path = path, + CreatorId = creatorId, + DatabaseType = databaseType, + SortOrder = sortOrder, + }; + } + + 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; + } + + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; + } + + string IWithNameBuilder.Name + { + 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/DataValueEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs new file mode 100644 index 0000000000..3d0b518ee7 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs @@ -0,0 +1,66 @@ +using System; +using Moq; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; + +namespace Umbraco.Tests.Common.Builders +{ + public class DataValueEditorBuilder : ChildBuilderBase + { + private string _configuration; + private string _view; + private bool? _hideLabel; + private string _valueType; + + + public DataValueEditorBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + public DataValueEditorBuilder WithConfiguration(string configuration) + { + _configuration = configuration; + return this; + } + + public DataValueEditorBuilder WithView(string view) + { + _view = view; + return this; + } + + public DataValueEditorBuilder WithHideLabel(bool hideLabel) + { + _hideLabel = hideLabel; + return this; + } + + public DataValueEditorBuilder WithValueType(string valueType) + { + _valueType = valueType; + return this; + } + + public override IDataValueEditor Build() + { + var configuration = _configuration ?? null; + var view = _view ?? null; + var hideLabel = _hideLabel ?? false; + var valueType = _valueType ?? Guid.NewGuid().ToString(); + + return new DataValueEditor( + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of() + ) + { + Configuration = configuration, + View = view, + HideLabel = hideLabel, + ValueType = valueType, + }; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs new file mode 100644 index 0000000000..206bccba80 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs @@ -0,0 +1,112 @@ +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 DictionaryItemBuilder + : BuilderBase, + IWithIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder, + IWithKeyBuilder + { + private readonly List _translationBuilders = + new List(); + + private DateTime? _createDate; + private DateTime? _deleteDate; + private int? _id; + private string _itemKey; + private Guid? _key; + private Guid? _parentId; + private DateTime? _updateDate; + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + public override DictionaryItem Build() + { + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var parentId = _parentId ?? null; + var itemKey = _itemKey ?? Guid.NewGuid().ToString(); + + var result = new DictionaryItem(itemKey) + { + Translations = _translationBuilders.Select(x => x.Build()), + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate, + Id = id, + ParentId = parentId, + Key = key, + }; + return result; + } + + public DictionaryItemBuilder WithParentId(Guid parentId) + { + _parentId = parentId; + return this; + } + + public DictionaryItemBuilder WithItemKey(string itemKey) + { + _itemKey = itemKey; + return this; + } + + public DictionaryTranslationBuilder AddTranslation() + { + var builder = new DictionaryTranslationBuilder(this); + + _translationBuilders.Add(builder); + + return builder; + } + + public DictionaryItemBuilder WithRandomTranslations(int count) + { + for (var i = 0; i < count; i++) + { + AddTranslation().Done(); + } + + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs new file mode 100644 index 0000000000..d2f9d4bf02 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs @@ -0,0 +1,89 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class DictionaryTranslationBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder, + IWithKeyBuilder + { + private readonly LanguageBuilder _languageBuilder; + private readonly Guid? _uniqueId = null; + private DateTime? _createDate; + private DateTime? _deleteDate; + private int? _id; + private Guid? _key; + private DateTime? _updateDate; + private string _value; + + public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) : base(parentBuilder) + { + _languageBuilder = new LanguageBuilder(this); + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + public override IDictionaryTranslation Build() + { + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + + var result = new DictionaryTranslation( + _languageBuilder.Build(), + _value ?? Guid.NewGuid().ToString(), + _uniqueId ?? key) + { + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate, + Id = id + }; + + return result; + } + + public LanguageBuilder AddLanguage() => _languageBuilder; + + public DictionaryTranslationBuilder WithValue(string value) + { + _value = value; + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs new file mode 100644 index 0000000000..c105a8bded --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs @@ -0,0 +1,120 @@ +using System; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders.Extensions +{ + public static class BuilderExtensions + { + public static T WithId(this T builder, int id) + where T : IWithIdBuilder + { + builder.Id = id; + 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 + { + builder.CreateDate = createDate; + return builder; + } + + public static T WithUpdateDate(this T builder, DateTime updateDate) + where T : IWithUpdateDateBuilder + { + builder.UpdateDate = updateDate; + return builder; + } + + public static T WithDeleteDate(this T builder, DateTime deleteDate) + where T : IWithDeleteDateBuilder + { + builder.DeleteDate = deleteDate; + return builder; + } + + public static T WithAlias(this T builder, string alias) + where T : IWithAliasBuilder + { + builder.Alias = alias; + return builder; + } + + public static T WithName(this T builder, string name) + where T : IWithNameBuilder + { + builder.Name = name; + return builder; + } + + public static T WithKey(this T builder, Guid key) + where T : IWithKeyBuilder + { + 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/GlobalSettingsBuilder.cs b/src/Umbraco.Tests.Common/Builders/GlobalSettingsBuilder.cs new file mode 100644 index 0000000000..56e242146c --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/GlobalSettingsBuilder.cs @@ -0,0 +1,249 @@ +using Umbraco.Core.Configuration; + +namespace Umbraco.Tests.Common.Builders +{ + public class GlobalSettingsBuilder : GlobalSettingsBuilder + { + public GlobalSettingsBuilder() : base(null) + { + } + } + + public class GlobalSettingsBuilder : ChildBuilderBase + { + private string _configurationStatus; + private string _databaseFactoryServerVersion; + private string _defaultUiLanguage; + private bool? _disableElectionForSingleServer; + private bool? _hideTopLevelNodeFromPath; + private bool? _installEmptyDatabase; + private bool? _installMissingDatabase; + private bool? _isSmtpServerConfigured; + private string _path; + private string _registerType; + private string _reservedPaths; + private string _reservedUrls; + private int? _timeOutInMinutes; + private string _umbracoCssPath; + private string _umbracoMediaPath; + private string _umbracoPath; + private string _umbracoScriptsPath; + private string _mainDomLock; + private string _noNodesViewPath; + private bool? _useHttps; + private int? _versionCheckPeriod; + private readonly SmtpSettingsBuilder> _smtpSettingsBuilder; + + + public GlobalSettingsBuilder(TParent parentBuilder) : base(parentBuilder) + { + _smtpSettingsBuilder = new SmtpSettingsBuilder>(this); + } + + public SmtpSettingsBuilder> AddSmtpSettings() => _smtpSettingsBuilder; + + public GlobalSettingsBuilder WithConfigurationStatus(string configurationStatus) + { + _configurationStatus = configurationStatus; + return this; + } + + public GlobalSettingsBuilder WithDatabaseFactoryServerVersion(string databaseFactoryServerVersion) + { + _databaseFactoryServerVersion = databaseFactoryServerVersion; + return this; + } + + public GlobalSettingsBuilder WithDefaultUiLanguage(string defaultUiLanguage) + { + _defaultUiLanguage = defaultUiLanguage; + return this; + } + + public GlobalSettingsBuilder WithDisableElectionForSingleServer(bool disableElectionForSingleServer) + { + _disableElectionForSingleServer = disableElectionForSingleServer; + return this; + } + + public GlobalSettingsBuilder WithHideTopLevelNodeFromPath(bool hideTopLevelNodeFromPath) + { + _hideTopLevelNodeFromPath = hideTopLevelNodeFromPath; + return this; + } + + public GlobalSettingsBuilder WithInstallEmptyDatabase(bool installEmptyDatabase) + { + _installEmptyDatabase = installEmptyDatabase; + return this; + } + + public GlobalSettingsBuilder WithInstallMissingDatabase(bool installMissingDatabase) + { + _installMissingDatabase = installMissingDatabase; + return this; + } + + public GlobalSettingsBuilder WithIsSmtpServerConfigured(bool isSmtpServerConfigured) + { + _isSmtpServerConfigured = isSmtpServerConfigured; + return this; + } + + public GlobalSettingsBuilder WithPath(string path) + { + _path = path; + return this; + } + + public GlobalSettingsBuilder WithRegisterType(string registerType) + { + _registerType = registerType; + return this; + } + + public GlobalSettingsBuilder WithReservedPaths(string reservedPaths) + { + _reservedPaths = reservedPaths; + return this; + } + + public GlobalSettingsBuilder WithReservedUrls(string reservedUrls) + { + _reservedUrls = reservedUrls; + return this; + } + + public GlobalSettingsBuilder WithUmbracoPath(string umbracoPath) + { + _umbracoPath = umbracoPath; + return this; + } + + public GlobalSettingsBuilder WithUseHttps(bool useHttps) + { + _useHttps = useHttps; + return this; + } + + public GlobalSettingsBuilder WithUmbracoCssPath(string umbracoCssPath) + { + _umbracoCssPath = umbracoCssPath; + return this; + } + + public GlobalSettingsBuilder WithUmbracoMediaPath(string umbracoMediaPath) + { + _umbracoMediaPath = umbracoMediaPath; + return this; + } + + public GlobalSettingsBuilder WithUmbracoScriptsPath(string umbracoScriptsPath) + { + _umbracoScriptsPath = umbracoScriptsPath; + return this; + } + + public GlobalSettingsBuilder WithMainDomLock(string mainDomLock) + { + _mainDomLock = mainDomLock; + return this; + } + + public GlobalSettingsBuilder WithNoNodesViewPath(string noNodesViewPath) + { + _noNodesViewPath = noNodesViewPath; + return this; + } + public GlobalSettingsBuilder WithVersionCheckPeriod(int versionCheckPeriod) + { + _versionCheckPeriod = versionCheckPeriod; + return this; + } + + public GlobalSettingsBuilder WithTimeOutInMinutes(int timeOutInMinutes) + { + _timeOutInMinutes = timeOutInMinutes; + return this; + } + + public override IGlobalSettings Build() + { + var configurationStatus = _configurationStatus ?? "9.0.0"; + var databaseFactoryServerVersion = _databaseFactoryServerVersion ?? null; + var defaultUiLanguage = _defaultUiLanguage ?? "en"; + var disableElectionForSingleServer = _disableElectionForSingleServer ?? false; + var hideTopLevelNodeFromPath = _hideTopLevelNodeFromPath ?? false; + var installEmptyDatabase = _installEmptyDatabase ?? false; + var installMissingDatabase = _installMissingDatabase ?? false; + var isSmtpServerConfigured = _isSmtpServerConfigured ?? false; + var path = _path ?? "/umbraco"; + var registerType = _registerType ?? null; + var reservedPaths = _reservedPaths ?? "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; + var reservedUrls = _reservedUrls ?? "~/config/splashes/noNodes.aspx,~/.well-known,"; + var umbracoPath = _umbracoPath ?? "~/umbraco"; + var useHttps = _useHttps ?? false; + var umbracoCssPath = _umbracoCssPath ?? "~/css"; + var umbracoMediaPath = _umbracoMediaPath ?? "~/media"; + var umbracoScriptsPath = _umbracoScriptsPath ?? "~/scripts"; + var versionCheckPeriod = _versionCheckPeriod ?? 0; + var timeOutInMinutes = _timeOutInMinutes ?? 20; + var smtpSettings = _smtpSettingsBuilder.Build(); + var mainDomLock = _mainDomLock ?? string.Empty; + var noNodesViewPath = _noNodesViewPath ?? "~/config/splashes/NoNodes.cshtml"; + + + return new TestGlobalSettings + { + ConfigurationStatus = configurationStatus, + DatabaseFactoryServerVersion = databaseFactoryServerVersion, + DefaultUILanguage = defaultUiLanguage, + DisableElectionForSingleServer = disableElectionForSingleServer, + HideTopLevelNodeFromPath = hideTopLevelNodeFromPath, + InstallEmptyDatabase = installEmptyDatabase, + InstallMissingDatabase = installMissingDatabase, + IsSmtpServerConfigured = isSmtpServerConfigured, + Path = path, + RegisterType = registerType, + ReservedPaths = reservedPaths, + ReservedUrls = reservedUrls, + UmbracoPath = umbracoPath, + UseHttps = useHttps, + UmbracoCssPath = umbracoCssPath, + UmbracoMediaPath = umbracoMediaPath, + UmbracoScriptsPath = umbracoScriptsPath, + VersionCheckPeriod = versionCheckPeriod, + TimeOutInMinutes = timeOutInMinutes, + SmtpSettings = smtpSettings, + MainDomLock = mainDomLock, + NoNodesViewPath = noNodesViewPath, + }; + } + + private class TestGlobalSettings : IGlobalSettings + { + public string ReservedUrls { get; set; } + public string ReservedPaths { get; set; } + public string Path { get; set; } + public string ConfigurationStatus { get; set; } + public int TimeOutInMinutes { get; set; } + public string DefaultUILanguage { get; set; } + public bool HideTopLevelNodeFromPath { get; set; } + public bool UseHttps { get; set; } + public int VersionCheckPeriod { get; set; } + public string UmbracoPath { get; set; } + public string UmbracoCssPath { get; set; } + public string UmbracoScriptsPath { get; set; } + public string UmbracoMediaPath { get; set; } + public bool IsSmtpServerConfigured { get; set; } + public ISmtpSettings SmtpSettings { get; set; } + public bool InstallMissingDatabase { get; set; } + public bool InstallEmptyDatabase { get; set; } + public bool DisableElectionForSingleServer { get; set; } + public string RegisterType { get; set; } + public string DatabaseFactoryServerVersion { get; set; } + public string MainDomLock { get; set; } + public string NoNodesViewPath { get; set; } + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs new file mode 100644 index 0000000000..0391061a84 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IBuildPropertyGroups + { + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs new file mode 100644 index 0000000000..e0eb9e19d5 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IBuildPropertyTypes + { + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs new file mode 100644 index 0000000000..78bbbddec9 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithAliasBuilder + { + string Alias { get; set; } + } +} 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/IWithCreateDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs new file mode 100644 index 0000000000..47acaa9a52 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithCreateDateBuilder + { + DateTime? CreateDate { 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/IWithCultureInfoBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs new file mode 100644 index 0000000000..a60fe3c23c --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs @@ -0,0 +1,9 @@ +using System.Globalization; + +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithCultureInfoBuilder + { + CultureInfo CultureInfo { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs new file mode 100644 index 0000000000..0fdeb6d69d --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithDeleteDateBuilder + { + DateTime? DeleteDate { 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/IWithIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs new file mode 100644 index 0000000000..c13343df15 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithIdBuilder + { + int? Id { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs new file mode 100644 index 0000000000..a71bd2d114 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithKeyBuilder + { + Guid? Key { 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/IWithNameBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs new file mode 100644 index 0000000000..d2ccb8dbbc --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithNameBuilder + { + string Name { 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/Interfaces/IWithUpdateDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs new file mode 100644 index 0000000000..80a5aa4f61 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithUpdateDateBuilder + { + DateTime? UpdateDate { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs b/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs new file mode 100644 index 0000000000..ae60920c9c --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs @@ -0,0 +1,120 @@ +using System; +using System.Globalization; +using Moq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class LanguageBuilder : LanguageBuilder + { + public LanguageBuilder() : base(null) + { + } + } + + public class LanguageBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder, + IWithCultureInfoBuilder + { + private DateTime? _createDate; + private CultureInfo _cultureInfo; + private DateTime? _deleteDate; + private int? _fallbackLanguageId; + private int? _id; + private bool? _isDefault; + private bool? _isMandatory; + private Guid? _key; + private DateTime? _updateDate; + + public LanguageBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + CultureInfo IWithCultureInfoBuilder.CultureInfo + { + get => _cultureInfo; + set => _cultureInfo = value; + } + + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + public override ILanguage Build() + { + var cultureInfo = _cultureInfo ?? CultureInfo.GetCultureInfo("en-US"); + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; + var fallbackLanguageId = _fallbackLanguageId ?? null; + var isDefault = _isDefault ?? false; + var isMandatory = _isMandatory ?? false; + + return new Language(Mock.Of(), cultureInfo.Name) + { + Id = _id ?? 1, + CultureName = cultureInfo.TwoLetterISOLanguageName, + IsoCode = new RegionInfo(cultureInfo.LCID).Name, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate, + IsDefault = isDefault, + IsMandatory = isMandatory, + FallbackLanguageId = fallbackLanguageId + }; + } + + public LanguageBuilder WithIsDefault(bool isDefault) + { + _isDefault = isDefault; + return this; + } + + public LanguageBuilder WithIsMandatory(bool isMandatory) + { + _isMandatory = isMandatory; + return this; + } + + public LanguageBuilder WithFallbackLanguageId(int fallbackLanguageId) + { + _fallbackLanguageId = fallbackLanguageId; + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs new file mode 100644 index 0000000000..3925ac642b --- /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..d5a145331e --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs @@ -0,0 +1,200 @@ +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, + IBuildPropertyGroups, + 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) + .WithId(99) + .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 IMemberType 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/PropertyBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs new file mode 100644 index 0000000000..6cb7a431f2 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs @@ -0,0 +1,71 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class PropertyBuilder + : BuilderBase, + IBuildPropertyTypes, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder + { + private PropertyTypeBuilder _propertyTypeBuilder; + + private int? _id; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + + public PropertyTypeBuilder AddPropertyType() + { + var builder = new PropertyTypeBuilder(this); + _propertyTypeBuilder = builder; + return builder; + } + + public override IProperty Build() + { + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + + // Needs to be within collection to support publishing. + var propertyTypeCollection = new PropertyTypeCollection(true, new[] { _propertyTypeBuilder.Build() }); + + return new Property(id, propertyTypeCollection[0]) + { + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + }; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = 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..5df61dd072 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs @@ -0,0 +1,112 @@ +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 : PropertyGroupBuilder + { + public PropertyGroupBuilder() : base(null) + { + } + } + + public class NullPropertyGroupBuilderParent : IBuildPropertyGroups + { + } + + public class PropertyGroupBuilder + : ChildBuilderBase, + IBuildPropertyTypes, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithNameBuilder, + IWithSortOrderBuilder where TParent: IBuildPropertyGroups + { + private readonly List>> _propertyTypeBuilders = new List>>(); + + private int? _id; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private string _name; + private int? _sortOrder; + + public PropertyGroupBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + public PropertyTypeBuilder> AddPropertyType() + { + var builder = new PropertyTypeBuilder>(this); + _propertyTypeBuilders.Add(builder); + return builder; + } + + public override PropertyGroup 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 sortOrder = _sortOrder ?? 0; + + var properties = new PropertyTypeCollection(false); + foreach (var propertyType in _propertyTypeBuilders.Select(x => x.Build())) + { + properties.Add(propertyType); + } + + return new PropertyGroup(properties) + { + Id = id, + Key = key, + Name = name, + SortOrder = sortOrder, + CreateDate = createDate, + UpdateDate = updateDate, + }; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = 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..9dc61014b5 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs @@ -0,0 +1,178 @@ +using System; +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 : PropertyTypeBuilder + { + public PropertyTypeBuilder() : base(null) + { + } + } + + public class NullPropertyTypeBuilderParent : IBuildPropertyTypes + { + } + + public class PropertyTypeBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithAliasBuilder, + IWithNameBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithSortOrderBuilder, + IWithDescriptionBuilder where TParent : IBuildPropertyTypes + { + private int? _id; + private Guid? _key; + private string _propertyEditorAlias; + private ValueStorageType? _valueStorageType; + private string _alias; + private string _name; + private DateTime? _createDate; + private DateTime? _updateDate; + private int? _sortOrder; + private string _description; + private int? _dataTypeId; + private Lazy _propertyGroupId; + private bool? _mandatory; + private string _mandatoryMessage; + private string _validationRegExp; + private string _validationRegExpMessage; + + public PropertyTypeBuilder(TParent 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 PropertyTypeBuilder WithPropertyGroupId(int propertyGroupId) + { + _propertyGroupId = new Lazy(() => propertyGroupId); + return this; + } + + public PropertyTypeBuilder WithMandatory(bool mandatory, string mandatoryMessage = "") + { + _mandatory = mandatory; + _mandatoryMessage = mandatoryMessage; + return this; + } + + public PropertyTypeBuilder WithValidationRegExp(string validationRegExp, string validationRegExpMessage = "") + { + _validationRegExp = validationRegExp; + _validationRegExpMessage = validationRegExpMessage; + return this; + } + + public override PropertyType Build() + { + var id = _id ?? 0; + var key = _key ?? Guid.NewGuid(); + var propertyEditorAlias = _propertyEditorAlias ?? Guid.NewGuid().ToString().ToCamelCase(); + var valueStorageType = _valueStorageType ?? ValueStorageType.Ntext; + var name = _name ?? Guid.NewGuid().ToString(); + var alias = _alias ?? name.ToCamelCase(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var sortOrder = _sortOrder ?? 0; + var dataTypeId = _dataTypeId ?? 0; + var description = _description ?? string.Empty; + var propertyGroupId = _propertyGroupId ?? null; + var mandatory = _mandatory ?? false; + var mandatoryMessage = _mandatoryMessage ?? string.Empty; + var validationRegExp = _validationRegExp ?? string.Empty; + var validationRegExpMessage = _validationRegExpMessage ?? string.Empty; + + var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); + + return new PropertyType(shortStringHelper, propertyEditorAlias, valueStorageType) + { + Id = id, + Key = key, + Alias = alias, + Name = name, + SortOrder = sortOrder, + DataTypeId = dataTypeId, + Description = description, + CreateDate = createDate, + UpdateDate = updateDate, + PropertyGroupId = propertyGroupId, + Mandatory = mandatory, + MandatoryMessage = mandatoryMessage, + ValidationRegExp = validationRegExp, + ValidationRegExpMessage = validationRegExpMessage, + }; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = 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/RelationBuilder.cs b/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs new file mode 100644 index 0000000000..2cea234200 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs @@ -0,0 +1,95 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class RelationBuilder + : BuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder + { + private RelationTypeBuilder _relationTypeBuilder; + + private int? _id; + private int? _parentId; + private int? _childId; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private string _comment; + + public RelationBuilder WithComment(string comment) + { + _comment = comment; + return this; + } + + public RelationBuilder BetweenIds(int parentId, int childId) + { + _parentId = parentId; + _childId = childId; + return this; + } + + public RelationTypeBuilder AddRelationType() + { + var builder = new RelationTypeBuilder(this); + _relationTypeBuilder = builder; + return builder; + } + + public override Relation Build() + { + var id = _id ?? 0; + var parentId = _parentId ?? 0; + var childId = _childId ?? 0; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var comment = _comment ?? string.Empty; + + if (_relationTypeBuilder == null) + { + throw new InvalidOperationException("Cannot construct a Relation without a RelationType. Use AddRelationType()."); + } + + var relationType = _relationTypeBuilder.Build(); + + return new Relation(parentId, childId, relationType) + { + Comment = comment, + CreateDate = createDate, + Id = id, + Key = key, + UpdateDate = updateDate + }; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs new file mode 100644 index 0000000000..677d02bf09 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs @@ -0,0 +1,119 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class RelationTypeBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithAliasBuilder, + IWithNameBuilder, + IWithKeyBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder + { + private string _alias; + private Guid? _childObjectType; + private DateTime? _createDate; + private DateTime? _deleteDate; + private int? _id; + private bool? _isBidirectional; + private Guid? _key; + private string _name; + private Guid? _parentObjectType; + private DateTime? _updateDate; + + public RelationTypeBuilder() : base(null) + { + } + + public RelationTypeBuilder(RelationBuilder parentBuilder) : base(parentBuilder) + { + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + public override IRelationType Build() + { + var alias = _alias ?? Guid.NewGuid().ToString(); + var name = _name ?? Guid.NewGuid().ToString(); + var parentObjectType = _parentObjectType ?? null; + var childObjectType = _childObjectType ?? null; + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var isBidirectional = _isBidirectional ?? false; + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; + + return new RelationType(name, alias, isBidirectional, parentObjectType, childObjectType) + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate + }; + } + + public RelationTypeBuilder WithIsBidirectional(bool isBidirectional) + { + _isBidirectional = isBidirectional; + return this; + } + + public RelationTypeBuilder WithChildObjectType(Guid childObjectType) + { + _childObjectType = childObjectType; + return this; + } + + public RelationTypeBuilder WithParentObjectType(Guid parentObjectType) + { + _parentObjectType = parentObjectType; + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs new file mode 100644 index 0000000000..3120cc95f6 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs @@ -0,0 +1,73 @@ +using Umbraco.Core.Configuration; + +namespace Umbraco.Tests.Common.Builders +{ + public class SmtpSettingsBuilder : SmtpSettingsBuilder + { + public SmtpSettingsBuilder() : base(null) + { + } + } + + public class SmtpSettingsBuilder + : ChildBuilderBase + { + private string _from; + private string _host; + private int? _port; + private string _pickupDirectoryLocation; + + public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + public SmtpSettingsBuilder WithFrom(string from) + { + _from = from; + return this; + } + + public SmtpSettingsBuilder WithHost(string host) + { + _host = host; + return this; + } + + public SmtpSettingsBuilder WithPost(int port) + { + _port = port; + return this; + } + + public SmtpSettingsBuilder WithPickupDirectoryLocation(string pickupDirectoryLocation) + { + _pickupDirectoryLocation = pickupDirectoryLocation; + return this; + } + + + public override ISmtpSettings Build() + { + var from = _from ?? null; + var host = _host ?? null; + var port = _port ?? 25; + var pickupDirectoryLocation = _pickupDirectoryLocation ?? null; + + return new TestSmtpSettings() + { + From = from, + Host = host, + Port = port, + PickupDirectoryLocation = pickupDirectoryLocation, + }; + } + + private class TestSmtpSettings : ISmtpSettings + { + public string From { get; set; } + public string Host { get; set; } + public int Port { get; set; } + public string PickupDirectoryLocation { get; set; } + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs b/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs new file mode 100644 index 0000000000..ed871b9c31 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs @@ -0,0 +1,34 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Common.Builders +{ + public class StylesheetBuilder + : BuilderBase + { + private string _path; + private string _content; + + public StylesheetBuilder WithPath(string path) + { + _path = path; + return this; + } + + public StylesheetBuilder WithContent(string content) + { + _content = content; + return this; + } + + public override Stylesheet Build() + { + var path = _path ?? string.Empty; + var content = _content ?? string.Empty; + + return new Stylesheet(path) + { + Content = content, + }; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs b/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs new file mode 100644 index 0000000000..7f7fa0f098 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs @@ -0,0 +1,116 @@ +using System; +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 TemplateBuilder + : BuilderBase