diff --git a/build/NuSpecs/UmbracoCms.StaticAssets.nuspec b/build/NuSpecs/UmbracoCms.StaticAssets.nuspec index b8109dbfa9..cd8367d8c8 100644 --- a/build/NuSpecs/UmbracoCms.StaticAssets.nuspec +++ b/build/NuSpecs/UmbracoCms.StaticAssets.nuspec @@ -28,6 +28,6 @@ - + diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 893c62509a..b2824d8237 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -401,7 +401,7 @@ stages: publishJUnitResults: true testResultsFiles: '**\TESTS-*.xml' - task: PowerShell@1 - displayName: Prepare Packages & Zip + displayName: Prepare Packages inputs: scriptType: inlineScript inlineScript: | @@ -410,19 +410,6 @@ stages: $ubuild.CompileUmbraco() $ubuild.PreparePackages() - $ubuild.PackageZip() - - task: CopyFiles@2 - displayName: Copy Zip Files to Staging - inputs: - SourceFolder: build.out - Contents: '*.zip' - TargetFolder: $(build.artifactstagingdirectory) - CleanTargetFolder: true - - task: PublishBuildArtifacts@1 - displayName: Publish Zip Files - inputs: - PathtoPublish: $(build.artifactstagingdirectory) - ArtifactName: zips - task: PowerShell@1 displayName: Verify & Package NuGet inputs: diff --git a/build/build.ps1 b/build/build.ps1 index ba99e1e660..fb869738f3 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -351,29 +351,6 @@ $this.RemoveDirectory("$tmp\Templates\UmbracoProject\bin") }) - $ubuild.DefineMethod("PackageZip", - { - - Write-Host "Create Zip packages" - - $src = "$($this.SolutionRoot)\src" - $tmp = $this.BuildTemp - $out = $this.BuildOutput - - Write-Host "Zip all binaries" - &$this.BuildEnv.Zip a -r "$out\UmbracoCms.AllBinaries.$($this.Version.Semver).zip" ` - "$tmp\bin\*" ` - "-x!dotless.Core.*" ` - > $null - if (-not $?) { throw "Failed to zip UmbracoCms.AllBinaries." } - - Write-Host "Zip cms" - &$this.BuildEnv.Zip a -r "$out\UmbracoCms.$($this.Version.Semver).zip" ` - "$tmp\WebApp\*" ` - "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb" ` - > $null - if (-not $?) { throw "Failed to zip UmbracoCms." } - }) $ubuild.DefineMethod("PrepareBuild", { @@ -541,8 +518,6 @@ # not running tests $this.PreparePackages() if ($this.OnError()) { return } - $this.PackageZip() - if ($this.OnError()) { return } $this.VerifyNuGet() if ($this.OnError()) { return } $this.PackageNuGet() diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index 4f2454706d..8a8c396dcb 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -15,7 +15,7 @@ }, "primaryOutputs": [ { - "path": "UmbracoProject.csproj" + "path": "UmbracoProject.csproj" } ], "postActions": [ @@ -29,12 +29,35 @@ "continueOnError": true } ], - "sourceName": "Umbraco.Cms.Web.UI.NetCore", + "sourceName": "UmbracoProject", "symbols": { + "namespaceReplacer": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "replaces": "Umbraco.Cms.Web.UI.NetCore", + "parameters": { + "source": "name", + "steps": [ + { + "regex": "\\s", + "replacement": "_" + }, + { + "regex": "-", + "replacement": "_" + }, + { + "regex": "^[^a-zA-Z_]+", + "replacement": "_" + } + ] + } + }, "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.0.0-beta003", + "defaultValue": "9.0.0-beta004", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, @@ -98,30 +121,134 @@ "type": "parameter", "datatype":"text", "description": "The friendly name of the user for Umbraco login when using Unattended install (Without installer wizard UI)", - "replaces": "FRIENDLY_NAME_FROM_TEMPLATE", "defaultValue": "" }, + "FriendlyNameReplaced":{ + "type": "generated", + "generator": "regex", + "dataType": "string", + "replaces": "FRIENDLY_NAME_FROM_TEMPLATE", + "parameters": { + "source": "FriendlyName", + "steps": [ + { + "regex": "\\\\", + "replacement": "\\\\" + }, + { + "regex": "\\\"", + "replacement": "\\\"" + }, + { + "regex": "\\\n", + "replacement": "\\\n" + }, + { + "regex": "\\\t", + "replacement": "\\\t" + } + ] + } + }, "Email":{ "type": "parameter", "datatype":"text", "description": "Email to use for Umbraco login when using Unattended install (Without installer wizard UI)", - "replaces": "EMAIL_FROM_TEMPLATE", "defaultValue": "" }, + "EmailReplaced":{ + "type": "generated", + "generator": "regex", + "dataType": "string", + "replaces": "EMAIL_FROM_TEMPLATE", + "parameters": { + "source": "Email", + "steps": [ + { + "regex": "\\\\", + "replacement": "\\\\" + }, + { + "regex": "\\\"", + "replacement": "\\\"" + }, + { + "regex": "\\\n", + "replacement": "\\\n" + }, + { + "regex": "\\\t", + "replacement": "\\\t" + } + ] + } + }, "Password":{ "type": "parameter", "datatype":"text", "description": "Password to use for Umbraco login when using Unattended install (Without installer wizard UI)", - "replaces": "PASSWORD_FROM_TEMPLATE", "defaultValue": "" }, + "PasswordReplaced":{ + "type": "generated", + "generator": "regex", + "dataType": "string", + "replaces": "PASSWORD_FROM_TEMPLATE", + "parameters": { + "source": "Password", + "steps": [ + { + "regex": "\\\\", + "replacement": "\\\\" + }, + { + "regex": "\\\"", + "replacement": "\\\"" + }, + { + "regex": "\\\n", + "replacement": "\\\n" + }, + { + "regex": "\\\t", + "replacement": "\\\t" + } + ] + } + }, "ConnectionString":{ "type": "parameter", "datatype":"text", "description": "Database connection string when using Unattended install (Without installer wizard UI)", - "replaces": "CONNECTION_FROM_TEMPLATE", "defaultValue": "" }, + "ConnectionStringReplaced":{ + "type": "generated", + "generator": "regex", + "dataType": "string", + "replaces": "CONNECTION_FROM_TEMPLATE", + "parameters": { + "source": "ConnectionString", + "steps": [ + { + "regex": "\\\\", + "replacement": "\\\\" + }, + { + "regex": "\\\"", + "replacement": "\\\"" + }, + { + "regex": "\\\n", + "replacement": "\\\n" + }, + { + "regex": "\\\t", + "replacement": "\\\t" + } + ] + } + }, "UsingUnattenedInstall":{ "type": "computed", "value": "(FriendlyName != \"\" && Email != \"\" && Password != \"\" && ConnectionString != \"\")" diff --git a/build/templates/UmbracoProject/Properties/launchSettings.json b/build/templates/UmbracoProject/Properties/launchSettings.json index 5f9252d553..f40916f4d1 100644 --- a/build/templates/UmbracoProject/Properties/launchSettings.json +++ b/build/templates/UmbracoProject/Properties/launchSettings.json @@ -18,11 +18,12 @@ }, "Umbraco.Web.UI.NetCore": { "commandName": "Project", + "dotnetRunMessages": true, "launchBrowser": true, + "applicationUrl": "https://localhost:HTTPS_PORT_FROM_TEMPLATE;http://localhost:HTTP_PORT_FROM_TEMPLATE", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:HTTPS_PORT_FROM_TEMPLATE;http://localhost:HTTP_PORT_FROM_TEMPLATE" + } } } } diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/build/templates/UmbracoProject/UmbracoProject.csproj index 9798a40ff2..6850940edd 100644 --- a/build/templates/UmbracoProject/UmbracoProject.csproj +++ b/build/templates/UmbracoProject/UmbracoProject.csproj @@ -44,7 +44,7 @@ - + false diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 66b7cb4bb5..a1220f9b3f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ 9.0.0 9.0.0 - 9.0.0-beta003 + 9.0.0-beta004 9.0.0 9.0 en-US diff --git a/src/Umbraco.Core/Collections/TopoGraph.cs b/src/Umbraco.Core/Collections/TopoGraph.cs index 12b0d431ea..2e199dd5a4 100644 --- a/src/Umbraco.Core/Collections/TopoGraph.cs +++ b/src/Umbraco.Core/Collections/TopoGraph.cs @@ -101,7 +101,7 @@ namespace Umbraco.Cms.Core.Collections var start = incr > 0 ? 0 : index; var count = incr > 0 ? index : sorted.Length - index; if (throwOnCycle && Contains(sorted, item, start, count) == false) - throw new Exception(CycleDependencyError); + throw new Exception(CycleDependencyError +": " + item); return; } diff --git a/src/Umbraco.Core/Composing/ICoreComposer.cs b/src/Umbraco.Core/Composing/ICoreComposer.cs deleted file mode 100644 index c6d0b3510c..0000000000 --- a/src/Umbraco.Core/Composing/ICoreComposer.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Cms.Core.Composing -{ - /// - /// Represents a core . - /// - /// - /// Core composers compose after the initial composer, and before user composers. - /// - public interface ICoreComposer : IComposer - { - // TODO: This should die, there should be exactly zero core composers. - } -} diff --git a/src/Umbraco.Core/Composing/IUserComposer.cs b/src/Umbraco.Core/Composing/IUserComposer.cs index 52ed4fdf5c..8e07dae672 100644 --- a/src/Umbraco.Core/Composing/IUserComposer.cs +++ b/src/Umbraco.Core/Composing/IUserComposer.cs @@ -6,7 +6,6 @@ /// /// User composers compose after core composers, and before the final composer. /// - [ComposeAfter(typeof(ICoreComposer))] public interface IUserComposer : IComposer { } } diff --git a/src/Umbraco.Core/Events/SendEmailEventArgs.cs b/src/Umbraco.Core/Events/SendEmailEventArgs.cs index 720f125d9c..c1e626c6c1 100644 --- a/src/Umbraco.Core/Events/SendEmailEventArgs.cs +++ b/src/Umbraco.Core/Events/SendEmailEventArgs.cs @@ -1,5 +1,5 @@ using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Core.Events { diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index e815f219ca..0494224614 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -186,7 +186,7 @@ namespace Umbraco.Extensions /// This methods ensures that the resulting URL is structured correctly, that there's only one '?' and that things are /// delimited properly with '&' /// - internal static string AppendQueryStringToUrl(this string url, params string[] queryStrings) + public static string AppendQueryStringToUrl(this string url, params string[] queryStrings) { //remove any prefixed '&' or '?' for (var i = 0; i < queryStrings.Length; i++) diff --git a/src/Umbraco.Core/Extensions/XmlExtensions.cs b/src/Umbraco.Core/Extensions/XmlExtensions.cs index 8ad37bd93e..aa3fe25eef 100644 --- a/src/Umbraco.Core/Extensions/XmlExtensions.cs +++ b/src/Umbraco.Core/Extensions/XmlExtensions.cs @@ -8,6 +8,7 @@ using System.Text; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Xml; namespace Umbraco.Extensions @@ -238,7 +239,7 @@ namespace Umbraco.Extensions { if (xml == null) { - throw new ArgumentNullException("xml"); + throw new ArgumentNullException(nameof(xml)); } if (xml.HasAttributes == false) @@ -246,19 +247,19 @@ namespace Umbraco.Extensions throw new InvalidOperationException($"{attributeName} not found in xml"); } - if (xml.Attribute(attributeName) == null) + XAttribute attribute = xml.Attribute(attributeName); + if (attribute is null) { throw new InvalidOperationException($"{attributeName} not found in xml"); } - var val = xml.Attribute(attributeName).Value; - var result = val.TryConvertTo(); + Attempt result = attribute.Value.TryConvertTo(); if (result.Success) { return result.Result; } - throw new InvalidOperationException($"{val} attribute value cannot be converted to {typeof(T)}"); + throw new InvalidOperationException($"{attribute.Value} attribute value cannot be converted to {typeof(T)}"); } public static T AttributeValue(this XElement xml, string attributeName) diff --git a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs index 51e31b1811..6e679ddbb1 100644 --- a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Mail; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; diff --git a/src/Umbraco.Core/Mail/IEmailSender.cs b/src/Umbraco.Core/Mail/IEmailSender.cs index 5b5adf71eb..45959a5a9a 100644 --- a/src/Umbraco.Core/Mail/IEmailSender.cs +++ b/src/Umbraco.Core/Mail/IEmailSender.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Core.Mail { diff --git a/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs index 3632e79c23..29265b4038 100644 --- a/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs +++ b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Core.Mail { diff --git a/src/Umbraco.Core/Models/EmailMessage.cs b/src/Umbraco.Core/Models/Email/EmailMessage.cs similarity index 96% rename from src/Umbraco.Core/Models/EmailMessage.cs rename to src/Umbraco.Core/Models/Email/EmailMessage.cs index 153e8f33f7..55e30f2150 100644 --- a/src/Umbraco.Core/Models/EmailMessage.cs +++ b/src/Umbraco.Core/Models/Email/EmailMessage.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models.Email { public class EmailMessage { @@ -33,7 +33,6 @@ namespace Umbraco.Cms.Core.Models public EmailMessage(string from, string[] to, string[] cc, string[] bcc, string[] replyTo, string subject, string body, bool isBodyHtml, IEnumerable attachments) { - ArgumentIsNotNullOrEmpty(from, nameof(from)); ArgumentIsNotNullOrEmpty(to, nameof(to)); ArgumentIsNotNullOrEmpty(subject, nameof(subject)); ArgumentIsNotNullOrEmpty(body, nameof(body)); diff --git a/src/Umbraco.Core/Models/EmailMessageAttachment.cs b/src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs similarity index 88% rename from src/Umbraco.Core/Models/EmailMessageAttachment.cs rename to src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs index ee4f3ef8cb..bbb24b69f7 100644 --- a/src/Umbraco.Core/Models/EmailMessageAttachment.cs +++ b/src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs @@ -1,6 +1,6 @@ using System.IO; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models.Email { public class EmailMessageAttachment { diff --git a/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs b/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs new file mode 100644 index 0000000000..755947c6a4 --- /dev/null +++ b/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Cms.Core.Models.Email +{ + /// + /// Represents an email address used for notifications. Contains both the address and its display name. + /// + public class NotificationEmailAddress + { + public string DisplayName { get; } + + public string Address { get; } + + public NotificationEmailAddress(string address, string displayName) + { + Address = address; + DisplayName = displayName; + } + } +} diff --git a/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs b/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs new file mode 100644 index 0000000000..a606e8e680 --- /dev/null +++ b/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Cms.Core.Models.Email +{ + /// + /// Represents an email when sent with notifications. + /// + public class NotificationEmailModel + { + public NotificationEmailAddress From { get; } + + public IEnumerable To { get; } + + public IEnumerable Cc { get; } + + public IEnumerable Bcc { get; } + + public IEnumerable ReplyTo { get; } + + public string Subject { get; } + + public string Body { get; } + + public bool IsBodyHtml { get; } + + public IList Attachments { get; } + + public bool HasAttachments => Attachments != null && Attachments.Count > 0; + + public NotificationEmailModel( + NotificationEmailAddress from, + IEnumerable to, + IEnumerable cc, + IEnumerable bcc, + IEnumerable replyTo, + string subject, + string body, + IEnumerable attachments, + bool isBodyHtml) + { + From = from; + To = to; + Cc = cc; + Bcc = bcc; + ReplyTo = replyTo; + Subject = subject; + Body = body; + IsBodyHtml = isBodyHtml; + Attachments = attachments?.ToList(); + } + + } +} diff --git a/src/Umbraco.Core/Notifications/SendEmailNotification.cs b/src/Umbraco.Core/Notifications/SendEmailNotification.cs index 194ee68edc..3c9caabb0e 100644 --- a/src/Umbraco.Core/Notifications/SendEmailNotification.cs +++ b/src/Umbraco.Core/Notifications/SendEmailNotification.cs @@ -1,11 +1,11 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Core.Notifications { public class SendEmailNotification : INotification { - public SendEmailNotification(EmailMessage message) => Message = message; + public SendEmailNotification(NotificationEmailModel message) => Message = message; - public EmailMessage Message { get; set; } + public NotificationEmailModel Message { get; } } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs new file mode 100644 index 0000000000..c29df4e85f --- /dev/null +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Notification that occurs during the Umbraco boot process, before instances of initialize. + /// + public class UmbracoApplicationComponentsInstallingNotification : INotification + { + /// + /// Initializes a new instance of the class. + /// + /// The runtime level + public UmbracoApplicationComponentsInstallingNotification(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel; + + /// + /// Gets the runtime level of execution. + /// + public RuntimeLevel RuntimeLevel { get; } + } +} diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs new file mode 100644 index 0000000000..6a66e2413f --- /dev/null +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs @@ -0,0 +1,19 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Notification that occurs during Umbraco boot after the MainDom has been acquired. + /// + public class UmbracoApplicationMainDomAcquiredNotification : INotification + { + /// + /// Initializes a new instance of the class. + /// + /// The runtime level + public UmbracoApplicationMainDomAcquiredNotification() + { + } + } +} diff --git a/src/Umbraco.Core/Packaging/PackageMigrationPlan.cs b/src/Umbraco.Core/Packaging/PackageMigrationPlan.cs index b7de5fa438..7d45cc01a9 100644 --- a/src/Umbraco.Core/Packaging/PackageMigrationPlan.cs +++ b/src/Umbraco.Core/Packaging/PackageMigrationPlan.cs @@ -1,5 +1,7 @@ +using System; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Migrations; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Packaging { @@ -46,6 +48,12 @@ namespace Umbraco.Cms.Core.Packaging /// public string PackageName { get; } + /// + /// Inform the plan executor to ignore all saved package state and + /// run the migration from initial state to it's end state. + /// + public override bool IgnoreCurrentState => true; + protected abstract void DefinePlan(); } diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs deleted file mode 100644 index 9f49dade80..0000000000 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: ComVisible(false)] -[assembly: Guid("DA322714-FB89-4943-92BD-BB122B82F66B")] - -// Umbraco Cms -[assembly: InternalsVisibleTo("Umbraco.Web")] -[assembly: InternalsVisibleTo("Umbraco.Web.UI")] - -[assembly: InternalsVisibleTo("Umbraco.Tests")] -[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] - -// Allow this to be mocked in our unit tests -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] - -// Umbraco Deploy -[assembly: InternalsVisibleTo("Umbraco.Deploy")] -[assembly: InternalsVisibleTo("Umbraco.Deploy.UI")] -[assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")] - -// Umbraco Forms -[assembly: InternalsVisibleTo("Umbraco.Forms.Core")] -[assembly: InternalsVisibleTo("Umbraco.Forms.Core.Providers")] -[assembly: InternalsVisibleTo("Umbraco.Forms.Web")] - -// Umbraco Headless -[assembly: InternalsVisibleTo("Umbraco.Cloud.Headless")] diff --git a/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs b/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs index c777aedd89..d653a831cb 100644 --- a/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs +++ b/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs @@ -2,6 +2,7 @@ namespace Umbraco.Cms.Core.PropertyEditors { + [DataContract] public class FileExtensionConfigItem : IFileExtensionConfigItem { [DataMember(Name = "id")] diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs deleted file mode 100644 index f14e0d6a0b..0000000000 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Handlers; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Routing; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Compose -{ - public sealed class NotificationsComposer : ICoreComposer - { - public void Compose(IUmbracoBuilder builder) - { - // add handlers for sending user notifications (i.e. emails) - builder.Services.AddUnique(); - builder - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler(); - - // add handlers for building content relations - builder - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler(); - - // add notification handlers for property editors - builder - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler(); - - // add notification handlers for redirect tracking - builder - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler(); - - // Add notification handlers for DistributedCache - builder - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - ; - // add notification handlers for auditing - builder - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler(); - } - } -} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index f2cd5cfad7..2b78be069b 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -3,14 +3,20 @@ using Examine; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Serilog; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Handlers; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Install; +using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Logging.Serilog.Enrichers; +using Umbraco.Cms.Core.Logging.Viewer; using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Media; @@ -39,6 +45,7 @@ using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Migrations.PostMigrations; using Umbraco.Cms.Infrastructure.Packaging; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0.DataTypes; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Runtime; using Umbraco.Cms.Infrastructure.Search; @@ -112,6 +119,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(factory => new MigrationBuilder(factory)); + builder.AddPreValueMigrators(); + builder.Services.AddUnique(); // register the published snapshot accessor - the "current" published snapshot is in the umbraco context @@ -211,5 +220,133 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection return builder; } + + + private static IUmbracoBuilder AddPreValueMigrators(this IUmbracoBuilder builder) + { + builder.WithCollectionBuilder() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + return builder; + } + + public static IUmbracoBuilder AddLogViewer(this IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + builder.SetLogViewer(); + builder.Services.AddUnique(factory => new SerilogJsonLogViewer(factory.GetRequiredService>(), + factory.GetRequiredService(), + factory.GetRequiredService(), + Log.Logger)); + + return builder; + } + + + public static IUmbracoBuilder AddCoreNotifications(this IUmbracoBuilder builder) + { + // add handlers for sending user notifications (i.e. emails) + builder.Services.AddUnique(); + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add handlers for building content relations + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add notification handlers for property editors + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add notification handlers for redirect tracking + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // Add notification handlers for DistributedCache + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + ; + // add notification handlers for auditing + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + return builder; + } } } diff --git a/src/Umbraco.Infrastructure/EmailSender.cs b/src/Umbraco.Infrastructure/EmailSender.cs index 02e83405d6..72daad7de1 100644 --- a/src/Umbraco.Infrastructure/EmailSender.cs +++ b/src/Umbraco.Infrastructure/EmailSender.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mail; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Infrastructure.Extensions; using SmtpClient = MailKit.Net.Smtp.SmtpClient; @@ -53,7 +53,8 @@ namespace Umbraco.Cms.Infrastructure { if (enableNotification) { - await _eventAggregator.PublishAsync(new SendEmailNotification(message)); + await _eventAggregator.PublishAsync( + new SendEmailNotification(message.ToNotificationEmail(_globalSettings.Smtp?.From))); } return; } diff --git a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs index cc1efa271e..f7bed9d74c 100644 --- a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs +++ b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs @@ -1,7 +1,8 @@ using System; +using System.Collections.Generic; using MimeKit; using MimeKit.Text; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Infrastructure.Extensions { @@ -9,13 +10,9 @@ namespace Umbraco.Cms.Infrastructure.Extensions { public static MimeMessage ToMimeMessage(this EmailMessage mailMessage, string configuredFromAddress) { - var fromEmail = mailMessage.From; - if (string.IsNullOrEmpty(fromEmail)) - { - fromEmail = configuredFromAddress; - } + var fromEmail = string.IsNullOrEmpty(mailMessage.From) ? configuredFromAddress : mailMessage.From; - if (!InternetAddress.TryParse(mailMessage.From, out InternetAddress fromAddress)) + if (!InternetAddress.TryParse(fromEmail, out InternetAddress fromAddress)) { throw new ArgumentException($"Email could not be sent. Could not parse from address {fromEmail} as a valid email address."); } @@ -78,5 +75,57 @@ namespace Umbraco.Cms.Infrastructure.Extensions throw new InvalidOperationException($"Email could not be sent. Could not parse a valid recipient address."); } } + + public static NotificationEmailModel ToNotificationEmail(this EmailMessage emailMessage, + string configuredFromAddress) + { + var fromEmail = string.IsNullOrEmpty(emailMessage.From) ? configuredFromAddress : emailMessage.From; + + NotificationEmailAddress from = ToNotificationAddress(fromEmail); + + return new NotificationEmailModel(from, + GetNotificationAddresses(emailMessage.To), + GetNotificationAddresses(emailMessage.Cc), + GetNotificationAddresses(emailMessage.Bcc), + GetNotificationAddresses(emailMessage.ReplyTo), + emailMessage.Subject, + emailMessage.Body, + emailMessage.Attachments, + emailMessage.IsBodyHtml); + } + + private static NotificationEmailAddress ToNotificationAddress(string address) + { + if (InternetAddress.TryParse(address, out InternetAddress internetAddress)) + { + if (internetAddress is MailboxAddress mailboxAddress) + { + return new NotificationEmailAddress(mailboxAddress.Address, internetAddress.Name); + } + } + + return null; + } + + private static IEnumerable GetNotificationAddresses(IEnumerable addresses) + { + if (addresses is null) + { + return null; + } + + var notificationAddresses = new List(); + + foreach (var address in addresses) + { + NotificationEmailAddress notificationAddress = ToNotificationAddress(address); + if (notificationAddress is not null) + { + notificationAddresses.Add(notificationAddress); + } + } + + return notificationAddresses; + } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 131b81322a..70dcb3a04e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -62,8 +62,9 @@ namespace Umbraco.Cms.Infrastructure.HostedServices } finally { - // Resume now that the task is complete - _timer?.Change((int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); + // Resume now that the task is complete - Note we use period in both because we don't want to execute again after the delay. + // So first execution is after _delay, and the we wait _period between each + _timer?.Change((int)_period.TotalMilliseconds, (int)_period.TotalMilliseconds); } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs index b1411270c3..6eab3a60bc 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs @@ -52,13 +52,24 @@ namespace Umbraco.Cms.Infrastructure.HostedServices try { - // Send data to LIVE telemetry - s_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); + + if (s_httpClient.BaseAddress is null) + { + // Send data to LIVE telemetry + s_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); + + // Set a low timeout - no need to use a larger default timeout for this POST request + s_httpClient.Timeout = new TimeSpan(0, 0, 1); #if DEBUG - // Send data to DEBUG telemetry service - s_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); + // Send data to DEBUG telemetry service + s_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); + + + #endif + } + s_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); @@ -67,9 +78,6 @@ namespace Umbraco.Cms.Infrastructure.HostedServices var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild() }; request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header - // Set a low timeout - no need to use a larger default timeout for this POST request - s_httpClient.Timeout = new TimeSpan(0, 0, 1); - // Make a HTTP Post to telemetry service // https://telemetry.umbraco.com/installs/ // Fire & Forget, do not need to know if its a 200, 500 etc diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs deleted file mode 100644 index dcfcb66d5d..0000000000 --- a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Serilog; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Logging; -using Umbraco.Cms.Infrastructure.DependencyInjection; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Logging.Viewer -{ - // ReSharper disable once UnusedMember.Global - public class LogViewerComposer : ICoreComposer - { - public void Compose(IUmbracoBuilder builder) - { - builder.Services.AddUnique(); - builder.SetLogViewer(); - builder.Services.AddUnique(factory => - { - - return new SerilogJsonLogViewer(factory.GetRequiredService>(), - factory.GetRequiredService(), - factory.GetRequiredService(), - Log.Logger); - } ); - } - } -} diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs deleted file mode 100644 index eafec39da3..0000000000 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DependencyInjection; - -namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0.DataTypes -{ - public class PreValueMigratorComposer : ICoreComposer -{ - public void Compose(IUmbracoBuilder builder) - { - // do NOT add DefaultPreValueMigrator to this list! - // it will be automatically used if nothing matches - - builder.WithCollectionBuilder() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - } -} -} diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index ecdc17f97d..597a8901da 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -101,6 +101,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate AcquireMainDom(); + await _eventAggregator.PublishAsync(new UmbracoApplicationMainDomAcquiredNotification(), cancellationToken); + // notify for unattended install await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification()); DetermineRuntimeLevel(); @@ -137,6 +139,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime break; } + await _eventAggregator.PublishAsync(new UmbracoApplicationComponentsInstallingNotification(State.Level), cancellationToken); + // create & initialize the components _components.Initialize(); diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index b21ee2254d..6e0b58af09 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -155,7 +155,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime } else { - _logger.LogDebug("Package migrations need to execute but unattended package migrations is disabled. They will need to be run from the back office."); + _logger.LogInformation("Package migrations need to execute but unattended package migrations is disabled. They will need to be run from the back office."); Reason = RuntimeLevelReason.Run; } diff --git a/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs b/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs index 459417b289..724eb77030 100644 --- a/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs +++ b/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs @@ -34,7 +34,7 @@ namespace Umbraco.Cms.Core.Security /// /// Used to construct a new instance without an identity /// - public static MemberIdentityUser CreateNew(string username, string email, string memberTypeAlias, string name = null) + public static MemberIdentityUser CreateNew(string username, string email, string memberTypeAlias, bool isApproved, string name = null) { if (string.IsNullOrWhiteSpace(username)) { @@ -46,6 +46,7 @@ namespace Umbraco.Cms.Core.Security user.UserName = username; user.Email = email; user.MemberTypeAlias = memberTypeAlias; + user.IsApproved = isApproved; user.Id = null; user.HasIdentity = false; user.Name = name; diff --git a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs index aedad0e56b..c6f440942e 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.PublishedCache.NuCache/Compose/NotificationsComposer.cs b/src/Umbraco.PublishedCache.NuCache/Compose/NotificationsComposer.cs deleted file mode 100644 index 9cb8936431..0000000000 --- a/src/Umbraco.PublishedCache.NuCache/Compose/NotificationsComposer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Notifications; - -namespace Umbraco.Cms.Infrastructure.PublishedCache.Compose -{ - public sealed class NotificationsComposer : ICoreComposer - { - public void Compose(IUmbracoBuilder builder) => - builder - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - ; - } -} diff --git a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs index 113a2245d8..b68662644e 100644 --- a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; @@ -46,10 +47,32 @@ namespace Umbraco.Extensions return idkSvc; }); + builder.AddNuCacheNotifications(); + // add the NuCache health check (hidden from type finder) // TODO: no NuCache health check yet // composition.HealthChecks().Add(); return builder; } + + + private static IUmbracoBuilder AddNuCacheNotifications(this IUmbracoBuilder builder) + { + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + ; + + return builder; + } + + } } diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index f0eac637fd..9629375553 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -90,9 +90,13 @@ namespace Umbraco.Cms.Tests.Integration.Testing s_firstTestInSession = false; // Ensure CoreRuntime stopped (now it's a HostedService) - IHost host = Services.GetRequiredService(); - await host.StopAsync(); - host.Dispose(); + IHost host = Services?.GetService(); + if (host is not null) + { + await host.StopAsync(); + host.Dispose(); + } + } [TearDown] diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs index 182ea07a7f..b1a3233277 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs @@ -17,7 +17,6 @@ using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.PublishedCache; -using Umbraco.Cms.Infrastructure.PublishedCache.Compose; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -54,8 +53,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { builder.AddNuCache(); builder.Services.AddUnique(); - var composer = new NotificationsComposer(); - composer.Compose(builder); } private void AssertJsonStartsWith(int id, string expected) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 44eabbac6f..14ed2ca6eb 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration; @@ -27,7 +28,6 @@ using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Tests.UnitTests.TestHelpers; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components { @@ -458,7 +458,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components } [ComposeAfter(typeof(Composer4))] - public class Composer2 : TestComposerBase, ICoreComposer + public class Composer2 : TestComposerBase { } @@ -466,6 +466,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components { } + public class Composer4 : TestComposerBase { } @@ -526,6 +527,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components { } + [ComposeAfter(typeof(Composer2))] + [ComposeAfter(typeof(Composer4))] public class Composer9 : TestComposerBase, ITestComposer { } @@ -541,7 +544,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components } [ComposeAfter(typeof(Composer4), true)] - public class Composer12 : TestComposerBase, ICoreComposer + public class Composer12 : TestComposerBase { } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs index 7915b6b92c..709873e8a3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using NUnit.Framework; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Infrastructure.Extensions; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions @@ -24,9 +25,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions const string subject = "Subject"; const string body = "

Message

"; const bool isBodyHtml = true; - var emailMesasge = new EmailMessage(from, to, subject, body, isBodyHtml); + var emailMessage = new EmailMessage(from, to, subject, body, isBodyHtml); - var result = emailMesasge.ToMimeMessage(ConfiguredSender); + var result = emailMessage.ToMimeMessage(ConfiguredSender); Assert.AreEqual(1, result.From.Count()); Assert.AreEqual(from, result.From.First().ToString()); @@ -54,9 +55,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions { new EmailMessageAttachment(attachmentStream, "test.txt"), }; - var emailMesasge = new EmailMessage(from, to, cc, bcc, replyTo, subject, body, isBodyHtml, attachments); + var emailMessage = new EmailMessage(from, to, cc, bcc, replyTo, subject, body, isBodyHtml, attachments); - var result = emailMesasge.ToMimeMessage(ConfiguredSender); + var result = emailMessage.ToMimeMessage(ConfiguredSender); Assert.AreEqual(1, result.From.Count()); Assert.AreEqual(from, result.From.First().ToString()); @@ -77,5 +78,147 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions Assert.AreEqual(body, result.TextBody.ToString()); Assert.AreEqual(1, result.Attachments.Count()); } + + [Test] + public void Can_Construct_MimeMessage_With_ConfiguredSender() + { + const string to = "to@email.com"; + const string subject = "Subject"; + const string body = "

Message

"; + const bool isBodyHtml = true; + var emailMessage = new EmailMessage(null, to, subject, body, isBodyHtml); + + var result = emailMessage.ToMimeMessage(ConfiguredSender); + + Assert.AreEqual(1, result.From.Count()); + Assert.AreEqual(ConfiguredSender, result.From.First().ToString()); + Assert.AreEqual(1, result.To.Count()); + Assert.AreEqual(to, result.To.First().ToString()); + Assert.AreEqual(subject, result.Subject); + Assert.IsNull(result.TextBody); + Assert.AreEqual(body, result.HtmlBody.ToString()); + } + + [Test] + public void Can_Construct_NotificationEmailModel_From_Simple_MailMessage() + { + const string from = "from@email.com"; + const string to = "to@email.com"; + const string subject = "Subject"; + const string body = "

Message

"; + const bool isBodyHtml = true; + var emailMessage = new EmailMessage(from, to, subject, body, isBodyHtml); + + NotificationEmailModel result = emailMessage.ToNotificationEmail(ConfiguredSender); + + Assert.AreEqual(from, result.From.Address); + Assert.AreEqual("", result.From.DisplayName); + Assert.AreEqual(1, result.To.Count()); + Assert.AreEqual(to, result.To.First().Address); + Assert.AreEqual("", result.To.First().DisplayName); + Assert.AreEqual(subject, result.Subject); + Assert.AreEqual(body, result.Body); + Assert.IsTrue(result.IsBodyHtml); + Assert.IsFalse(result.HasAttachments); + } + + [Test] + public void Can_Construct_NotificationEmailModel_From_Simple_MailMessage_With_Configured_Sender() + { + const string to = "to@email.com"; + const string subject = "Subject"; + const string body = "

Message

"; + const bool isBodyHtml = true; + var emailMessage = new EmailMessage(null, to, subject, body, isBodyHtml); + + NotificationEmailModel result = emailMessage.ToNotificationEmail(ConfiguredSender); + + Assert.AreEqual(ConfiguredSender, result.From.Address); + Assert.AreEqual("", result.From.DisplayName); + Assert.AreEqual(1, result.To.Count()); + Assert.AreEqual(to, result.To.First().Address); + Assert.AreEqual("", result.To.First().DisplayName); + Assert.AreEqual(subject, result.Subject); + Assert.AreEqual(body, result.Body); + Assert.IsTrue(result.IsBodyHtml); + Assert.IsFalse(result.HasAttachments); + } + + [Test] + public void Can_Construct_NotificationEmailModel_From_Simple_MailMessage_With_DisplayName() + { + const string from = "\"From Email\" "; + const string to = "\"To Email\" "; + const string subject = "Subject"; + const string body = "

Message

"; + const bool isBodyHtml = true; + var emailMessage = new EmailMessage(from, to, subject, body, isBodyHtml); + + NotificationEmailModel result = emailMessage.ToNotificationEmail(ConfiguredSender); + + Assert.AreEqual("from@from.com", result.From.Address); + Assert.AreEqual("From Email", result.From.DisplayName); + Assert.AreEqual(1, result.To.Count()); + Assert.AreEqual("to@to.com", result.To.First().Address); + Assert.AreEqual("To Email", result.To.First().DisplayName); + Assert.AreEqual(subject, result.Subject); + Assert.AreEqual(body, result.Body); + Assert.IsTrue(result.IsBodyHtml); + Assert.IsFalse(result.HasAttachments); + } + + + [Test] + public void Can_Construct_NotificationEmailModel_From_Full_EmailMessage() + { + const string from = "\"From Email\" "; + string[] to = { "to@email.com", "\"Second Email\" ", "invalid@invalid@invalid" }; + string[] cc = { "\"First CC\" ", "cc2@email.com", "invalid@invalid@invalid" }; + string[] bcc = { "bcc@email.com", "bcc2@email.com", "\"Third BCC\" ", "invalid@email@address" }; + string[] replyTo = { "replyto@email.com", "invalid@invalid@invalid" }; + const string subject = "Subject"; + const string body = "Message"; + const bool isBodyHtml = false; + + using var attachmentStream = new MemoryStream(Encoding.UTF8.GetBytes("test")); + var attachments = new List + { + new EmailMessageAttachment(attachmentStream, "test.txt"), + }; + var emailMessage = new EmailMessage(from, to, cc, bcc, replyTo, subject, body, isBodyHtml, attachments); + + var result = emailMessage.ToNotificationEmail(ConfiguredSender); + + Assert.AreEqual("from@from.com", result.From.Address); + Assert.AreEqual("From Email", result.From.DisplayName); + + Assert.AreEqual(2, result.To.Count()); + Assert.AreEqual("to@email.com", result.To.First().Address); + Assert.AreEqual("", result.To.First().DisplayName); + Assert.AreEqual("to2@email.com", result.To.Skip(1).First().Address); + Assert.AreEqual("Second Email", result.To.Skip(1).First().DisplayName); + + Assert.AreEqual(2, result.Cc.Count()); + Assert.AreEqual("cc@email.com", result.Cc.First().Address); + Assert.AreEqual("First CC", result.Cc.First().DisplayName); + Assert.AreEqual("cc2@email.com", result.Cc.Skip(1).First().Address); + Assert.AreEqual("", result.Cc.Skip(1).First().DisplayName); + + Assert.AreEqual(3, result.Bcc.Count()); + Assert.AreEqual("bcc@email.com", result.Bcc.First().Address); + Assert.AreEqual("", result.Bcc.First().DisplayName); + Assert.AreEqual("bcc2@email.com", result.Bcc.Skip(1).First().Address); + Assert.AreEqual("", result.Bcc.Skip(1).First().DisplayName); + Assert.AreEqual("bcc3@email.com", result.Bcc.Skip(2).First().Address); + Assert.AreEqual("Third BCC", result.Bcc.Skip(2).First().DisplayName); + + Assert.AreEqual(1, result.ReplyTo.Count()); + Assert.AreEqual("replyto@email.com", result.ReplyTo.First().Address); + Assert.AreEqual("", result.ReplyTo.First().DisplayName); + + Assert.AreEqual(subject, result.Subject); + Assert.AreEqual(body, result.Body); + Assert.AreEqual(1, result.Attachments.Count()); + } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 707c486f3e..c552c0d976 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -17,6 +17,7 @@ using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index 3d435322b8..6045cec8f9 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -366,6 +366,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers contentItem.Username, contentItem.Email, memberType.Alias, + contentItem.IsApproved, contentItem.Name); IdentityResult created = await _memberManager.CreateAsync(identityMember, contentItem.Password.NewPassword); diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 192399b028..ee09b7d67b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -26,6 +26,7 @@ using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; @@ -683,12 +684,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers // if it's the current user, the current user cannot reset their own password without providing their old password if (currentUser.Username == found.Username && string.IsNullOrEmpty(changingPasswordModel.OldPassword)) { - return new ValidationErrorResult("Password reset is not allowed without providing old password"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Password reset is not allowed without providing old password"); } if (!currentUser.IsAdmin() && found.IsAdmin()) { - return new ValidationErrorResult("The current user cannot change the password for the specified user"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("The current user cannot change the password for the specified user"); } Attempt passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _userManager); diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 5088f69ef1..b24b0c32fc 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,6 +1,7 @@ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; @@ -49,6 +50,8 @@ namespace Umbraco.Extensions .AddDistributedCache() .AddModelsBuilderDashboard() .AddUnattendedInstallInstallCreateUser() + .AddCoreNotifications() + .AddLogViewer() .AddExamine() .AddExamineIndexes(); @@ -58,7 +61,6 @@ namespace Umbraco.Extensions return builder; } - /// /// Adds Umbraco preview support /// diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs index 68ba148f49..090ef52790 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Web.Common.ApplicationBuilder { - public interface IUmbracoApplicationBuilder : IUmbracoMiddlewareBuilder + public interface IUmbracoApplicationBuilder { /// /// Called to include umbraco middleware diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index 05fc38cc71..b7acc45d22 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Web.Common.ApplicationBuilder /// /// A builder to allow encapsulating the enabled endpoints in Umbraco /// - internal class UmbracoApplicationBuilder : IUmbracoApplicationBuilder + internal class UmbracoApplicationBuilder : IUmbracoApplicationBuilder, IUmbracoMiddlewareBuilder { public UmbracoApplicationBuilder(IServiceProvider services, IRuntimeState runtimeState, IApplicationBuilder appBuilder) { diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs index 2a0b77461d..e991a6a6ed 100644 --- a/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs @@ -109,6 +109,9 @@ namespace Umbraco.Extensions public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) => content.IsAllowedTemplate(ContentTypeService, WebRoutingSettings.Value, templateId); + public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) + => content.IsAllowedTemplate(WebRoutingSettings.Value.DisableAlternativeTemplates, WebRoutingSettings.Value.ValidateAlternativeTemplates, templateAlias); + public static bool IsAllowedTemplate( this IPublishedContent content, bool disableAlternativeTemplates, diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs index c2608efc41..97ba933838 100644 --- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -228,7 +228,7 @@ namespace Umbraco.Cms.Web.Common.Views // if not possible or it is not generic then we need to create a new ViewDataDictionary Type nViewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType); - var tViewData = new ViewDataDictionary(viewData) { Model = null }; // temp view data to copy values + var tViewData = new ViewDataDictionary(viewData) { Model = default(TModel) }; // temp view data to copy values var nViewData = (ViewDataDictionary)Activator.CreateInstance(nViewDataType, tViewData); return nViewData; } diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index b127fa3c3d..813ba75095 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -52,10 +52,10 @@ }, "devDependencies": { "@babel/core": "7.6.4", - "@babel/preset-env": "7.6.3", "@babel/plugin-proposal-object-rest-spread": "7.13.8", + "@babel/preset-env": "7.6.3", "autoprefixer": "9.6.5", - "caniuse-lite": "^1.0.30001037", + "caniuse-lite": "^1.0.30001237", "cssnano": "4.1.10", "fs": "0.0.2", "gulp": "4.0.2", @@ -67,6 +67,7 @@ "gulp-eslint": "6.0.0", "gulp-imagemin": "7.1.0", "gulp-less": "4.0.1", + "gulp-minify": "3.1.0", "gulp-notify": "^3.0.0", "gulp-postcss": "8.0.0", "gulp-rename": "1.4.0", @@ -75,12 +76,11 @@ "gulp-watch": "5.0.1", "gulp-wrap": "0.15.0", "gulp-wrap-js": "0.4.1", - "gulp-minify": "3.1.0", "jasmine-core": "3.5.0", "jsdom": "16.4.0", "karma": "4.4.1", - "karma-jsdom-launcher": "^8.0.2", "karma-jasmine": "2.0.1", + "karma-jsdom-launcher": "^8.0.2", "karma-junit-reporter": "2.0.1", "karma-spec-reporter": "0.0.32", "less": "3.10.3", diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js index e788e6fc9b..d7d5153956 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js @@ -7,38 +7,59 @@ * Controls the dashboards of the application * */ - -function DashboardController($scope, $routeParams, dashboardResource, localizationService) { + +function DashboardController($scope, $q, $routeParams, $location, dashboardResource, localizationService) { + const DASHBOARD_QUERY_PARAM = 'dashboard'; $scope.page = {}; $scope.page.nameLocked = true; $scope.page.loading = true; $scope.dashboard = {}; - localizationService.localize("sections_" + $routeParams.section).then(function(name){ - $scope.dashboard.name = name; - }); - - dashboardResource.getDashboard($routeParams.section).then(function(tabs){ - $scope.dashboard.tabs = tabs; - - // set first tab to active - if($scope.dashboard.tabs && $scope.dashboard.tabs.length > 0) { - $scope.dashboard.tabs[0].active = true; - } + var promises = []; + + promises.push(localizationService.localize("sections_" + $routeParams.section).then(function (name) { + $scope.dashboard.name = name; + })); + + promises.push(dashboardResource.getDashboard($routeParams.section).then(function (tabs) { + $scope.dashboard.tabs = tabs; + + if ($scope.dashboard.tabs && $scope.dashboard.tabs.length > 0) { + initActiveTab(); + } + })); + + $q.all(promises).then(function () { $scope.page.loading = false; }); - $scope.changeTab = function(tab) { - $scope.dashboard.tabs.forEach(function(tab) { - tab.active = false; - }); + $scope.changeTab = function (tab) { + if ($scope.dashboard.tabs && $scope.dashboard.tabs.length > 0) { + $scope.dashboard.tabs.forEach(function (tab) { + tab.active = false; + }); + } + tab.active = true; + $location.search(DASHBOARD_QUERY_PARAM, tab.alias); }; + function initActiveTab() { + // Check the query parameter for a dashboard alias + const dashboardAlias = $location.search()[DASHBOARD_QUERY_PARAM]; + const dashboardIndex = $scope.dashboard.tabs.findIndex(tab => tab.alias === dashboardAlias); + + // Set the first dashboard to active if there is no query parameter or we can't find a matching dashboard for the alias + const activeIndex = dashboardIndex !== -1 ? dashboardIndex : 0; + + const tab = $scope.dashboard.tabs[activeIndex]; + + tab.active = true; + $location.search(DASHBOARD_QUERY_PARAM, tab.alias); + } } - -//register it +// Register it angular.module('umbraco').controller("Umbraco.DashboardController", DashboardController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js index 4e1b3e868d..e2a6e30405 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js @@ -8,141 +8,140 @@ */ (function () { - "use strict"; + "use strict"; - function DataTypeSettingsController($scope, dataTypeResource, dataTypeHelper, - localizationService, notificationsService, overlayService, formHelper, eventsService) { + function DataTypeSettingsController($scope, dataTypeResource, dataTypeHelper, + localizationService, notificationsService, overlayService, formHelper, eventsService) { - var vm = this; + var vm = this; - vm.dataType = {}; - vm.loadingDataType = false; - vm.saveButtonState = "init"; + vm.dataType = {}; + vm.loadingDataType = false; + vm.saveButtonState = "init"; - vm.close = close; - vm.submit = submit; + vm.close = close; + vm.submit = submit; - function onInit() { + function onInit() { - setTitle(); + setTitle(); - if($scope.model.create) { - createNewDataType(); - } else { - getDataType(); - } + if($scope.model.create) { + createNewDataType(); + } else { + getDataType(); + } + } + + function setTitle() { + if(!$scope.model.title) { + localizationService.localize("contentTypeEditor_editorSettings") + .then(function(data){ + $scope.model.title = data; + }); + } + } + + function createNewDataType() { + + vm.loadingDataType = true; + + var parentId = -1; + var newDataType = {}; + + dataTypeResource.getScaffold(parentId).then(function(dataType) { + + newDataType = dataType; + + // set alias + newDataType.selectedEditor = $scope.model.propertyEditor.alias; + + // set name + var nameArray = []; + + if ($scope.model.contentTypeName) { + nameArray.push($scope.model.contentTypeName); } - function setTitle() { - if(!$scope.model.title) { - localizationService.localize("contentTypeEditor_editorSettings") - .then(function(data){ - $scope.model.title = data; - }); - } + if ($scope.model.property.label) { + nameArray.push($scope.model.property.label); } - function createNewDataType() { - - vm.loadingDataType = true; - - var parentId = -1; - var newDataType = {}; - - dataTypeResource.getScaffold(parentId).then(function(dataType) { - - newDataType = dataType; - - // set alias - newDataType.selectedEditor = $scope.model.propertyEditor.alias; - - // set name - var nameArray = []; - - if ($scope.model.contentTypeName) { - nameArray.push($scope.model.contentTypeName); - } - - if ($scope.model.property.label) { - nameArray.push($scope.model.property.label); - } - - if ($scope.model.propertyEditor.name) { - nameArray.push($scope.model.propertyEditor.name); - } - - // make name - newDataType.name = nameArray.join(" - "); - - // get pre values - dataTypeResource.getPreValues(newDataType.selectedEditor).then(function(preValues) { - newDataType.preValues = dataTypeHelper.createPreValueProps(preValues); - vm.dataType = newDataType; - vm.loadingDataType = false; - }); - - }); - + if ($scope.model.propertyEditor.name) { + nameArray.push($scope.model.propertyEditor.name); } - function getDataType() { - vm.loadingDataType = true; - dataTypeResource.getById($scope.model.id).then(function (dataType) { - dataType.preValues = dataTypeHelper.createPreValueProps(dataType.preValues); - vm.dataType = dataType; - vm.loadingDataType = false; - }); - } + // make name + newDataType.name = nameArray.join(" - "); - function close() { - if ($scope.model && $scope.model.close) { - $scope.model.close(); - } - } + // get pre values + dataTypeResource.getPreValues(newDataType.selectedEditor).then(function(preValues) { + newDataType.preValues = preValues; + vm.dataType = newDataType; + vm.loadingDataType = false; + }); - function submit() { - if (!formHelper.submitForm({ scope: $scope })) { - return; - } - - vm.saveButtonState = "busy"; - - var preValues = dataTypeHelper.createPreValueProps(vm.dataType.preValues); - - dataTypeResource.save(vm.dataType, preValues, $scope.model.create).then( - function(newDataType) { - $scope.model.dataType = newDataType; - - var args = { dataType: newDataType }; - eventsService.emit("editors.dataTypeSettings.saved", args); - - vm.saveButtonState = "success"; - - if ($scope.model && $scope.model.submit) { - $scope.model.submit($scope.model); - } - }, function(err) { - vm.saveButtonState = "error"; - - if(err.status === 400) { - if (err.data && (err.data.ModelState)) { - - formHelper.handleServerValidation(err.data.ModelState); - - for (var e in err.data.ModelState) { - notificationsService.error("Validation", err.data.ModelState[e][0]); - } - } - } - } - ); - - } - - onInit(); + }); } - angular.module("umbraco").controller("Umbraco.Editors.DataTypeSettingsController", DataTypeSettingsController); + function getDataType() { + vm.loadingDataType = true; + dataTypeResource.getById($scope.model.id).then(function (dataType) { + vm.dataType = dataType; + vm.loadingDataType = false; + }); + } + + function close() { + if ($scope.model && $scope.model.close) { + $scope.model.close(); + } + } + + function submit() { + if (!formHelper.submitForm({ scope: $scope })) { + return; + } + + vm.saveButtonState = "busy"; + + var preValues = dataTypeHelper.createPreValueProps(vm.dataType.preValues); + + dataTypeResource.save(vm.dataType, preValues, $scope.model.create).then( + function(newDataType) { + $scope.model.dataType = newDataType; + + var args = { dataType: newDataType }; + eventsService.emit("editors.dataTypeSettings.saved", args); + + vm.saveButtonState = "success"; + + if ($scope.model && $scope.model.submit) { + $scope.model.submit($scope.model); + } + }, function(err) { + vm.saveButtonState = "error"; + + if(err.status === 400) { + if (err.data && (err.data.ModelState)) { + + formHelper.handleServerValidation(err.data.ModelState); + + for (var e in err.data.ModelState) { + notificationsService.error("Validation", err.data.ModelState[e][0]); + } + } + } + } + ); + + } + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.DataTypeSettingsController", DataTypeSettingsController); })(); diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 2f97a15d8f..0419a8c0e4 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -3,9 +3,9 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Extensions; -using Microsoft.Extensions.Hosting; namespace Umbraco.Cms.Web.UI.NetCore { @@ -41,7 +41,7 @@ namespace Umbraco.Cms.Web.UI.NetCore { #pragma warning disable IDE0022 // Use expression body for methods services.AddUmbraco(_env, _config) - .AddBackOffice() + .AddBackOffice() .AddWebsite() .AddComposers() .Build(); diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index aaa5e72f9b..1e2d97a018 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -39,7 +39,7 @@ }, "RuntimeMinification": { "dataFolder": "umbraco/Data/TEMP/Smidge", - "version": "637510451273675926" + "version": "637550581849220194" }, "Security": { "KeepUserLoggedIn": false, @@ -71,4 +71,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs b/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs index 2a8dd6a71a..1e6c417ed3 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs @@ -127,7 +127,7 @@ namespace Umbraco.Cms.Web.Website.Controllers model.Username = (model.UsernameIsEmail || model.Username == null) ? model.Email : model.Username; - var identityUser = MemberIdentityUser.CreateNew(model.Username, model.Email, model.MemberTypeAlias, model.Name); + var identityUser = MemberIdentityUser.CreateNew(model.Username, model.Email, model.MemberTypeAlias, true, model.Name); IdentityResult identityResult = await _memberManager.CreateAsync( identityUser, model.Password); @@ -142,6 +142,7 @@ namespace Umbraco.Cms.Web.Website.Controllers // should never happen throw new InvalidOperationException($"Could not find a member with key: {member.Key}."); } + if (model.MemberProperties != null) { foreach (MemberPropertyModel property in model.MemberProperties.Where(p => p.Value != null) @@ -159,7 +160,6 @@ namespace Umbraco.Cms.Web.Website.Controllers } return identityResult; - } } } diff --git a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs index ddca7f37aa..123ed6801a 100644 --- a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs @@ -791,73 +791,73 @@ namespace Umbraco.Extensions /// /// Strips all HTML tags from a given string, all contents of the tags will remain. /// - public static IHtmlContent StripHtml(this HtmlHelper helper, IHtmlContent html, params string[] tags) + public static IHtmlContent StripHtml(this IHtmlHelper helper, IHtmlContent html, params string[] tags) => helper.StripHtml(html.ToHtmlString(), tags); /// /// Strips all HTML tags from a given string, all contents of the tags will remain. /// - public static IHtmlContent StripHtml(this HtmlHelper helper, string html, params string[] tags) + public static IHtmlContent StripHtml(this IHtmlHelper helper, string html, params string[] tags) => StringUtilities.StripHtmlTags(html, tags); /// /// Will take the first non-null value in the collection and return the value of it. /// - public static string Coalesce(this HtmlHelper helper, params object[] args) + public static string Coalesce(this IHtmlHelper helper, params object[] args) => StringUtilities.Coalesce(args); /// /// Joins any number of int/string/objects into one string /// - public static string Concatenate(this HtmlHelper helper, params object[] args) + public static string Concatenate(this IHtmlHelper helper, params object[] args) => StringUtilities.Concatenate(args); /// /// Joins any number of int/string/objects into one string and separates them with the string separator parameter. /// - public static string Join(this HtmlHelper helper, string separator, params object[] args) + public static string Join(this IHtmlHelper helper, string separator, params object[] args) => StringUtilities.Join(separator, args); /// /// Truncates a string to a given length, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent Truncate(this HtmlHelper helper, IHtmlContent html, int length) + public static IHtmlContent Truncate(this IHtmlHelper helper, IHtmlContent html, int length) => helper.Truncate(html.ToHtmlString(), length, true, false); /// /// Truncates a string to a given length, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent Truncate(this HtmlHelper helper, IHtmlContent html, int length, bool addElipsis) + public static IHtmlContent Truncate(this IHtmlHelper helper, IHtmlContent html, int length, bool addElipsis) => helper.Truncate(html.ToHtmlString(), length, addElipsis, false); /// /// Truncates a string to a given length, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent Truncate(this HtmlHelper helper, IHtmlContent html, int length, bool addElipsis, bool treatTagsAsContent) + public static IHtmlContent Truncate(this IHtmlHelper helper, IHtmlContent html, int length, bool addElipsis, bool treatTagsAsContent) => helper.Truncate(html.ToHtmlString(), length, addElipsis, treatTagsAsContent); /// /// Truncates a string to a given length, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent Truncate(this HtmlHelper helper, string html, int length) + public static IHtmlContent Truncate(this IHtmlHelper helper, string html, int length) => helper.Truncate(html, length, true, false); /// /// Truncates a string to a given length, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent Truncate(this HtmlHelper helper, string html, int length, bool addElipsis) + public static IHtmlContent Truncate(this IHtmlHelper helper, string html, int length, bool addElipsis) => helper.Truncate(html, length, addElipsis, false); /// /// Truncates a string to a given length, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent Truncate(this HtmlHelper helper, string html, int length, bool addElipsis, bool treatTagsAsContent) + public static IHtmlContent Truncate(this IHtmlHelper helper, string html, int length, bool addElipsis, bool treatTagsAsContent) => StringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); /// /// Truncates a string to a given amount of words, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent TruncateByWords(this HtmlHelper helper, string html, int words) + public static IHtmlContent TruncateByWords(this IHtmlHelper helper, string html, int words) { int length = StringUtilities.WordsToLength(html, words); @@ -867,7 +867,7 @@ namespace Umbraco.Extensions /// /// Truncates a string to a given amount of words, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent TruncateByWords(this HtmlHelper helper, string html, int words, bool addElipsis) + public static IHtmlContent TruncateByWords(this IHtmlHelper helper, string html, int words, bool addElipsis) { int length = StringUtilities.WordsToLength(html, words); @@ -877,7 +877,7 @@ namespace Umbraco.Extensions /// /// Truncates a string to a given amount of words, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent TruncateByWords(this HtmlHelper helper, IHtmlContent html, int words) + public static IHtmlContent TruncateByWords(this IHtmlHelper helper, IHtmlContent html, int words) { int length = StringUtilities.WordsToLength(html.ToHtmlString(), words); @@ -887,7 +887,7 @@ namespace Umbraco.Extensions /// /// Truncates a string to a given amount of words, can add a ellipsis at the end (...). Method checks for open HTML tags, and makes sure to close them /// - public static IHtmlContent TruncateByWords(this HtmlHelper helper, IHtmlContent html, int words, bool addElipsis) + public static IHtmlContent TruncateByWords(this IHtmlHelper helper, IHtmlContent html, int words, bool addElipsis) { int length = StringUtilities.WordsToLength(html.ToHtmlString(), words); diff --git a/src/Umbraco.Web/Runtime/WebFinalComposer.cs b/src/Umbraco.Web/Runtime/WebFinalComposer.cs index 818bff521a..9f2fbb1018 100644 --- a/src/Umbraco.Web/Runtime/WebFinalComposer.cs +++ b/src/Umbraco.Web/Runtime/WebFinalComposer.cs @@ -5,7 +5,6 @@ namespace Umbraco.Web.Runtime // web's final composer composes after all user composers // and *also* after ICoreComposer (in case IUserComposer is disabled) [ComposeAfter(typeof(IUserComposer))] - [ComposeAfter(typeof(ICoreComposer))] public class WebFinalComposer : ComponentComposer { }