From 4c037774de2d2e4bc0eceeb5e212f24a0b011957 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 23 Feb 2022 22:45:35 +0100 Subject: [PATCH 01/67] Move templates to root --- build/build.ps1 | 63 +++++------------- build/templates/Umbraco.Templates.nuspec | 21 ------ .../UmbracoProject/.template.config/icon.png | Bin 22265 -> 0 bytes .../UmbracoProject/Views/_ViewImports.cshtml | 8 --- templates/Umbraco.Templates.nuspec | 29 ++++++++ .../.template.config/dotnetcli.host.json | 0 .../.template.config/ide.host.json | 2 +- .../.template.config/template.json | 0 .../UmbracoPackage/package.manifest | 0 .../UmbracoPackage/UmbracoPackage.csproj | 0 .../build/UmbracoPackage.targets | 0 .../UmbracoProject/.gitignore | 0 .../.template.config/dotnetcli.host.json | 0 .../.template.config/ide.host.json | 2 +- .../.template.config/template.json | 0 .../Properties/launchSettings.json | 0 .../UmbracoProject/UmbracoProject.csproj | 0 .../appsettings.Development.json | 0 .../UmbracoProject/appsettings.json | 0 .../.template.config => templates}/icon.png | Bin 20 files changed, 46 insertions(+), 79 deletions(-) delete mode 100644 build/templates/Umbraco.Templates.nuspec delete mode 100644 build/templates/UmbracoProject/.template.config/icon.png delete mode 100644 build/templates/UmbracoProject/Views/_ViewImports.cshtml create mode 100644 templates/Umbraco.Templates.nuspec rename {build/templates => templates}/UmbracoPackage/.template.config/dotnetcli.host.json (100%) rename {build/templates => templates}/UmbracoPackage/.template.config/ide.host.json (89%) rename {build/templates => templates}/UmbracoPackage/.template.config/template.json (100%) rename {build/templates => templates}/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest (100%) rename {build/templates => templates}/UmbracoPackage/UmbracoPackage.csproj (100%) rename {build/templates => templates}/UmbracoPackage/build/UmbracoPackage.targets (100%) rename {build/templates => templates}/UmbracoProject/.gitignore (100%) rename {build/templates => templates}/UmbracoProject/.template.config/dotnetcli.host.json (100%) rename {build/templates => templates}/UmbracoProject/.template.config/ide.host.json (98%) rename {build/templates => templates}/UmbracoProject/.template.config/template.json (100%) rename {build/templates => templates}/UmbracoProject/Properties/launchSettings.json (100%) rename {build/templates => templates}/UmbracoProject/UmbracoProject.csproj (100%) rename {build/templates => templates}/UmbracoProject/appsettings.Development.json (100%) rename {build/templates => templates}/UmbracoProject/appsettings.json (100%) rename {build/templates/UmbracoPackage/.template.config => templates}/icon.png (100%) diff --git a/build/build.ps1 b/build/build.ps1 index da4733d432..24fd548c61 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -11,7 +11,7 @@ [Alias("loc")] [switch] $local = $false, - # enable docfx + # enable docfx [Parameter(Mandatory=$false)] [Alias("doc")] [switch] $docfx = $false, @@ -40,7 +40,7 @@ @{ Continue = $continue }) if ($ubuild.OnError()) { return } - Write-Host "Umbraco Cms Build" + Write-Host "Umbraco CMS Build" Write-Host "Umbraco.Build v$($ubuild.BuildVersion)" # ################################################################ @@ -84,7 +84,7 @@ $this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache) $this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix) - $ignore = $this.ClearEnvVar("NODE_NO_HTTP2") + $this.ClearEnvVar("NODE_NO_HTTP2") }) $ubuild.DefineMethod("CompileBelle", @@ -171,11 +171,6 @@ $src = "$($this.SolutionRoot)\src" $log = "$($this.BuildTemp)\build.umbraco.log" - if ($this.BuildEnv.VisualStudio -eq $null) - { - throw "Build environment does not provide VisualStudio." - } - Write-Host "Compile Umbraco" Write-Host "Logging to $log" @@ -255,27 +250,21 @@ $buildConfiguration = "Release" $log = "$($this.BuildTemp)\msbuild.tests.log" - if ($this.BuildEnv.VisualStudio -eq $null) - { - throw "Build environment does not provide VisualStudio." - } - Write-Host "Compile Tests" Write-Host "Logging to $log" # beware of the weird double \\ at the end of paths # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ - &$this.BuildEnv.VisualStudio.MsBuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" ` - /p:WarningLevel=0 ` - /p:Configuration=$buildConfiguration ` - /p:Platform=AnyCPU ` - /p:UseWPP_CopyWebApplication=True ` - /p:PipelineDependsOnBuild=False ` - /p:OutDir="$($this.BuildTemp)\tests\\" ` - /p:Verbosity=minimal ` - /t:Build ` - /tv:"$($this.BuildEnv.VisualStudio.ToolsVersion)" ` - /p:UmbracoBuild=True ` + &dotnet msbuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" ` + -target:Build ` + -property:WarningLevel=0 ` + -property:Configuration=$buildConfiguration ` + -property:Platform=AnyCPU ` + -property:UseWPP_CopyWebApplication=True ` + -property:PipelineDependsOnBuild=False ` + -property:OutDir="$($this.BuildTemp)\tests\\" ` + -property:Verbosity=minimal ` + -property:UmbracoBuild=True ` > $log # copy Umbraco.Persistence.SqlCe files into WebApp @@ -292,10 +281,6 @@ $src = "$($this.SolutionRoot)\src" $tmp = "$($this.BuildTemp)" - $out = "$($this.BuildOutput)" - $templates = "$($this.SolutionRoot)\build\templates" - - $buildConfiguration = "Release" # cleanup build Write-Host "Clean build" @@ -309,7 +294,6 @@ # create directories Write-Host "Create directories" mkdir "$tmp\WebApp\App_Data" > $null - mkdir "$tmp\Templates" > $null #mkdir "$tmp\WebApp\Media" > $null #mkdir "$tmp\WebApp\Views" > $null @@ -332,10 +316,6 @@ { $nugetPackages = [System.Environment]::ExpandEnvironmentVariables("%userprofile%\.nuget\packages") } - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\bin\x86") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\bin\amd64") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\WebApp\bin\x86") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\WebApp\bin\amd64") # copy Belle Write-Host "Copy Belle" @@ -343,19 +323,6 @@ $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\js", "*", "$tmp\WebApp\wwwroot\umbraco\js") $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\lib", "*", "$tmp\WebApp\wwwroot\umbraco\lib") $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\views", "*", "$tmp\WebApp\wwwroot\umbraco\views") - - - - # Prepare templates - Write-Host "Copy template files" - $this.CopyFiles("$templates", "*", "$tmp\Templates") - - Write-Host "Copy files for dotnet templates" - $this.CopyFiles("$src\Umbraco.Web.UI", "Program.cs", "$tmp\Templates\UmbracoProject") - $this.CopyFiles("$src\Umbraco.Web.UI", "Startup.cs", "$tmp\Templates\UmbracoProject") - $this.CopyFiles("$src\Umbraco.Web.UI\Views", "*", "$tmp\Templates\UmbracoProject\Views") - - $this.RemoveDirectory("$tmp\Templates\UmbracoProject\bin") }) @@ -389,7 +356,7 @@ { Write-Host "Restore NuGet" Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log" - $params = "-Source", $nugetsourceUmbraco + $params = "-Source", $nugetsourceUmbraco &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\umbraco-netcore-only.sln" > "$($this.BuildTemp)\nuget.restore.log" @params if (-not $?) { throw "Failed to restore NuGet packages." } }) @@ -397,7 +364,7 @@ $ubuild.DefineMethod("PackageNuGet", { $nuspecs = "$($this.SolutionRoot)\build\NuSpecs" - $templates = "$($this.BuildTemp)\Templates" + $templates = "$($this.SolutionRoot)\templates" Write-Host "Create NuGet packages" diff --git a/build/templates/Umbraco.Templates.nuspec b/build/templates/Umbraco.Templates.nuspec deleted file mode 100644 index 21201d8d55..0000000000 --- a/build/templates/Umbraco.Templates.nuspec +++ /dev/null @@ -1,21 +0,0 @@ - - - - Umbraco.Templates - 1.0.0 - Umbraco HQ - Umbraco HQ - MIT - https://umbraco.com/ - https://umbraco.com/dist/nuget/logo-small.png - false - Umbraco Cms templates for .NET Core Template Engine available through the dotnet CLI's new command - en-US - umbraco - - - - - - - diff --git a/build/templates/UmbracoProject/.template.config/icon.png b/build/templates/UmbracoProject/.template.config/icon.png deleted file mode 100644 index 6e94105808e0f05cb21726b4f729c979ed7d33ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22265 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 diff --git a/build/templates/UmbracoProject/Views/_ViewImports.cshtml b/build/templates/UmbracoProject/Views/_ViewImports.cshtml deleted file mode 100644 index cb9a0b658e..0000000000 --- a/build/templates/UmbracoProject/Views/_ViewImports.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@using Umbraco.Web.UI -@using Umbraco.Extensions -@using Umbraco.Web.PublishedModels -@using Umbraco.Cms.Core.Models.PublishedContent -@using Microsoft.AspNetCore.Html -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, Smidge -@inject Smidge.SmidgeHelper SmidgeHelper diff --git a/templates/Umbraco.Templates.nuspec b/templates/Umbraco.Templates.nuspec new file mode 100644 index 0000000000..48688a66cd --- /dev/null +++ b/templates/Umbraco.Templates.nuspec @@ -0,0 +1,29 @@ + + + + Umbraco.Templates + 1.0.0 + Umbraco HQ + Umbraco HQ + MIT + https://umbraco.com/ + icon.png + https://umbraco.com/dist/nuget/logo-small.png + false + Umbraco CMS templates for .NET Core Template Engine available through the dotnet CLI's new command + en-US + umbraco + + + + + + + + + + + + + + diff --git a/build/templates/UmbracoPackage/.template.config/dotnetcli.host.json b/templates/UmbracoPackage/.template.config/dotnetcli.host.json similarity index 100% rename from build/templates/UmbracoPackage/.template.config/dotnetcli.host.json rename to templates/UmbracoPackage/.template.config/dotnetcli.host.json diff --git a/build/templates/UmbracoPackage/.template.config/ide.host.json b/templates/UmbracoPackage/.template.config/ide.host.json similarity index 89% rename from build/templates/UmbracoPackage/.template.config/ide.host.json rename to templates/UmbracoPackage/.template.config/ide.host.json index 8d3bae3e3c..aa4eb34552 100644 --- a/build/templates/UmbracoPackage/.template.config/ide.host.json +++ b/templates/UmbracoPackage/.template.config/ide.host.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/vs-2017.3.host", "order" : 0, - "icon": "icon.png", + "icon": "../../icon.png", "description": { "id": "UmbracoPackage", "text": "Umbraco Package - An empty Umbraco CMS package (Plugin)" diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json similarity index 100% rename from build/templates/UmbracoPackage/.template.config/template.json rename to templates/UmbracoPackage/.template.config/template.json diff --git a/build/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest b/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest similarity index 100% rename from build/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest rename to templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest diff --git a/build/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj similarity index 100% rename from build/templates/UmbracoPackage/UmbracoPackage.csproj rename to templates/UmbracoPackage/UmbracoPackage.csproj diff --git a/build/templates/UmbracoPackage/build/UmbracoPackage.targets b/templates/UmbracoPackage/build/UmbracoPackage.targets similarity index 100% rename from build/templates/UmbracoPackage/build/UmbracoPackage.targets rename to templates/UmbracoPackage/build/UmbracoPackage.targets diff --git a/build/templates/UmbracoProject/.gitignore b/templates/UmbracoProject/.gitignore similarity index 100% rename from build/templates/UmbracoProject/.gitignore rename to templates/UmbracoProject/.gitignore diff --git a/build/templates/UmbracoProject/.template.config/dotnetcli.host.json b/templates/UmbracoProject/.template.config/dotnetcli.host.json similarity index 100% rename from build/templates/UmbracoProject/.template.config/dotnetcli.host.json rename to templates/UmbracoProject/.template.config/dotnetcli.host.json diff --git a/build/templates/UmbracoProject/.template.config/ide.host.json b/templates/UmbracoProject/.template.config/ide.host.json similarity index 98% rename from build/templates/UmbracoProject/.template.config/ide.host.json rename to templates/UmbracoProject/.template.config/ide.host.json index 1ee7a492aa..d44cb154c1 100644 --- a/build/templates/UmbracoProject/.template.config/ide.host.json +++ b/templates/UmbracoProject/.template.config/ide.host.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/vs-2017.3.host", "order" : 0, - "icon": "icon.png", + "icon": "../../icon.png", "description": { "id": "UmbracoProject", "text": "Umbraco Web Application - An empty Umbraco CMS web application" diff --git a/build/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json similarity index 100% rename from build/templates/UmbracoProject/.template.config/template.json rename to templates/UmbracoProject/.template.config/template.json diff --git a/build/templates/UmbracoProject/Properties/launchSettings.json b/templates/UmbracoProject/Properties/launchSettings.json similarity index 100% rename from build/templates/UmbracoProject/Properties/launchSettings.json rename to templates/UmbracoProject/Properties/launchSettings.json diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj similarity index 100% rename from build/templates/UmbracoProject/UmbracoProject.csproj rename to templates/UmbracoProject/UmbracoProject.csproj diff --git a/build/templates/UmbracoProject/appsettings.Development.json b/templates/UmbracoProject/appsettings.Development.json similarity index 100% rename from build/templates/UmbracoProject/appsettings.Development.json rename to templates/UmbracoProject/appsettings.Development.json diff --git a/build/templates/UmbracoProject/appsettings.json b/templates/UmbracoProject/appsettings.json similarity index 100% rename from build/templates/UmbracoProject/appsettings.json rename to templates/UmbracoProject/appsettings.json diff --git a/build/templates/UmbracoPackage/.template.config/icon.png b/templates/icon.png similarity index 100% rename from build/templates/UmbracoPackage/.template.config/icon.png rename to templates/icon.png From 5813a8aadf56ec15f4fee41ecd1bb852a230fcec Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 23 Feb 2022 23:20:13 +0100 Subject: [PATCH 02/67] Clean up and improve project templates --- src/Umbraco.Web.UI/Program.cs | 10 +- src/Umbraco.Web.UI/Startup.cs | 7 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 94 +-- .../.template.config/dotnetcli.host.json | 13 +- .../.template.config/ide.host.json | 24 +- .../.template.config/template.json | 197 +++--- .../UmbracoPackage/package.manifest | 3 + .../UmbracoPackage/UmbracoPackage.csproj | 6 +- .../build/UmbracoPackage.targets | 6 +- templates/UmbracoProject/.gitignore | 3 - .../.template.config/dotnetcli.host.json | 96 +-- .../.template.config/ide.host.json | 119 ++-- .../.template.config/template.json | 622 +++++++++--------- .../UmbracoProject/UmbracoProject.csproj | 15 +- .../appsettings.Development.json | 18 +- templates/UmbracoProject/appsettings.json | 19 +- 16 files changed, 602 insertions(+), 650 deletions(-) diff --git a/src/Umbraco.Web.UI/Program.cs b/src/Umbraco.Web.UI/Program.cs index 797fd00562..9b77c126b7 100644 --- a/src/Umbraco.Web.UI/Program.cs +++ b/src/Umbraco.Web.UI/Program.cs @@ -12,14 +12,10 @@ namespace Umbraco.Cms.Web.UI .Build() .Run(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) + public static IHostBuilder CreateHostBuilder(string[] args) + => Host.CreateDefaultBuilder(args) #if DEBUG - .ConfigureAppConfiguration(config - => config.AddJsonFile( - "appsettings.Local.json", - optional: true, - reloadOnChange: true)) + .ConfigureAppConfiguration(config => config.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true)) #endif .ConfigureLogging(x => x.ClearProviders()) .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); diff --git a/src/Umbraco.Web.UI/Startup.cs b/src/Umbraco.Web.UI/Startup.cs index 71c3dd008c..e0a48216e0 100644 --- a/src/Umbraco.Web.UI/Startup.cs +++ b/src/Umbraco.Web.UI/Startup.cs @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Web.UI /// The web hosting environment. /// The configuration. /// - /// Only a few services are possible to be injected here https://github.com/dotnet/aspnetcore/issues/9337 + /// Only a few services are possible to be injected here https://github.com/dotnet/aspnetcore/issues/9337. /// public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config) { @@ -34,18 +34,15 @@ namespace Umbraco.Cms.Web.UI /// The services. /// /// This method gets called by the runtime. Use this method to add services to the container. - /// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + /// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940. /// public void ConfigureServices(IServiceCollection services) { -#pragma warning disable IDE0022 // Use expression body for methods services.AddUmbraco(_env, _config) .AddBackOffice() .AddWebsite() .AddComposers() .Build(); -#pragma warning restore IDE0022 // Use expression body for methods - } /// diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 4222f4312d..33129accef 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -3,89 +3,39 @@ net5.0 Umbraco.Cms.Web.UI + + $(DefaultItemExcludes); + umbraco/Data/**; + umbraco/Logs/**; + wwwroot/umbraco/** + - + + bin/Release/Umbraco.Web.UI.xml + true + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - Always - - - true - Always - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - + + @@ -96,8 +46,8 @@ $(ProjectDir)wwwroot/umbraco $(ProjectDir)umbraco/config/appsettings-schema.json - + @@ -108,33 +58,33 @@ - - - + - + + - + + + - - + @@ -144,8 +94,8 @@ - + diff --git a/templates/UmbracoPackage/.template.config/dotnetcli.host.json b/templates/UmbracoPackage/.template.config/dotnetcli.host.json index 141f7bf97c..0f64d676c1 100644 --- a/templates/UmbracoPackage/.template.config/dotnetcli.host.json +++ b/templates/UmbracoPackage/.template.config/dotnetcli.host.json @@ -1,6 +1,13 @@ { - "$schema": "http://json.schemastore.org/dotnetcli.host", - "symbolInfo": { - + "$schema": "http://json.schemastore.org/dotnetcli.host", + "symbolInfo": { + "Framework": { + "longName": "Framework", + "shortName": "F" + }, + "UmbracoVersion": { + "longName": "version", + "shortName": "v" } + } } diff --git a/templates/UmbracoPackage/.template.config/ide.host.json b/templates/UmbracoPackage/.template.config/ide.host.json index aa4eb34552..baec9f98c6 100644 --- a/templates/UmbracoPackage/.template.config/ide.host.json +++ b/templates/UmbracoPackage/.template.config/ide.host.json @@ -1,13 +1,15 @@ { - "$schema": "http://json.schemastore.org/vs-2017.3.host", - "order" : 0, - "icon": "../../icon.png", - "description": { - "id": "UmbracoPackage", - "text": "Umbraco Package - An empty Umbraco CMS package (Plugin)" - }, - "symbolInfo": [ - - ] - + "$schema": "http://json.schemastore.org/vs-2017.3.host", + "order": 0, + "icon": "../../icon.png", + "description": { + "id": "UmbracoPackage", + "text": "Umbraco Package - An empty Umbraco CMS package/plugin" + }, + "symbolInfo": [ + { + "id": "UmbracoVersion", + "isVisible": "true" + } + ] } diff --git a/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json index 082f9301bf..733853d1ec 100644 --- a/templates/UmbracoPackage/.template.config/template.json +++ b/templates/UmbracoPackage/.template.config/template.json @@ -1,99 +1,108 @@ { - "$schema": "http://json.schemastore.org/template", - "author": "Umbraco HQ", - "description": "An empty Umbraco Package/Plugin ready to get started", - "classifications": [ "Web", "CMS", "Umbraco", "Package", "Plugin"], - "groupIdentity": "Umbraco.Templates.UmbracoPackage", - "identity": "Umbraco.Templates.UmbracoPackage.CSharp", - "name": "Umbraco Package", - "shortName": "umbracopackage", - "defaultName": "UmbracoPackage1", - "preferNameDirectory": true, - "tags": { - "language": "C#", - "type": "project" - }, - "primaryOutputs": [ + "$schema": "http://json.schemastore.org/template", + "author": "Umbraco HQ", + "classifications": [ + "Web", + "CMS", + "Umbraco", + "Package", + "Plugin" + ], + "name": "Umbraco Package", + "description": "An empty Umbraco package/plugin project ready to get started", + "groupIdentity": "Umbraco.Templates.UmbracoPackage", + "identity": "Umbraco.Templates.UmbracoPackage.CSharp", + "shortName": "umbracopackage", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "UmbracoPackage", + "defaultName": "UmbracoPackage1", + "preferNameDirectory": true, + "symbols": { + "Framework": { + "displayName": "Framework", + "description": "The target framework for the project.", + "type": "parameter", + "datatype": "choice", + "choices": [ { - "path": "UmbracoPackage.csproj" - } - ], - "sourceName": "UmbracoPackage", - "preferNameDirectory": true, - "symbols": { - "version": { - "type": "parameter", - "datatype": "string", - "defaultValue": "9.4.0-rc", - "description": "The version of Umbraco to load using NuGet", - "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" - }, - "namespaceReplacer": { - "type": "generated", - "generator": "regex", - "dataType": "string", - "replaces": "UmbracoPackage", - "parameters": { - "source": "name", - "steps": [ - { - "regex": "\\s", - "replacement": "_" - }, - { - "regex": "-", - "replacement": "_" - }, - { - "regex": "^[^a-zA-Z_]+", - "replacement": "_" - } - ] - } - }, - "msbuildReplacer": { - "type": "generated", - "generator": "regex", - "dataType": "string", - "replaces": "UmbracoPackageMsBuild", - "parameters": { - "source": "name", - "steps": [ - { - "regex": "\\s", - "replacement": "" - }, - { - "regex": "\\.", - "replacement": "" - }, - { - "regex": "-", - "replacement": "" - }, - { - "regex": "^[^a-zA-Z_]+", - "replacement": "" - } - ] - } - }, - "Framework": { - "type": "parameter", - "description": "The target framework for the project.", - "datatype": "choice", - "choices": [ - { - "choice": "net5.0", - "description": "Target net5.0" - }, - { - "choice": "net6.0", - "description": "Target net6.0" - } - ], - "replaces": "net5.0", - "defaultValue": "net5.0" + "displayName": ".NET 5.0", + "description": "Target net5.0", + "choice": "net5.0" + }, + { + "displayName": ".NET 6.0", + "description": "Target net6.0", + "choice": "net6.0" } + ], + "defaultValue": "net5.0", + "replaces": "net5.0" + }, + "UmbracoVersion": { + "displayName": "Umbraco version", + "description": "The version of Umbraco.Cms to add as PackageReference.", + "type": "parameter", + "datatype": "string", + "defaultValue": "9.4.0-rc", + "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" + }, + "namespaceReplacer": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "parameters": { + "source": "name", + "steps": [ + { + "regex": "\\s", + "replacement": "_" + }, + { + "regex": "-", + "replacement": "_" + }, + { + "regex": "^[^a-zA-Z_]+", + "replacement": "_" + } + ] + }, + "replaces": "UmbracoPackage" + }, + "msbuildReplacer": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "parameters": { + "source": "name", + "steps": [ + { + "regex": "\\s", + "replacement": "" + }, + { + "regex": "\\.", + "replacement": "" + }, + { + "regex": "-", + "replacement": "" + }, + { + "regex": "^[^a-zA-Z_]+", + "replacement": "" + } + ] + }, + "replaces": "UmbracoPackageMsBuild" } + }, + "primaryOutputs": [ + { + "path": "UmbracoPackage.csproj" + } + ] } diff --git a/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest b/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest index 8593c62d96..906db79b7a 100644 --- a/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest +++ b/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest @@ -1,2 +1,5 @@ { + "name": "UmbracoPackage", + "version": "", + "allowPackageTelemetry": true } \ No newline at end of file diff --git a/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj index a1ec4fa23d..f203c34a3d 100644 --- a/templates/UmbracoPackage/UmbracoPackage.csproj +++ b/templates/UmbracoPackage/UmbracoPackage.csproj @@ -12,8 +12,8 @@ - - + + @@ -22,7 +22,7 @@ Always - True + true buildTransitive diff --git a/templates/UmbracoPackage/build/UmbracoPackage.targets b/templates/UmbracoPackage/build/UmbracoPackage.targets index 5e3abf6ae1..33d3689179 100644 --- a/templates/UmbracoPackage/build/UmbracoPackage.targets +++ b/templates/UmbracoPackage/build/UmbracoPackage.targets @@ -9,11 +9,7 @@ - - + diff --git a/templates/UmbracoProject/.gitignore b/templates/UmbracoProject/.gitignore index f4caa41045..602728d104 100644 --- a/templates/UmbracoProject/.gitignore +++ b/templates/UmbracoProject/.gitignore @@ -464,9 +464,6 @@ $RECYCLE.BIN/ # Umbraco log files **/umbraco/Logs/ -# Dont commit files that are generated and cached from the default ImageSharp location -**/umbraco/mediacache/ - # Umbraco backoffice language files # Nuget package Umbraco.Cms.StaticAssets will copy them in during dotnet build # Customize langguage files in /config/lang/{language}.user.xml diff --git a/templates/UmbracoProject/.template.config/dotnetcli.host.json b/templates/UmbracoProject/.template.config/dotnetcli.host.json index d357188ed7..68f7f906be 100644 --- a/templates/UmbracoProject/.template.config/dotnetcli.host.json +++ b/templates/UmbracoProject/.template.config/dotnetcli.host.json @@ -1,47 +1,55 @@ { - "$schema": "http://json.schemastore.org/dotnetcli.host", - "symbolInfo": { - "PackageTestSiteName": { - "longName": "PackageTestSiteName", - "shortName": "p" - }, - "UseSqlCe": { - "longName": "SqlCe", - "shortName": "ce" - }, - "SkipRestore": { - "longName": "no-restore", - "shortName": "" - }, - "FriendlyName": { - "longName": "friendly-name", - "shortName": "" - }, - "Email": { - "longName": "email", - "shortName": "" - }, - "Password": { - "longName": "password", - "shortName": "" - }, - "ConnectionString":{ - "longName": "connection-string", - "shortName": "" - }, - "NoNodesViewPath":{ - "longName": "no-nodes-view-path", - "shortName": "" - }, - "UseHttpsRedirect": { - "longName": "use-https-redirect", - "shortName": "" - } + "$schema": "http://json.schemastore.org/dotnetcli.host", + "symbolInfo": { + "Framework": { + "longName": "Framework", + "shortName": "F" }, - "usageExamples": [ - "dotnet new umbraco -n MyNewProject", - "dotnet new umbraco -n MyNewProjectWithCE -ce", - "dotnet new umbraco -n MyNewProject --no-restore", - "dotnet new umbraco -n MyNewProject --friendly-name \"Friendly User\" --email user@email.com --password password1234 --connection-string \"Server=ConnectionStringHere\"" - ] + "UmbracoVersion": { + "longName": "version", + "shortName": "v" + }, + "UseHttpsRedirect": { + "longName": "use-https-redirect", + "shortName": "" + }, + "UseSqlCe": { + "longName": "SqlCe", + "shortName": "ce" + }, + "SkipRestore": { + "longName": "no-restore", + "shortName": "" + }, + "UnattendedUserName": { + "longName": "friendly-name", + "shortName": "" + }, + "UnattendedUserEmail": { + "longName": "email", + "shortName": "" + }, + "UnattendedUserPassword": { + "longName": "password", + "shortName": "" + }, + "ConnectionString": { + "longName": "connection-string", + "shortName": "" + }, + "NoNodesViewPath": { + "longName": "no-nodes-view-path", + "shortName": "" + }, + "PackageProjectName": { + "longName": "PackageTestSiteName", + "shortName": "p" + } + }, + "usageExamples": [ + "dotnet new umbraco -n MyNewProject", + "dotnet new umbraco -n MyNewProjectUsingSqlCE -ce", + "dotnet new umbraco -n MyNewProject --no-restore", + "dotnet new umbraco -n MyNewProject --friendly-name \"Friendly Admin User\" --email admin@example.com --password password1234 --connection-string \"Server=ConnectionStringHere\"" + ] } diff --git a/templates/UmbracoProject/.template.config/ide.host.json b/templates/UmbracoProject/.template.config/ide.host.json index d44cb154c1..893b9cc3f9 100644 --- a/templates/UmbracoProject/.template.config/ide.host.json +++ b/templates/UmbracoProject/.template.config/ide.host.json @@ -1,74 +1,51 @@ { - "$schema": "http://json.schemastore.org/vs-2017.3.host", - "order" : 0, - "icon": "../../icon.png", - "description": { - "id": "UmbracoProject", - "text": "Umbraco Web Application - An empty Umbraco CMS web application" + "$schema": "http://json.schemastore.org/vs-2017.3.host", + "order": 0, + "icon": "../../icon.png", + "description": { + "id": "UmbracoProject", + "text": "Umbraco Web Application - An empty Umbraco CMS web application" + }, + "symbolInfo": [ + { + "id": "UmbracoVersion", + "isVisible": "true" }, - "symbolInfo": [ - { - "id": "UseSqlCe", - "name": { - "text": "Use Sql Compact Edition (SqlCE)" - }, - "isVisible": "true" - }, - { - "id": "SkipRestore", - "name": { - "text": "Skips the automatic NuGet restore of the project on create" - }, - "isVisible": "true" - }, - { - "id": "PackageTestSiteName", - "name": { - "text": "Optional: Specify the name of a package that this should be a test site for" - }, - "isVisible": "true" - }, - { - "id": "FriendlyName", - "name": { - "text": "Optional: The friendly name of the user for Umbraco login when using Unattended install" - }, - "isVisible": "true" - }, - { - "id": "Email", - "name": { - "text": "Optional: Email to use for Umbraco login when using Unattended install" - }, - "isVisible": "true" - }, - { - "id": "Password", - "name": { - "text": "Optional: Password to use for Umbraco login when using Unattended install" - }, - "isVisible": "true" - }, - { - "id": "ConnectionString", - "name": { - "text": "Optional: Database connection string when using Unattended install" - }, - "isVisible": "true" - }, - { - "id": "NoNodesViewPath", - "name": { - "text": "Optional: Path to a custom view presented with the Umbraco installation contains no published content" - }, - "isVisible": "true" - }, - { - "id": "UseHttpsRedirect", - "name": { - "text": "Optional: Adds code to Startup.cs to redirect HTTP to HTTPS and enables the UseHttps setting." - }, - "isVisible": "true" - } - ] + { + "id": "UseHttpsRedirect", + "isVisible": "true" + }, + { + "id": "UseSqlCe", + "isVisible": "true" + }, + { + "id": "SkipRestore", + "isVisible": "true" + }, + { + "id": "UnattendedUserName", + "isVisible": "true" + }, + { + "id": "UnattendedUserEmail", + "isVisible": "true" + }, + { + "id": "UnattendedUserPassword", + "isVisible": "true" + }, + { + "id": "ConnectionString", + "isVisible": "true" + }, + { + "id": "NoNodesViewPath", + "isVisible": "true" + }, + { + "id": "PackageProjectName", + "isVisible": "true" + } + ] } diff --git a/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json index 810940c4eb..9966070519 100644 --- a/templates/UmbracoProject/.template.config/template.json +++ b/templates/UmbracoProject/.template.config/template.json @@ -1,304 +1,326 @@ { - "$schema": "http://json.schemastore.org/template", - "author": "Umbraco HQ", - "description": "An empty Umbraco Project ready to get started", - "classifications": [ "Web", "CMS", "Umbraco"], - "groupIdentity": "Umbraco.Templates.UmbracoProject", - "identity": "Umbraco.Templates.UmbracoProject.CSharp", - "name": "Umbraco Project", - "shortName": "umbraco", - "defaultName": "UmbracoProject1", - "preferNameDirectory": true, - "tags": { - "language": "C#", - "type": "project" + "$schema": "http://json.schemastore.org/template", + "author": "Umbraco HQ", + "classifications": [ + "Web", + "CMS", + "Umbraco" + ], + "name": "Umbraco Project", + "description": "An empty Umbraco project ready to get started", + "groupIdentity": "Umbraco.Templates.UmbracoProject", + "identity": "Umbraco.Templates.UmbracoProject.CSharp", + "shortName": "umbraco", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "UmbracoProject", + "defaultName": "UmbracoProject1", + "preferNameDirectory": true, + "symbols": { + "Framework": { + "displayName": "Framework", + "description": "The target framework for the project.", + "type": "parameter", + "datatype": "choice", + "choices": [ + { + "displayName": ".NET 5.0", + "description": "Target net5.0", + "choice": "net5.0" + }, + { + "displayName": ".NET 6.0", + "description": "Target net6.0", + "choice": "net6.0" + } + ], + "defaultValue": "net5.0", + "replaces": "net5.0" }, - "primaryOutputs": [ - { - "path": "UmbracoProject.csproj" - } - ], - "postActions": [ - { - "condition": "(!SkipRestore)", - "description": "Restore NuGet packages required by this project", - "manualInstructions": [{ - "text": "Run 'dotnet restore'" - }], - "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", - "continueOnError": true - } - ], - "sourceName": "UmbracoProject", - "symbols": { - "namespaceReplacer": { - "type": "generated", - "generator": "regex", - "dataType": "string", - "replaces": "Umbraco.Cms.Web.UI", - "parameters": { - "source": "name", - "steps": [ - { - "regex": "\\s", - "replacement": "_" - }, - { - "regex": "-", - "replacement": "_" - }, - { - "regex": "^[^a-zA-Z_]+", - "replacement": "_" - } - ] - } - }, - "version": { - "type": "parameter", - "datatype": "string", - "defaultValue": "9.4.0-rc", - "description": "The version of Umbraco to load using NuGet", - "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" - }, - "PackageTestSiteName": { - "type": "parameter", - "datatype":"text", - "defaultValue": "", - "replaces":"PackageTestSiteName", - "description": "The name of the package this should be a test site for (Default: '')" - }, - "UseSqlCe":{ - "type": "parameter", - "datatype":"bool", - "defaultValue": "false", - "description": "Adds the required dependencies to use SqlCE (Windows only) (Default: false)" - }, - "Framework": { - "type": "parameter", - "description": "The target framework for the project", - "datatype": "choice", - "choices": [ - { - "choice": "net5.0", - "description": "Target net5.0" - }, - { - "choice": "net6.0", - "description": "Target net6.0" - } - ], - "replaces": "net5.0", - "defaultValue": "net5.0" - }, - "SkipRestore": { - "type": "parameter", - "datatype": "bool", - "description": "If specified, skips the automatic restore of the project on create", - "defaultValue": "false" - }, - "HttpPort": { - "type": "generated", - "generator": "port", - "replaces": "HTTP_PORT_FROM_TEMPLATE", - "parameters": { - "high": 65535, - "low": 1024, - "fallback": 5000 - } - }, - "HttpsPort": { - "type": "generated", - "generator": "port", - "replaces": "HTTPS_PORT_FROM_TEMPLATE", - "parameters": { - "low": 44300, - "high": 44399, - "fallback": 5001 - } - }, - "FriendlyName":{ - "type": "parameter", - "datatype":"text", - "description": "The friendly name of the user for Umbraco login when using Unattended install (Without installer wizard UI)", - "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)", - "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)", - "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)", - "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" - } - ] - } - }, - "NoNodesViewPath":{ - "type": "parameter", - "datatype":"text", - "description": "Path to a custom view presented with the Umbraco installation contains no published content", - "defaultValue": "" - }, - "NoNodesViewPathReplaced":{ - "type": "generated", - "generator": "regex", - "dataType": "string", - "replaces": "NO_NODES_VIEW_PATH_FROM_TEMPLATE", - "parameters": { - "source": "NoNodesViewPath", - "steps": [ - { - "regex": "\\\\", - "replacement": "\\\\" - }, - { - "regex": "\\\"", - "replacement": "\\\"" - }, - { - "regex": "\\\n", - "replacement": "\\\n" - }, - { - "regex": "\\\t", - "replacement": "\\\t" - } - ] - } - }, - "HasConnectionString":{ - "type": "computed", - "value": "(ConnectionString != \"\")" - }, - "HasNoNodesViewPath":{ - "type": "computed", - "value": "(NoNodesViewPath != \"\")" - }, - "UsingUnattenedInstall":{ - "type": "computed", - "value": "(FriendlyName != \"\" && Email != \"\" && Password != \"\" && ConnectionString != \"\")" - }, - "UseHttpsRedirect":{ - "type": "parameter", - "datatype":"bool", - "defaultValue": "false", - "description": "Adds code to Startup.cs to redirect HTTP to HTTPS and enables the UseHttps setting (Default: false)" - } + "UmbracoVersion": { + "displayName": "Umbraco version", + "description": "The version of Umbraco.Cms to add as PackageReference.", + "type": "parameter", + "datatype": "string", + "defaultValue": "9.4.0-rc", + "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" + }, + "UseHttpsRedirect": { + "displayName": "Use HTTPS redirect", + "description": "Adds code to Startup.cs to redirect HTTP to HTTPS and enables the UseHttps setting.", + "type": "parameter", + "datatype": "bool", + "defaultValue": "false" + }, + "UseSqlCe": { + "displayName": "Add dependencies to use SQL CE database provider", + "description": "Adds the required dependencies to use SQL Compact Edition as database provider (Windows only).", + "type": "parameter", + "datatype": "bool", + "defaultValue": "false" + }, + "SkipRestore": { + "displayName": "Skip restore", + "description": "If specified, skips the automatic restore of the project on create.", + "type": "parameter", + "datatype": "bool", + "defaultValue": "false" + }, + "UnattendedUserName": { + "displayName": "Unattended user name", + "description": "Used to specify the name of the default admin user when using unattended install (stored as plain text).", + "type": "parameter", + "datatype": "string", + "defaultValue": "" + }, + "UnattendedUserNameReplacer": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "parameters": { + "source": "UnattendedUserName", + "steps": [ + { + "regex": "\\\\", + "replacement": "\\\\" + }, + { + "regex": "\\\"", + "replacement": "\\\"" + }, + { + "regex": "\\\n", + "replacement": "\\\n" + }, + { + "regex": "\\\t", + "replacement": "\\\t" + } + ] + }, + "replaces": "UNATTENDED_USER_NAME_FROM_TEMPLATE" + }, + "UnattendedUserEmail": { + "displayName": "Unattended user email", + "description": "Used to specify the email of the default admin user when using unattended install (stored as plain text).", + "type": "parameter", + "datatype": "string", + "defaultValue": "" + }, + "UnattendedUserEmailReplacer": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "parameters": { + "source": "UnattendedUserEmail", + "steps": [ + { + "regex": "\\\\", + "replacement": "\\\\" + }, + { + "regex": "\\\"", + "replacement": "\\\"" + }, + { + "regex": "\\\n", + "replacement": "\\\n" + }, + { + "regex": "\\\t", + "replacement": "\\\t" + } + ] + }, + "replaces": "UNATTENDED_USER_EMAIL_FROM_TEMPLATE" + }, + "UnattendedUserPassword": { + "displayName": "Unattended user password", + "description": "Used to specify the password of the default admin user when using unattended install (stored as plain text).", + "type": "parameter", + "datatype": "string", + "defaultValue": "" + }, + "UnattendedUserPasswordReplacer": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "parameters": { + "source": "UnattendedUserPassword", + "steps": [ + { + "regex": "\\\\", + "replacement": "\\\\" + }, + { + "regex": "\\\"", + "replacement": "\\\"" + }, + { + "regex": "\\\n", + "replacement": "\\\n" + }, + { + "regex": "\\\t", + "replacement": "\\\t" + } + ] + }, + "replaces": "UNATTENDED_USER_PASSWORD_FROM_TEMPLATE" + }, + "ConnectionString": { + "displayName": "Connection string", + "description": "Database connection string used by Umbraco.", + "type": "parameter", + "datatype": "string", + "defaultValue": "" + }, + "ConnectionStringReplacer": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "parameters": { + "source": "ConnectionString", + "steps": [ + { + "regex": "\\\\", + "replacement": "\\\\" + }, + { + "regex": "\\\"", + "replacement": "\\\"" + }, + { + "regex": "\\\n", + "replacement": "\\\n" + }, + { + "regex": "\\\t", + "replacement": "\\\t" + } + ] + }, + "replaces": "CONNECTION_STRING_FROM_TEMPLATE" + }, + "HasConnectionString": { + "type": "computed", + "value": "(ConnectionString != \"\")" + }, + "UsingUnattenedInstall": { + "type": "computed", + "value": "(UnattendedUserName != \"\" && UnattendedUserEmail != \"\" && UnattendedUserPassword != \"\" && ConnectionString != \"\")" + }, + "NoNodesViewPath": { + "displayName": "No nodes view path", + "description": "Path to a custom view presented with the Umbraco installation contains no published content", + "type": "parameter", + "datatype": "string", + "defaultValue": "" + }, + "NoNodesViewPathReplacer": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "parameters": { + "source": "NoNodesViewPath", + "steps": [ + { + "regex": "\\\\", + "replacement": "\\\\" + }, + { + "regex": "\\\"", + "replacement": "\\\"" + }, + { + "regex": "\\\n", + "replacement": "\\\n" + }, + { + "regex": "\\\t", + "replacement": "\\\t" + } + ] + }, + "replaces": "NO_NODES_VIEW_PATH_FROM_TEMPLATE" + }, + "HasNoNodesViewPath": { + "type": "computed", + "value": "(NoNodesViewPath != \"\")" + }, + "PackageProjectName": { + "displayName": "Umbraco package project name", + "description": "The name of the package project this should be a test site for.", + "type": "parameter", + "datatype": "string", + "defaultValue": "", + "replaces": "PACKAGE_PROJECT_NAME_FROM_TEMPLATE" + }, + "NamespaceReplacer": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "parameters": { + "source": "name", + "steps": [ + { + "regex": "\\s", + "replacement": "_" + }, + { + "regex": "-", + "replacement": "_" + }, + { + "regex": "^[^a-zA-Z_]+", + "replacement": "_" + } + ] + }, + "replaces": "Umbraco.Cms.Web.UI" + }, + "HttpPort": { + "type": "generated", + "generator": "port", + "parameters": { + "fallback": 5000 + }, + "replaces": "HTTP_PORT_FROM_TEMPLATE" + }, + "HttpsPort": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 44300, + "high": 44399, + "fallback": 5001 + }, + "replaces": "HTTPS_PORT_FROM_TEMPLATE" + }, + "TelemetryId": { + "type": "generated", + "generator": "guid", + "replaces": "TELEMETRYID_FROM_TEMPLATE" } + }, + "primaryOutputs": [ + { + "path": "UmbracoProject.csproj" + } + ], + "postActions": [ + { + "condition": "(!SkipRestore)", + "description": "Restore NuGet packages required by this project", + "manualInstructions": [ + { + "text": "Run 'dotnet restore'" + } + ], + "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", + "continueOnError": true + } + ] } diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj index 6b47686415..2459c071de 100644 --- a/templates/UmbracoProject/UmbracoProject.csproj +++ b/templates/UmbracoProject/UmbracoProject.csproj @@ -11,20 +11,15 @@ - + - - + - - - - + + + diff --git a/templates/UmbracoProject/appsettings.Development.json b/templates/UmbracoProject/appsettings.Development.json index dad70ac5d8..e132ce944d 100644 --- a/templates/UmbracoProject/appsettings.Development.json +++ b/templates/UmbracoProject/appsettings.Development.json @@ -1,5 +1,5 @@ { - "$schema" : "./umbraco/config/appsettings-schema.json", + "$schema": "./umbraco/config/appsettings-schema.json", "Serilog": { "MinimumLevel": { "Default": "Information" @@ -19,22 +19,22 @@ }, //#if (HasConnectionString) "ConnectionStrings": { - "umbracoDbDSN": "CONNECTION_FROM_TEMPLATE" + "umbracoDbDSN": "CONNECTION_STRING_FROM_TEMPLATE" }, //#endif "Umbraco": { "CMS": { - "Content": { - "MacroErrors": "Throw" - }, //#if (UsingUnattenedInstall) "Unattended": { "InstallUnattended": true, - "UnattendedUserName": "FRIENDLY_NAME_FROM_TEMPLATE", - "UnattendedUserEmail": "EMAIL_FROM_TEMPLATE", - "UnattendedUserPassword": "PASSWORD_FROM_TEMPLATE" + "UnattendedUserName": "UNATTENDED_USER_NAME_FROM_TEMPLATE", + "UnattendedUserEmail": "UNATTENDED_USER_EMAIL_FROM_TEMPLATE", + "UnattendedUserPassword": "UNATTENDED_USER_PASSWORD_FROM_TEMPLATE" }, //#endif + "Content": { + "MacroErrors": "Throw" + }, "Global": { "Smtp": { "From": "your@email.here", @@ -51,4 +51,4 @@ } } } -} \ No newline at end of file +} diff --git a/templates/UmbracoProject/appsettings.json b/templates/UmbracoProject/appsettings.json index 99e877812c..896d67d5fa 100644 --- a/templates/UmbracoProject/appsettings.json +++ b/templates/UmbracoProject/appsettings.json @@ -1,5 +1,5 @@ { - "$schema" : "./umbraco/config/appsettings-schema.json", + "$schema": "./umbraco/config/appsettings-schema.json", "Serilog": { "MinimumLevel": { "Default": "Information", @@ -11,26 +11,19 @@ } }, "ConnectionStrings": { - "umbracoDbDSN": "" + "umbracoDbDSN": null }, "Umbraco": { "CMS": { - //#if (HasNoNodesViewPath || UseHttpsRedirect) "Global": { - "SanitizeTinyMce": true, - //#if (!HasNoNodesViewPath && UseHttpsRedirect) - "UseHttps": true - //#elseif (UseHttpsRedirect) + "Id": "TELEMETRYID_FROM_TEMPLATE", + //#if (UseHttpsRedirect) "UseHttps": true, //#endif //#if (HasNoNodesViewPath) - "NoNodesViewPath": "NO_NODES_VIEW_PATH_FROM_TEMPLATE" + "NoNodesViewPath": "NO_NODES_VIEW_PATH_FROM_TEMPLATE", //#endif - - }, - //#endif - "Hosting": { - "Debug": false + "SanitizeTinyMce": true }, "Content": { "ContentVersionCleanupPolicy": { From 9187186c62c86dce7e88dbbb9d9da8fd7d19b8c4 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Feb 2022 10:21:15 +0100 Subject: [PATCH 03/67] Generate lowercase telemetry ID --- templates/UmbracoProject/.template.config/template.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json index 9966070519..e2fe9c9927 100644 --- a/templates/UmbracoProject/.template.config/template.json +++ b/templates/UmbracoProject/.template.config/template.json @@ -302,6 +302,9 @@ "TelemetryId": { "type": "generated", "generator": "guid", + "parameters": { + "defaultFormat": "d" + }, "replaces": "TELEMETRYID_FROM_TEMPLATE" } }, From 2dc9ccda6d31522dc0c6247198ddd003d2a8f5ba Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Feb 2022 10:24:27 +0100 Subject: [PATCH 04/67] Disable packaging of JsonSchema and test projects --- src/JsonSchema/JsonSchema.csproj | 33 ++++++++++--------- .../Umbraco.TestData/Umbraco.TestData.csproj | 1 + .../Umbraco.Tests.Benchmarks.csproj | 12 +++++-- .../Umbraco.Tests.Common.csproj | 3 +- .../Umbraco.Tests.UnitTests.csproj | 4 +-- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj index c7cbbe1320..7e87eed2e4 100644 --- a/src/JsonSchema/JsonSchema.csproj +++ b/src/JsonSchema/JsonSchema.csproj @@ -1,24 +1,25 @@ - - Exe - net5.0 - true - + + Exe + net5.0 + true + false + - - - - - - - + + + + + + + - - - + + + - + $(UserProfile)\.nuget\packages\ diff --git a/tests/Umbraco.TestData/Umbraco.TestData.csproj b/tests/Umbraco.TestData/Umbraco.TestData.csproj index c54dedf06f..aea4e775cc 100644 --- a/tests/Umbraco.TestData/Umbraco.TestData.csproj +++ b/tests/Umbraco.TestData/Umbraco.TestData.csproj @@ -4,6 +4,7 @@ false Umbraco.TestData net5.0 + false diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 55a2b609a9..0a616d2e41 100644 --- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -1,20 +1,25 @@ net5.0 - Exe + Exe false + false + + + + 0.13.1 @@ -25,7 +30,8 @@ 4.16.1 - + - + + diff --git a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj index 3e1723f59e..91e19b57c0 100644 --- a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj +++ b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj @@ -1,4 +1,4 @@ - + net5.0 @@ -6,6 +6,7 @@ Umbraco.Cms.Tests Umbraco CMS Test Tools Contains commonly used tools to write tests for Umbraco CMS, such as various builders for content etc. + true diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index 14c1ef689c..de43f66ea3 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -3,14 +3,14 @@ Exe net5.0 - false Umbraco.Cms.Tests.UnitTests + false - + From 05d606d6a7480de5ca55cb76bd35a8d2b76c4947 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Feb 2022 10:42:59 +0100 Subject: [PATCH 05/67] Fix artifact build and ensure the version of future templates are also updated --- build/azure-pipelines.yml | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 7f231bd298..ec01ebee82 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -396,42 +396,25 @@ stages: $ubuild = build/build.ps1 -get -continue - $version = $ubuild.GetUmbracoVersion() $isRelease = [regex]::matches($env:BUILD_SOURCEBRANCH,"v\d+\/\d+.\d+.*") - if ($isRelease.Count -gt 0){ + if ($isRelease.Count -gt 0) { $continuous = $version.Semver - - } - else - { + } else { $date = (Get-Date).ToString("yyyyMMdd") $continuous = "$($version.release)-preview$date.$(Build.BuildId)" $ubuild.SetUmbracoVersion($continuous) - #Update the version in templates also - - $templatePath = - 'build/templates/UmbracoProject/.template.config/template.json' - - $a = Get-Content $templatePath -raw | ConvertFrom-Json - - $a.symbols.version.defaultValue = $continuous - - $a | ConvertTo-Json -depth 32| set-content $templatePath - - - $templatePath = - 'build/templates/UmbracoPackage/.template.config/template.json' - - $a = Get-Content $templatePath -raw | ConvertFrom-Json - - $a.symbols.version.defaultValue = $continuous - - $a | ConvertTo-Json -depth 32| set-content $templatePath - } + # Update the version in templates + $templatePaths = Get-ChildItem 'templates/**/.template.config/template.json' + foreach ($templatePath in $templatePaths) { + $a = Get-Content $templatePath -Raw | ConvertFrom-Json + $a.symbols.version.defaultValue = $continuous + $a | ConvertTo-Json -Depth 32 | Set-Content $templatePath + } + } Write-Host "##vso[build.updatebuildnumber]$continuous.$(Build.BuildId)" From 5a3578eca93f33495c604cf78bbdbfeca2ffca98 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Feb 2022 11:37:22 +0100 Subject: [PATCH 06/67] Fix updating default Umbraco version in templates --- build/azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index ec01ebee82..2f07d879d2 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -407,11 +407,11 @@ stages: $continuous = "$($version.release)-preview$date.$(Build.BuildId)" $ubuild.SetUmbracoVersion($continuous) - # Update the version in templates + # Update the default Umbraco version in templates $templatePaths = Get-ChildItem 'templates/**/.template.config/template.json' foreach ($templatePath in $templatePaths) { $a = Get-Content $templatePath -Raw | ConvertFrom-Json - $a.symbols.version.defaultValue = $continuous + $a.symbols.UmbracoVersion.defaultValue = $continuous $a | ConvertTo-Json -Depth 32 | Set-Content $templatePath } } From ee3f7520b33d92f1ee2cc68b8d29eec76d0d072f Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Feb 2022 11:41:31 +0100 Subject: [PATCH 07/67] Conditionally update Umbraco version in templates --- build/azure-pipelines.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 2f07d879d2..2dd5626d9a 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -411,8 +411,10 @@ stages: $templatePaths = Get-ChildItem 'templates/**/.template.config/template.json' foreach ($templatePath in $templatePaths) { $a = Get-Content $templatePath -Raw | ConvertFrom-Json - $a.symbols.UmbracoVersion.defaultValue = $continuous - $a | ConvertTo-Json -Depth 32 | Set-Content $templatePath + if ($a.symbols -and $a.symbols.UmbracoVersion) { + $a.symbols.UmbracoVersion.defaultValue = $continuous + $a | ConvertTo-Json -Depth 32 | Set-Content $templatePath + } } } From a98b52a46b4536bf4403c0ff2dc69b55caced95a Mon Sep 17 00:00:00 2001 From: Paul Woodland <7717900+paulwoodland@users.noreply.github.com> Date: Wed, 9 Mar 2022 08:51:49 +0000 Subject: [PATCH 08/67] Possible NullReferenceException in MultiUrlPickerValueConverter (#12109) * Fixed a bug in the MultiUrlPickerValueConverter file, where an invalid value being set will result in a NullReferenceException * Change to checking for whitespace values before de-serializing Co-authored-by: Paul Woodland --- .../ValueConverters/MultiUrlPickerValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs index 3d8f15f6d0..281f6c58eb 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs @@ -51,7 +51,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters { var maxNumber = propertyType.DataType.ConfigurationAs().MaxNumber; - if (inter == null) + if (string.IsNullOrWhiteSpace(inter?.ToString())) { return maxNumber == 1 ? null : Enumerable.Empty(); } From 8d82f86c29ec7ae5ab865cbc414baee39c484b69 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 9 Mar 2022 15:12:46 +0100 Subject: [PATCH 09/67] Re-use DefaultItemExcludes from StaticAssets --- .../buildTransitive/Umbraco.Cms.StaticAssets.props | 13 +++++++------ .../Umbraco.Cms.StaticAssets.targets | 12 +----------- build/build.ps1 | 6 +++--- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 11 +++++------ 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.props b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.props index 371b0aa5ab..ea0b013665 100644 --- a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.props +++ b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.props @@ -1,7 +1,8 @@ - - - - $(DefaultItemExcludes);wwwroot\is-cache\**;wwwroot\ms-cache\** - - + + + $(DefaultItemExcludes);App_Plugins/** + $(DefaultItemExcludes);umbraco/Data/** + $(DefaultItemExcludes);umbraco/Logs/** + $(DefaultItemExcludes);wwwroot/media/** + diff --git a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets index 7f73c14650..b67f6f1c34 100644 --- a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets +++ b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets @@ -1,4 +1,4 @@ - + $(MSBuildThisFileDirectory)..\content\umbraco\**\*.* @@ -6,16 +6,6 @@ umbraco - - $(DefaultItemExcludes);App_Plugins\**; - - $(DefaultItemExcludes);umbraco\Data\**; - $(DefaultItemExcludes);umbraco\Logs\**; - $(DefaultItemExcludes);umbraco\mediacache\**; - - $(DefaultItemExcludes);wwwroot\media\**; - - diff --git a/build/build.ps1 b/build/build.ps1 index 24fd548c61..07ef733ad1 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -190,14 +190,14 @@ # remove extra files $webAppBin = "$($this.BuildTemp)\WebApp\bin" - $excludeDirs = @("$($webAppBin)\refs","$($webAppBin)\runtimes","$($webAppBin)\Umbraco","$($webAppBin)\wwwroot") + $excludeDirs = @("$($webAppBin)\refs","$($webAppBin)\runtimes","$($webAppBin)\umbraco","$($webAppBin)\wwwroot") $excludeFiles = @("$($webAppBin)\appsettings.*","$($webAppBin)\*.deps.json","$($webAppBin)\*.exe","$($webAppBin)\*.config","$($webAppBin)\*.runtimeconfig.json") $this.RemoveDirectory($excludeDirs) $this.RemoveFile($excludeFiles) # copy rest of the files into WebApp - $this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI\Umbraco", "*", "$($this.BuildTemp)\WebApp\umbraco") - $excludeUmbracoDirs = @("$($this.BuildTemp)\WebApp\umbraco\lib") + $this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI\umbraco", "*", "$($this.BuildTemp)\WebApp\umbraco") + $excludeUmbracoDirs = @("$($this.BuildTemp)\WebApp\umbraco\lib","$($this.BuildTemp)\WebApp\umbraco\Data","$($this.BuildTemp)\WebApp\umbraco\Logs") $this.RemoveDirectory($excludeUmbracoDirs) $this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI\Views", "*", "$($this.BuildTemp)\WebApp\Views") Copy-Item "$($this.SolutionRoot)\src\Umbraco.Web.UI\appsettings.json" "$($this.BuildTemp)\WebApp" diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 33129accef..093eb2d3b1 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -3,12 +3,11 @@ net5.0 Umbraco.Cms.Web.UI - - $(DefaultItemExcludes); - umbraco/Data/**; - umbraco/Logs/**; - wwwroot/umbraco/** - + + + + + $(DefaultItemExcludes);wwwroot/umbraco/** From 62b289e1790c721bb9bc3c717c21a4d13de7a173 Mon Sep 17 00:00:00 2001 From: Andrey Karandashov <51944778+AndreyKarandashovUKAD@users.noreply.github.com> Date: Wed, 9 Mar 2022 17:47:54 +0200 Subject: [PATCH 10/67] Block List Settings throws exception if Models builder mode is set to "Nothing" (#11725) --- .../BlockListPropertyValueConverter.cs | 15 ++++++++++++++- .../BlockListPropertyValueConverterTests.cs | 5 ++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs index 6916f2ea3f..1c8c7c7d4f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs @@ -4,9 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters @@ -17,12 +22,20 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters private readonly IProfilingLogger _proflog; private readonly BlockEditorConverter _blockConverter; private readonly BlockListEditorDataConverter _blockListEditorDataConverter; + private readonly ModelsBuilderSettings _modelsBuilderSettings; + [Obsolete("Use ctor injecting ModelsBuilderSettings")] public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter) + : this(proflog, blockConverter,StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IOptions modelsBuilderOptions) { _proflog = proflog; _blockConverter = blockConverter; _blockListEditorDataConverter = new BlockListEditorDataConverter(); + _modelsBuilderSettings = modelsBuilderOptions?.Value; } /// @@ -116,7 +129,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters } // Get settings type from configuration - var settingsType = blockConfig.SettingsElementTypeKey.HasValue + var settingsType = blockConfig.SettingsElementTypeKey.HasValue && _modelsBuilderSettings.ModelsMode != ModelsMode.Nothing ? _blockConverter.GetModelType(blockConfig.SettingsElementTypeKey.Value) : typeof(IPublishedElement); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs index 084bf03370..09fc427d12 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs @@ -2,9 +2,11 @@ // See LICENSE for more details. using System; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.PublishedContent; @@ -62,9 +64,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors { IPublishedSnapshotAccessor publishedSnapshotAccessor = GetPublishedSnapshotAccessor(); var publishedModelFactory = new NoopPublishedModelFactory(); + var modelsBuilderSettings = Mock.Of>(x => x.Value == new ModelsBuilderSettings()); var editor = new BlockListPropertyValueConverter( Mock.Of(), - new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory)); + new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory), modelsBuilderSettings); return editor; } From 3c40c20d27789c6c61e0541ee6d7145233a99155 Mon Sep 17 00:00:00 2001 From: vsilvar Date: Tue, 15 Feb 2022 15:09:31 +0100 Subject: [PATCH 11/67] Made sure Umbraco files aren't included twice This fixes CS8785 RazorSourceGenerator failures due to repeated files in .Net 6 --- .../NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets index 7f73c14650..2a95da1cd9 100644 --- a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets +++ b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets @@ -39,6 +39,7 @@ @@ -65,6 +66,7 @@ @@ -92,7 +94,7 @@ - + From cade6cb68473c3e09b09a9d297be8efd9f986f92 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Wed, 9 Mar 2022 17:18:28 +0100 Subject: [PATCH 12/67] Add missing copy member type menu action (#12089) --- .../Controllers/MemberTypeController.cs | 14 +++++++ .../Trees/MemberGroupTreeController.cs | 5 ++- .../MemberTypeAndGroupTreeControllerBase.cs | 39 ++++++++++++++++++- .../Trees/MemberTypeTreeController.cs | 3 +- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs index 7c1f6f4187..724e17adb1 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs @@ -252,5 +252,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return display; } + + /// + /// Copy the member type + /// + /// + /// + [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] + public IActionResult PostCopy(MoveOrCopy copy) + { + return PerformCopy( + copy, + i => _memberTypeService.Get(i), + (type, i) => _memberTypeService.Copy(type, i)); + } } } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs index 0559a17a53..858a1c8184 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs @@ -26,8 +26,9 @@ namespace Umbraco.Cms.Web.BackOffice.Trees UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, IMenuItemCollectionFactory menuItemCollectionFactory, IMemberGroupService memberGroupService, - IEventAggregator eventAggregator) - : base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator) + IEventAggregator eventAggregator, + IMemberTypeService memberTypeService) + : base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator, memberTypeService) => _memberGroupService = memberGroupService; protected override IEnumerable GetTreeNodesFromService(string id, FormCollection queryStrings) diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs index 94c84accab..64e8081dec 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Events; @@ -8,6 +10,8 @@ using Umbraco.Cms.Core.Models.Trees; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.DependencyInjection; +using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.Trees @@ -16,21 +20,46 @@ namespace Umbraco.Cms.Web.BackOffice.Trees [CoreTree] public abstract class MemberTypeAndGroupTreeControllerBase : TreeController { + private readonly IMemberTypeService _memberTypeService; + public IMenuItemCollectionFactory MenuItemCollectionFactory { get; } protected MemberTypeAndGroupTreeControllerBase( ILocalizedTextService localizedTextService, UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, IMenuItemCollectionFactory menuItemCollectionFactory, - IEventAggregator eventAggregator) + IEventAggregator eventAggregator, + IMemberTypeService memberTypeService) : base(localizedTextService, umbracoApiControllerTypeCollection, eventAggregator) { MenuItemCollectionFactory = menuItemCollectionFactory; + + _memberTypeService = memberTypeService; + } + + [Obsolete("Use ctor injecting IMemberTypeService")] + protected MemberTypeAndGroupTreeControllerBase( + ILocalizedTextService localizedTextService, + UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, + IMenuItemCollectionFactory menuItemCollectionFactory, + IEventAggregator eventAggregator) + : this( + localizedTextService, + umbracoApiControllerTypeCollection, + menuItemCollectionFactory, + eventAggregator, + StaticServiceProvider.Instance.GetRequiredService()) + { } protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); + + // if the request is for folders only then just return + if (queryStrings["foldersonly"].ToString().IsNullOrWhiteSpace() == false && queryStrings["foldersonly"].ToString() == "1") + return nodes; + nodes.AddRange(GetTreeNodesFromService(id, queryStrings)); return nodes; } @@ -48,7 +77,13 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } else { - //delete member type/group + var memberType = _memberTypeService.Get(int.Parse(id)); + if (memberType != null) + { + menu.Items.Add(LocalizedTextService, opensDialog: true); + } + + // delete member type/group menu.Items.Add(LocalizedTextService, opensDialog: true); } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs index 5595365e2b..318cce629d 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs @@ -32,13 +32,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees UmbracoTreeSearcher treeSearcher, IMemberTypeService memberTypeService, IEventAggregator eventAggregator) - : base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator) + : base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator, memberTypeService) { _treeSearcher = treeSearcher; _memberTypeService = memberTypeService; } - protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); From 8c9410a30766890fed356d166d8d8c29f15db594 Mon Sep 17 00:00:00 2001 From: CyberReiter <90895378+CyberReiter@users.noreply.github.com> Date: Sun, 13 Mar 2022 23:46:35 +0100 Subject: [PATCH 13/67] v9/bugfix/remove_useless_tolists: removed useless tolist()'s (#12123) Co-authored-by: Reiter --- src/Umbraco.Core/Composing/TypeFinder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 07a2eb72c9..0ce78b5b84 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -198,7 +198,7 @@ namespace Umbraco.Cms.Core.Composing IEnumerable assemblies = null, bool onlyConcreteClasses = true) { - var assemblyList = (assemblies ?? AssembliesToScan).ToList(); + var assemblyList = assemblies ?? AssembliesToScan; return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses, //the additional filter will ensure that any found types also have the attribute applied. @@ -214,7 +214,7 @@ namespace Umbraco.Cms.Core.Composing /// public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies = null, bool onlyConcreteClasses = true) { - var assemblyList = (assemblies ?? AssembliesToScan).ToList(); + var assemblyList = assemblies ?? AssembliesToScan; return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses); } @@ -231,7 +231,7 @@ namespace Umbraco.Cms.Core.Composing IEnumerable assemblies = null, bool onlyConcreteClasses = true) { - var assemblyList = (assemblies ?? AssembliesToScan).ToList(); + var assemblyList = assemblies ?? AssembliesToScan; return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses); } From 886d7f1a255acda0a9d001a4177f9e7481c979c6 Mon Sep 17 00:00:00 2001 From: patrickdemooij9 Date: Wed, 9 Mar 2022 14:50:49 +0100 Subject: [PATCH 14/67] Add allowedChildren call to Outgoing Editor events --- .../SendingAllowedChildrenNotification.cs | 19 +++++++++++++++++++ .../Controllers/ContentTypeController.cs | 2 ++ .../Controllers/MediaTypeController.cs | 2 ++ .../OutgoingEditorModelEventAttribute.cs | 6 ++++++ 4 files changed, 29 insertions(+) create mode 100644 src/Umbraco.Core/Notifications/SendingAllowedChildrenNotification.cs diff --git a/src/Umbraco.Core/Notifications/SendingAllowedChildrenNotification.cs b/src/Umbraco.Core/Notifications/SendingAllowedChildrenNotification.cs new file mode 100644 index 0000000000..07ab3c3626 --- /dev/null +++ b/src/Umbraco.Core/Notifications/SendingAllowedChildrenNotification.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Web; + +namespace Umbraco.Cms.Core.Notifications +{ + public class SendingAllowedChildrenNotification : INotification + { + public IUmbracoContext UmbracoContext { get; } + + public IEnumerable Children { get; set; } + + public SendingAllowedChildrenNotification(IEnumerable children, IUmbracoContext umbracoContext) + { + UmbracoContext = umbracoContext; + Children = children; + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index 99d22de939..8c9beb931c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -22,6 +22,7 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Packaging; +using Umbraco.Cms.Web.BackOffice.Filters; using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; @@ -452,6 +453,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes)] + [OutgoingEditorModelEvent] public IEnumerable GetAllowedChildren(int contentId) { if (contentId == Constants.System.RecycleBinContent) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs index cd1e9037ec..66efc37f47 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Web.BackOffice.Filters; using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; @@ -339,6 +340,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] + [OutgoingEditorModelEvent] public IEnumerable GetAllowedChildren(int contentId) { if (contentId == Constants.System.RecycleBinContent) diff --git a/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs index 8899499887..ed086f7ed5 100644 --- a/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs @@ -84,6 +84,12 @@ namespace Umbraco.Cms.Web.BackOffice.Filters case IEnumerable> dashboards: _eventAggregator.Publish(new SendingDashboardsNotification(dashboards, umbracoContext)); break; + case IEnumerable allowedChildren: + // Changing the Enumerable will generate a new instance, so we need to update the context result with the new content + var notification = new SendingAllowedChildrenNotification(allowedChildren, umbracoContext); + _eventAggregator.Publish(notification); + context.Result = new ObjectResult(notification.Children); + break; } } } From ead813989dd6ded1d941b82b85125361ae8fb407 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 18 Mar 2022 13:36:40 +0100 Subject: [PATCH 15/67] Code of conduct has been moved to an organization-wide repository --- .github/CODE_OF_CONDUCT.md | 93 -------------------------- .github/CODE_OF_CONDUCT_ENFORCEMENT.md | 57 ---------------- 2 files changed, 150 deletions(-) delete mode 100644 .github/CODE_OF_CONDUCT.md delete mode 100644 .github/CODE_OF_CONDUCT_ENFORCEMENT.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 1acc6b602b..0000000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,93 +0,0 @@ -# Umbraco Code of Conduct - -## Preamble - -We are the friendly CMS. And our friendliness stems from our values. That's why we have set for ourselves, Umbraco HQ, and the community, five values to guide us in everything we do: - -* Trust - We believe in and empower people -* Respect - We treat others as we would like to be treated -* Open - We share our thoughts and knowledge -* Hungry - We want to do things better, best is next -* Friendly - We want to build long-lasting relationships - -With these values in mind, we want to offer the Umbraco community a code of conduct that specifies a baseline standard of behavior so that people with different social values and communication styles can work together. - -This code of conduct is based on the widely used Contributor Covenant, as described in [https://www.contributor-covenant.org/](https://www.contributor-covenant.org/) - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. - -## Our Standards -Examples of behavior that contributes to a positive environment for our community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Enforcement Responsibilities - -Community leaders (e.g. Meetup & festival organizers, moderators, maintainers, ...) are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. - -Specific enforcement steps are listed in the [Code of Conduct Enforcement Guidelines](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT_ENFORCEMENT.md) document which is an appendix of this document, updated and maintained by the Code of Conduct Team. - -## Scope -This Code of Conduct applies within all community spaces and events supported by Umbraco HQ or using the Umbraco name. It also applies when an individual is officially representing the community in public spaces. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior, may be reported at [conduct@umbraco.com](mailto:conduct@umbraco.com). All complaints will be reviewed and investigated promptly and fairly. - -Or alternatively, you can reach out directly to any of the team members behind the address above: - -* Sebastiaan Janssen (He, Him - Languages spoken: English, Dutch, Danish(Read)) [sebastiaan@umbraco.com](mailto:sebastiaan@umbraco.com) -* Ilham Boulghallat (She, Her - Languages spoken: English, French, Arabic) [ilham@umbraco.com](mailto:ilham@umbraco.com) -* Arnold Visser (He, Him - Languages spoken: English, Dutch) [arnold@umbraco.com](mailto:arnold@umbraco.com) -* Emma Burstow (She, Her - Languages spoken: English) [ema@umbraco.com](mailto:ema@umbraco.com) - -The review process is done with full respect for the privacy and security of the reporter of any incident. - -People with a conflict of interest should exclude themselves or if necessary be excluded by the other team members. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: - -**1. Correction** -Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. - -Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. - -**2. Warning** -Community Impact: A violation through a single incident or series of actions. - -Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. - -**3. Temporary Ban** -Community Impact: A serious violation of community standards, including sustained inappropriate behavior. - -Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. - -**4. Permanent Ban** -Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. - -Consequence: A permanent ban from any sort of public interaction within the community. - -## Attribution -This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). - -This Code of Conduct will be maintained and reviewed by the team listed above. diff --git a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md deleted file mode 100644 index 2bb45644c2..0000000000 --- a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md +++ /dev/null @@ -1,57 +0,0 @@ -# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder - -These are the steps followed by the [Umbraco Code of Conduct Team](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT.md) when we respond to an issue or incident brought to our attention by a community member. - -This is an appendix to the Code of Conduct and is updated and maintained by the Code of Conduct Team. - -To make sure that all reports will be reviewed and investigated promptly and fairly, as highlighted in the Umbraco Code of Conduct, we are following [Mozilla’s Consequence Ladder approach](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md). - -This approach helps the Team enforce the Code of Conduct in a structured manner and can be used as a way of communicating escalation. Each time the Team takes an action (warning, ban) the individual is made aware of future consequences. The Team can either follow the order of the levels in the ladder or decide to jump levels. When needed, the team can go directly to a permanent ban. - -**Level 0: No Action** -Recommendations do not indicate a violation of the Code of Conduct. - -**Level 1: Simple Warning Issued** -A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. - -**Level 2: Warning** -A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: - -* Communication of next-level consequences if behaviors are repeated (according to this ladder). - -**Level 3: Warning + Mandatory Cooling Off Period (Access Retained)** -A private warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: - -* Request to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). - * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder. -* Require they do not interact with others in the report, or those who they suspect are involved in the report. -* Suggestions for 'out of office' type of message on platforms, to reduce curiosity, or suspicion among those not involved. - -**Level 4: Temporary Ban (Access Revoked)** -Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: - -* 3-6 months imposed break. -* All accounts deactivated, or blocked during this time (Our, HQ Slack if applicable). -* Require to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). - * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder. -* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) suspended. (onboarding/reapplication required outside of this process) -* No attendance at Umbraco events during the ban period. -* Not allowed to enter Umbraco HQ offices during the ban period. -* Permission to use the MVP title, if applicable, is revoked during this ban period. -* The community leaders running events and other initiatives are informed of the ban. - -**Level 5: Permanent Ban** -Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: - -* All accounts deactivated permanently. -* No attendance at Umbraco events going forward. -* Not allowed to enter Umbraco HQ offices permanently. -* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) permanently suspended. -* Permission to use the MVP title, if applicable, revoked. -* The community leaders running events and other initiatives are informed of the ban. - - -Sources: -* [Mozilla Code of Conduct - Enforcement Consequence Ladder](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md) -* [Drupal Conflict Resolution Policy and Process](https://www.drupal.org/conflict-resolution) -* [Django Code of Conduct - Enforcement Manual](https://www.djangoproject.com/conduct/enforcement-manual/) From 4469191c998512c6534861b13048264753fe61f6 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 18 Mar 2022 13:40:03 +0100 Subject: [PATCH 16/67] Updating some more obsolete Code of Conduct usages --- .github/CONTRIBUTING.md | 2 +- .github/README.md | 2 +- umbraco-netcore-only.sln | 1 - umbraco.sln | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 4cb593a39b..e8b378fb15 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -10,7 +10,7 @@ Remember, we're a friendly bunch and are happy with whatever contribution you mi **Code of conduct** -This project and everyone participating in it, is governed by the [our Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk). +This project and everyone participating in it, is governed by the [our Code of Conduct](https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md). **Table of contents** diff --git a/.github/README.md b/.github/README.md index d4565a8cb5..95ba778db2 100644 --- a/.github/README.md +++ b/.github/README.md @@ -15,7 +15,7 @@ See the official [Umbraco website](https://umbraco.com) for an introduction, cor - [Community](#join-the-umbraco-community) - [Contributing](#contributing) -Please also see our [Code of Conduct](CODE_OF_CONDUCT.md). +Please also see our [Code of Conduct](https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md). ## Getting Started diff --git a/umbraco-netcore-only.sln b/umbraco-netcore-only.sln index 80a688bde5..c34dd9e11d 100644 --- a/umbraco-netcore-only.sln +++ b/umbraco-netcore-only.sln @@ -16,7 +16,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-1 ProjectSection(SolutionItems) = preProject .github\BUILD.md = .github\BUILD.md .github\CLEAR.md = .github\CLEAR.md - .github\CODE_OF_CONDUCT.md = .github\CODE_OF_CONDUCT.md .github\CONTRIBUTING.md = .github\CONTRIBUTING.md .github\CONTRIBUTING_DETAILED.md = .github\CONTRIBUTING_DETAILED.md .github\CONTRIBUTION_GUIDELINES.md = .github\CONTRIBUTION_GUIDELINES.md diff --git a/umbraco.sln b/umbraco.sln index 497258c699..7a6c7a04e8 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -15,7 +15,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-1 ProjectSection(SolutionItems) = preProject .github\BUILD.md = .github\BUILD.md .github\CLEAR.md = .github\CLEAR.md - .github\CODE_OF_CONDUCT.md = .github\CODE_OF_CONDUCT.md .github\CONTRIBUTING.md = .github\CONTRIBUTING.md .github\CONTRIBUTING_DETAILED.md = .github\CONTRIBUTING_DETAILED.md .github\CONTRIBUTION_GUIDELINES.md = .github\CONTRIBUTION_GUIDELINES.md From defe9d432b64928a1b7a2287453ee14ca3173cfe Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Thu, 17 Mar 2022 23:59:32 +0000 Subject: [PATCH 17/67] Add a basic oEmbedProvider for LottieFiles animations --- .../UmbracoBuilder.Collections.cs | 3 +- .../Media/EmbedProviders/LottieFiles.cs | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index a0ff6104a7..578498de42 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -118,7 +118,8 @@ namespace Umbraco.Cms.Core.DependencyInjection .Append() .Append() .Append() - .Append(); + .Append() + .Append(); builder.SearchableTrees().Add(() => builder.TypeLoader.GetTypes()); builder.BackOfficeAssets(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs new file mode 100644 index 0000000000..938d22d37d --- /dev/null +++ b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using Umbraco.Cms.Core.Serialization; + +namespace Umbraco.Cms.Core.Media.EmbedProviders +{ + /// + /// Embed Provider for lottiefiles.com the popular opensource JSON-based animation format platform. + /// + public class LottieFiles : OEmbedProviderBase + { + public LottieFiles(IJsonSerializer jsonSerializer) : base(jsonSerializer) + { + + } + + public override string ApiEndpoint => "https://embed.lottiefiles.com/oembed"; + + public override string[] UrlSchemeRegex => new string[] + { + @"lottiefiles\.com/*" + }; + public override Dictionary RequestParams => new Dictionary(); + + public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse oembed = base.GetJsonResponse(requestUrl); + var html = oembed.GetHtml(); + //LottieFiles doesn't seem to support maxwidth and maxheight via oembed + // this is therefore a hack... with regexes.. is that ok? HtmlAgility etc etc + // otherwise it always defaults to 300... + if (maxWidth > 0 && maxHeight > 0) + { + + html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"" + maxWidth + "\""); + html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"" + maxHeight + "\""); + + } + else + { + //if set to 0, let's default to 100% as an easter egg + html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"100%\""); + html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"100%\""); + } + return html; + } + + } +} From 18c2a18ec802baa3db700ccc352446285df596ab Mon Sep 17 00:00:00 2001 From: Vitor Rodrigues Date: Sun, 20 Mar 2022 13:12:45 +0100 Subject: [PATCH 18/67] Fixes RecurringHostServices leaking the execution context / ambient scope (#12022) As timers flow the execution context by default this resulted in the Ambient context scope being shared --- .../HostedServices/RecurringHostedServiceBase.cs | 13 ++++++++++++- .../Services/Implement/CacheInstructionService.cs | 7 +++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index d97737a8f8..1a69533f2d 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -40,7 +40,18 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// public Task StartAsync(CancellationToken cancellationToken) { - _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); + if (!ExecutionContext.IsFlowSuppressed()) + { + using (ExecutionContext.SuppressFlow()) + { + _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); + } + } + else + { + _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); + } + return Task.CompletedTask; } diff --git a/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs b/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs index a037cd1095..ef8acf135f 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs @@ -247,6 +247,13 @@ namespace Umbraco.Cms.Core.Services.Implement /// private bool TryDeserializeInstructions(CacheInstruction instruction, out JArray jsonInstructions) { + if (instruction.Instructions == null) + { + _logger.LogError("Failed to deserialize instructions ({DtoId}: 'null').", instruction.Id); + jsonInstructions = null; + return false; + } + try { jsonInstructions = JsonConvert.DeserializeObject(instruction.Instructions); From b3b4059ec880a623d7d64466b46a6550efe8dc14 Mon Sep 17 00:00:00 2001 From: vsilvar Date: Mon, 21 Mar 2022 15:34:04 +0100 Subject: [PATCH 19/67] Implemented PR suggestions - code simplification Co-authored-by: Ronald Barendse --- .../HostedServices/RecurringHostedServiceBase.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 1a69533f2d..3dda944a3e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -40,14 +40,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// public Task StartAsync(CancellationToken cancellationToken) { - if (!ExecutionContext.IsFlowSuppressed()) - { - using (ExecutionContext.SuppressFlow()) - { - _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); - } - } - else + using (!ExecutionContext.IsFlowSuppressed() ? (IDisposable)ExecutionContext.SuppressFlow() : null) { _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); } From 59eee2e5dd6089344560a4f1a7bda90edbb0771f Mon Sep 17 00:00:00 2001 From: vsilvar Date: Mon, 21 Mar 2022 15:34:32 +0100 Subject: [PATCH 20/67] Implemented PR suggestions - is instead of equality operator Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> --- .../Services/Implement/CacheInstructionService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs b/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs index ef8acf135f..b4af98ad0a 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs @@ -247,7 +247,7 @@ namespace Umbraco.Cms.Core.Services.Implement /// private bool TryDeserializeInstructions(CacheInstruction instruction, out JArray jsonInstructions) { - if (instruction.Instructions == null) + if (instruction.Instructions is null) { _logger.LogError("Failed to deserialize instructions ({DtoId}: 'null').", instruction.Id); jsonInstructions = null; From 267df80161ed661747f7ae2359d7907121bb8f79 Mon Sep 17 00:00:00 2001 From: Vitor Rodrigues Date: Mon, 21 Mar 2022 16:03:16 +0100 Subject: [PATCH 21/67] Ensure exceptions are caught for all recurring hosted services --- .../HostedServices/ContentVersionCleanup.cs | 2 +- .../HostedServices/HealthCheckNotifier.cs | 1 + .../HostedServices/KeepAlive.cs | 2 +- .../HostedServices/LogScrubber.cs | 2 +- .../HostedServices/RecurringHostedServiceBase.cs | 14 +++++++++++--- .../HostedServices/ReportSiteTask.cs | 2 +- .../HostedServices/ScheduledPublishing.cs | 2 +- .../ServerRegistration/InstructionProcessTask.cs | 2 +- .../ServerRegistration/TouchServerTask.cs | 2 +- .../HostedServices/TempFileCleanup.cs | 2 +- 10 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs index 5f3aba5f3f..8c9f3223f0 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs @@ -32,7 +32,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IContentVersionService service, IMainDom mainDom, IServerRoleAccessor serverRoleAccessor) - : base(TimeSpan.FromHours(1), TimeSpan.FromMinutes(3)) + : base(logger, TimeSpan.FromHours(1), TimeSpan.FromMinutes(1)) { _runtimeState = runtimeState; _logger = logger; diff --git a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs index 6a0828fad3..e6d8e75304 100644 --- a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs @@ -61,6 +61,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IProfilingLogger profilingLogger, ICronTabParser cronTabParser) : base( + logger, healthChecksSettings.Value.Notification.Period, healthChecksSettings.Value.GetNotificationDelay(cronTabParser, DateTime.Now, DefaultDelay)) { diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs index 22160b8f6e..3233cfa8f2 100644 --- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs +++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs @@ -49,7 +49,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IProfilingLogger profilingLogger, IServerRoleAccessor serverRegistrar, IHttpClientFactory httpClientFactory) - : base(TimeSpan.FromMinutes(5), DefaultDelay) + : base(logger, TimeSpan.FromMinutes(5), DefaultDelay) { _hostingEnvironment = hostingEnvironment; _mainDom = mainDom; diff --git a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs index 27d9c29e8d..79c1c4b8ea 100644 --- a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs +++ b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IScopeProvider scopeProvider, ILogger logger, IProfilingLogger profilingLogger) - : base(TimeSpan.FromHours(4), DefaultDelay) + : base(logger, TimeSpan.FromHours(4), DefaultDelay) { _mainDom = mainDom; _serverRegistrar = serverRegistrar; diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 3dda944a3e..18fe9fc47f 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -5,6 +5,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -21,6 +22,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// protected static readonly TimeSpan DefaultDelay = TimeSpan.FromMinutes(3); + private readonly ILogger _logger; private TimeSpan _period; private readonly TimeSpan _delay; private Timer _timer; @@ -29,10 +31,12 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// /// Initializes a new instance of the class. /// - /// Timepsan representing how often the task should recur. - /// Timespan represeting the initial delay after application start-up before the first run of the task occurs. - protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) + /// Logger. + /// Timespan representing how often the task should recur. + /// Timespan representing the initial delay after application start-up before the first run of the task occurs. + protected RecurringHostedServiceBase(ILogger logger, TimeSpan period, TimeSpan delay) { + _logger = logger; _period = period; _delay = delay; } @@ -65,6 +69,10 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Hat-tip: https://stackoverflow.com/a/14207615/489433 await PerformExecuteAsync(state); } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception in recurring hosted service {serviceName}.", GetType().Name); + } finally { // Resume now that the task is complete - Note we use period in both because we don't want to execute again after the delay. diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs index cfce96281c..6e5d412e71 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs @@ -23,7 +23,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices public ReportSiteTask( ILogger logger, ITelemetryService telemetryService) - : base(TimeSpan.FromDays(1), TimeSpan.FromMinutes(1)) + : base(logger, TimeSpan.FromDays(1), TimeSpan.FromMinutes(1)) { _logger = logger; _telemetryService = telemetryService; diff --git a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs index 429389273f..fd70c05fc1 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs @@ -71,7 +71,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices ILogger logger, IServerMessenger serverMessenger, IScopeProvider scopeProvider) - : base(TimeSpan.FromMinutes(1), DefaultDelay) + : base(logger, TimeSpan.FromMinutes(1), DefaultDelay) { _runtimeState = runtimeState; _mainDom = mainDom; diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs index 43e2522efd..3aa49f3f71 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs @@ -30,7 +30,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration /// The typed logger. /// The configuration for global settings. public InstructionProcessTask(IRuntimeState runtimeState, IServerMessenger messenger, ILogger logger, IOptions globalSettings) - : base(globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) + : base(logger, globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) { _runtimeState = runtimeState; _messenger = messenger; diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index d54d67338e..5f20a3654e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -44,7 +44,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration ILogger logger, IOptions globalSettings, IServerRoleAccessor serverRoleAccessor) - : base(globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) + : base(logger, globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) { _runtimeState = runtimeState; _serverRegistrationService = serverRegistrationService ?? throw new ArgumentNullException(nameof(serverRegistrationService)); diff --git a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs index e59cca5fbd..8a2a312455 100644 --- a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs @@ -33,7 +33,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// Representation of the main application domain. /// The typed logger. public TempFileCleanup(IIOHelper ioHelper, IMainDom mainDom, ILogger logger) - : base(TimeSpan.FromMinutes(60), DefaultDelay) + : base(logger, TimeSpan.FromMinutes(60), DefaultDelay) { _ioHelper = ioHelper; _mainDom = mainDom; From fe1df8d4ea209e89f3bd4f79c1ffed533f235363 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Tue, 22 Mar 2022 13:47:58 +0100 Subject: [PATCH 22/67] Amend breaking change --- .../HostedServices/RecurringHostedServiceBase.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 18fe9fc47f..3ac04a73c6 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -4,8 +4,10 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -41,6 +43,13 @@ namespace Umbraco.Cms.Infrastructure.HostedServices _delay = delay; } + // Scheduled for removal in V11 + [Obsolete("Please use constructor that takes an ILogger instead")] + protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) + : this(StaticServiceProvider.Instance.GetRequiredService(), period, delay) + { + } + /// public Task StartAsync(CancellationToken cancellationToken) { From 5a613bacf06fa4b5b1bfd3ecb0ab4e4ed4e7a9dc Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Tue, 22 Mar 2022 14:09:47 +0100 Subject: [PATCH 23/67] Amend breaking change v2 --- .../RecurringHostedServiceBase.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 3ac04a73c6..14f779d079 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -24,7 +24,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// protected static readonly TimeSpan DefaultDelay = TimeSpan.FromMinutes(3); - private readonly ILogger _logger; + private readonly ILogger _logger; private TimeSpan _period; private readonly TimeSpan _delay; private Timer _timer; @@ -36,7 +36,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// Logger. /// Timespan representing how often the task should recur. /// Timespan representing the initial delay after application start-up before the first run of the task occurs. - protected RecurringHostedServiceBase(ILogger logger, TimeSpan period, TimeSpan delay) + protected RecurringHostedServiceBase(ILogger logger, TimeSpan period, TimeSpan delay) { _logger = logger; _period = period; @@ -46,7 +46,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Scheduled for removal in V11 [Obsolete("Please use constructor that takes an ILogger instead")] protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) - : this(StaticServiceProvider.Instance.GetRequiredService(), period, delay) + : this(StaticServiceProvider.Instance.GetRequiredService().CreateLogger(), period, delay) { } @@ -80,7 +80,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices } catch (Exception ex) { - _logger.LogError(ex, "Unhandled exception in recurring hosted service {serviceName}.", GetType().Name); + _logger.LogError(ex, "Unhandled exception in recurring hosted service {serviceName}."); } finally { @@ -120,4 +120,14 @@ namespace Umbraco.Cms.Infrastructure.HostedServices GC.SuppressFinalize(this); } } + + public class RecurringHostedServiceBaseImpl : RecurringHostedServiceBase + { + + public RecurringHostedServiceBaseImpl(TimeSpan period, TimeSpan delay) : base(period, delay) + { + } + + public override Task PerformExecuteAsync(object state) => Task.CompletedTask; + } } From b55ee70fe84dcf279c2d05a102dd36f34dbd5ea0 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Tue, 22 Mar 2022 14:14:54 +0100 Subject: [PATCH 24/67] Switched from service location to StaticAplicationLogging --- .../HostedServices/RecurringHostedServiceBase.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 14f779d079..e78374205c 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -4,10 +4,9 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Umbraco.Cms.Web.Common.DependencyInjection; +using Umbraco.Cms.Core; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -46,7 +45,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Scheduled for removal in V11 [Obsolete("Please use constructor that takes an ILogger instead")] protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) - : this(StaticServiceProvider.Instance.GetRequiredService().CreateLogger(), period, delay) + : this(StaticApplicationLogging.CreateLogger(), period, delay) { } From cca29bf723c622947f1c113782fee7fbbda3f606 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Tue, 22 Mar 2022 14:16:46 +0100 Subject: [PATCH 25/67] Don't commit test classes. --- .../HostedServices/RecurringHostedServiceBase.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index e78374205c..a4ab41fd7a 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -119,14 +119,4 @@ namespace Umbraco.Cms.Infrastructure.HostedServices GC.SuppressFinalize(this); } } - - public class RecurringHostedServiceBaseImpl : RecurringHostedServiceBase - { - - public RecurringHostedServiceBaseImpl(TimeSpan period, TimeSpan delay) : base(period, delay) - { - } - - public override Task PerformExecuteAsync(object state) => Task.CompletedTask; - } } From fd701068ae4bfa7a80db4e3fb1e465551eac09d2 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 22 Mar 2022 14:18:12 +0100 Subject: [PATCH 26/67] Update src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs Co-authored-by: Paul Johnson --- .../HostedServices/RecurringHostedServiceBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index a4ab41fd7a..2b63f45734 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -23,7 +23,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// protected static readonly TimeSpan DefaultDelay = TimeSpan.FromMinutes(3); - private readonly ILogger _logger; + private readonly ILogger _logger; private TimeSpan _period; private readonly TimeSpan _delay; private Timer _timer; From c6bfc61909f81351d950d1439468bf3a1a7ed3b1 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 22 Mar 2022 14:21:53 +0100 Subject: [PATCH 27/67] Update src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs Co-authored-by: Paul Johnson --- .../HostedServices/RecurringHostedServiceBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 2b63f45734..32e2ab188d 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -35,7 +35,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// Logger. /// Timespan representing how often the task should recur. /// Timespan representing the initial delay after application start-up before the first run of the task occurs. - protected RecurringHostedServiceBase(ILogger logger, TimeSpan period, TimeSpan delay) + protected RecurringHostedServiceBase(ILogger logger, TimeSpan period, TimeSpan delay) { _logger = logger; _period = period; From 3f7f2797a6cd0ef764bea5c3f5f87238d8fff0ab Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Tue, 22 Mar 2022 14:52:39 +0100 Subject: [PATCH 28/67] Add CreateLogger method so we still can get types using the legacy ctor --- src/Umbraco.Core/StaticApplicationLogging.cs | 6 ++++++ .../HostedServices/RecurringHostedServiceBase.cs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/StaticApplicationLogging.cs b/src/Umbraco.Core/StaticApplicationLogging.cs index e216011014..0f32075144 100644 --- a/src/Umbraco.Core/StaticApplicationLogging.cs +++ b/src/Umbraco.Core/StaticApplicationLogging.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -18,5 +19,10 @@ namespace Umbraco.Cms.Core { return _loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); } + + public static ILogger CreateLogger(Type type) + { + return _loggerFactory?.CreateLogger(type) ?? NullLoggerFactory.Instance.CreateLogger(type); + } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 32e2ab188d..7686c357a4 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -45,8 +45,10 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Scheduled for removal in V11 [Obsolete("Please use constructor that takes an ILogger instead")] protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) - : this(StaticApplicationLogging.CreateLogger(), period, delay) { + _period = period; + _delay = delay; + _logger = StaticApplicationLogging.CreateLogger(GetType()); } /// From 10ae51273166978f410e49590091ad4727da840b Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Tue, 22 Mar 2022 15:12:51 +0100 Subject: [PATCH 29/67] Use ILoggerFactory instead of StaticApplicationLogging --- src/Umbraco.Core/StaticApplicationLogging.cs | 5 ----- .../HostedServices/RecurringHostedServiceBase.cs | 5 +++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/StaticApplicationLogging.cs b/src/Umbraco.Core/StaticApplicationLogging.cs index 0f32075144..73078b0f42 100644 --- a/src/Umbraco.Core/StaticApplicationLogging.cs +++ b/src/Umbraco.Core/StaticApplicationLogging.cs @@ -19,10 +19,5 @@ namespace Umbraco.Cms.Core { return _loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); } - - public static ILogger CreateLogger(Type type) - { - return _loggerFactory?.CreateLogger(type) ?? NullLoggerFactory.Instance.CreateLogger(type); - } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 7686c357a4..d906738430 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -4,9 +4,10 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -48,7 +49,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices { _period = period; _delay = delay; - _logger = StaticApplicationLogging.CreateLogger(GetType()); + _logger = StaticServiceProvider.Instance.GetRequiredService().CreateLogger(GetType()); } /// From 496e281f2a84bb55c6c638038986c79cf38e7b24 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Tue, 22 Mar 2022 15:34:27 +0100 Subject: [PATCH 30/67] Add back service name --- .../HostedServices/RecurringHostedServiceBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index d906738430..c1c7cdf3cf 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -82,7 +82,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices } catch (Exception ex) { - _logger.LogError(ex, "Unhandled exception in recurring hosted service {serviceName}."); + _logger.LogError(ex, "Unhandled exception in recurring hosted service."); } finally { From 7114c564da5f0b753e2f42d01a4a1cf4e08d78fb Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 23 Mar 2022 08:27:48 +0100 Subject: [PATCH 31/67] Amend unintentional change --- .../HostedServices/ContentVersionCleanup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs index 8c9f3223f0..d037c91d86 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs @@ -32,7 +32,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IContentVersionService service, IMainDom mainDom, IServerRoleAccessor serverRoleAccessor) - : base(logger, TimeSpan.FromHours(1), TimeSpan.FromMinutes(1)) + : base(logger, TimeSpan.FromHours(1), TimeSpan.FromMinutes(3)) { _runtimeState = runtimeState; _logger = logger; From 538fb173c2121685fb9ac1f46547a97f80de473d Mon Sep 17 00:00:00 2001 From: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com> Date: Wed, 23 Mar 2022 17:05:12 +0100 Subject: [PATCH 32/67] Fixes #11169 Should also be ported to v10. --- .../ModelsBuilder/InMemoryModelFactory.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs index 44e08f0af1..419352c56c 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs @@ -799,8 +799,12 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder { if (disposing) { - _watcher.EnableRaisingEvents = false; - _watcher.Dispose(); + if (_watcher != null) + { + _watcher.EnableRaisingEvents = false; + _watcher.Dispose(); + } + _locker.Dispose(); } From f5d0abc28bb93bbd920c8bcf2ca3313dddc6f6bc Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Mar 2022 13:48:43 +0100 Subject: [PATCH 33/67] Move templates to root --- build/azure-pipelines.yml | 4 +- build/build.ps1 | 63 +++++------------- .../UmbracoProject/.template.config/icon.png | Bin 22265 -> 0 bytes .../UmbracoProject/Views/_ViewImports.cshtml | 8 --- .../Umbraco.Templates.nuspec | 11 ++- .../.template.config/dotnetcli.host.json | 0 .../.template.config/ide.host.json | 2 +- .../.template.config/template.json | 0 .../UmbracoPackage/package.manifest | 0 .../UmbracoPackage/UmbracoPackage.csproj | 0 .../build/UmbracoPackage.targets | 0 .../UmbracoProject/.gitignore | 0 .../.template.config/dotnetcli.host.json | 0 .../.template.config/ide.host.json | 2 +- .../.template.config/template.json | 0 .../Properties/launchSettings.json | 0 .../UmbracoProject/UmbracoProject.csproj | 0 .../appsettings.Development.json | 0 .../UmbracoProject/appsettings.json | 0 .../.template.config => templates}/icon.png | Bin 20 files changed, 28 insertions(+), 62 deletions(-) delete mode 100644 build/templates/UmbracoProject/.template.config/icon.png delete mode 100644 build/templates/UmbracoProject/Views/_ViewImports.cshtml rename {build/templates => templates}/Umbraco.Templates.nuspec (64%) rename {build/templates => templates}/UmbracoPackage/.template.config/dotnetcli.host.json (100%) rename {build/templates => templates}/UmbracoPackage/.template.config/ide.host.json (89%) rename {build/templates => templates}/UmbracoPackage/.template.config/template.json (100%) rename {build/templates => templates}/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest (100%) rename {build/templates => templates}/UmbracoPackage/UmbracoPackage.csproj (100%) rename {build/templates => templates}/UmbracoPackage/build/UmbracoPackage.targets (100%) rename {build/templates => templates}/UmbracoProject/.gitignore (100%) rename {build/templates => templates}/UmbracoProject/.template.config/dotnetcli.host.json (100%) rename {build/templates => templates}/UmbracoProject/.template.config/ide.host.json (98%) rename {build/templates => templates}/UmbracoProject/.template.config/template.json (100%) rename {build/templates => templates}/UmbracoProject/Properties/launchSettings.json (100%) rename {build/templates => templates}/UmbracoProject/UmbracoProject.csproj (100%) rename {build/templates => templates}/UmbracoProject/appsettings.Development.json (100%) rename {build/templates => templates}/UmbracoProject/appsettings.json (100%) rename {build/templates/UmbracoPackage/.template.config => templates}/icon.png (100%) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 38d5e7673b..064b7aec0b 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -414,7 +414,7 @@ stages: #Update the version in templates also $templatePath = - 'build/templates/UmbracoProject/.template.config/template.json' + 'templates/UmbracoProject/.template.config/template.json' $a = Get-Content $templatePath -raw | ConvertFrom-Json @@ -424,7 +424,7 @@ stages: $templatePath = - 'build/templates/UmbracoPackage/.template.config/template.json' + 'templates/UmbracoPackage/.template.config/template.json' $a = Get-Content $templatePath -raw | ConvertFrom-Json diff --git a/build/build.ps1 b/build/build.ps1 index da4733d432..24fd548c61 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -11,7 +11,7 @@ [Alias("loc")] [switch] $local = $false, - # enable docfx + # enable docfx [Parameter(Mandatory=$false)] [Alias("doc")] [switch] $docfx = $false, @@ -40,7 +40,7 @@ @{ Continue = $continue }) if ($ubuild.OnError()) { return } - Write-Host "Umbraco Cms Build" + Write-Host "Umbraco CMS Build" Write-Host "Umbraco.Build v$($ubuild.BuildVersion)" # ################################################################ @@ -84,7 +84,7 @@ $this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache) $this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix) - $ignore = $this.ClearEnvVar("NODE_NO_HTTP2") + $this.ClearEnvVar("NODE_NO_HTTP2") }) $ubuild.DefineMethod("CompileBelle", @@ -171,11 +171,6 @@ $src = "$($this.SolutionRoot)\src" $log = "$($this.BuildTemp)\build.umbraco.log" - if ($this.BuildEnv.VisualStudio -eq $null) - { - throw "Build environment does not provide VisualStudio." - } - Write-Host "Compile Umbraco" Write-Host "Logging to $log" @@ -255,27 +250,21 @@ $buildConfiguration = "Release" $log = "$($this.BuildTemp)\msbuild.tests.log" - if ($this.BuildEnv.VisualStudio -eq $null) - { - throw "Build environment does not provide VisualStudio." - } - Write-Host "Compile Tests" Write-Host "Logging to $log" # beware of the weird double \\ at the end of paths # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ - &$this.BuildEnv.VisualStudio.MsBuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" ` - /p:WarningLevel=0 ` - /p:Configuration=$buildConfiguration ` - /p:Platform=AnyCPU ` - /p:UseWPP_CopyWebApplication=True ` - /p:PipelineDependsOnBuild=False ` - /p:OutDir="$($this.BuildTemp)\tests\\" ` - /p:Verbosity=minimal ` - /t:Build ` - /tv:"$($this.BuildEnv.VisualStudio.ToolsVersion)" ` - /p:UmbracoBuild=True ` + &dotnet msbuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" ` + -target:Build ` + -property:WarningLevel=0 ` + -property:Configuration=$buildConfiguration ` + -property:Platform=AnyCPU ` + -property:UseWPP_CopyWebApplication=True ` + -property:PipelineDependsOnBuild=False ` + -property:OutDir="$($this.BuildTemp)\tests\\" ` + -property:Verbosity=minimal ` + -property:UmbracoBuild=True ` > $log # copy Umbraco.Persistence.SqlCe files into WebApp @@ -292,10 +281,6 @@ $src = "$($this.SolutionRoot)\src" $tmp = "$($this.BuildTemp)" - $out = "$($this.BuildOutput)" - $templates = "$($this.SolutionRoot)\build\templates" - - $buildConfiguration = "Release" # cleanup build Write-Host "Clean build" @@ -309,7 +294,6 @@ # create directories Write-Host "Create directories" mkdir "$tmp\WebApp\App_Data" > $null - mkdir "$tmp\Templates" > $null #mkdir "$tmp\WebApp\Media" > $null #mkdir "$tmp\WebApp\Views" > $null @@ -332,10 +316,6 @@ { $nugetPackages = [System.Environment]::ExpandEnvironmentVariables("%userprofile%\.nuget\packages") } - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\bin\x86") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\bin\amd64") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\WebApp\bin\x86") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\WebApp\bin\amd64") # copy Belle Write-Host "Copy Belle" @@ -343,19 +323,6 @@ $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\js", "*", "$tmp\WebApp\wwwroot\umbraco\js") $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\lib", "*", "$tmp\WebApp\wwwroot\umbraco\lib") $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\views", "*", "$tmp\WebApp\wwwroot\umbraco\views") - - - - # Prepare templates - Write-Host "Copy template files" - $this.CopyFiles("$templates", "*", "$tmp\Templates") - - Write-Host "Copy files for dotnet templates" - $this.CopyFiles("$src\Umbraco.Web.UI", "Program.cs", "$tmp\Templates\UmbracoProject") - $this.CopyFiles("$src\Umbraco.Web.UI", "Startup.cs", "$tmp\Templates\UmbracoProject") - $this.CopyFiles("$src\Umbraco.Web.UI\Views", "*", "$tmp\Templates\UmbracoProject\Views") - - $this.RemoveDirectory("$tmp\Templates\UmbracoProject\bin") }) @@ -389,7 +356,7 @@ { Write-Host "Restore NuGet" Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log" - $params = "-Source", $nugetsourceUmbraco + $params = "-Source", $nugetsourceUmbraco &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\umbraco-netcore-only.sln" > "$($this.BuildTemp)\nuget.restore.log" @params if (-not $?) { throw "Failed to restore NuGet packages." } }) @@ -397,7 +364,7 @@ $ubuild.DefineMethod("PackageNuGet", { $nuspecs = "$($this.SolutionRoot)\build\NuSpecs" - $templates = "$($this.BuildTemp)\Templates" + $templates = "$($this.SolutionRoot)\templates" Write-Host "Create NuGet packages" diff --git a/build/templates/UmbracoProject/.template.config/icon.png b/build/templates/UmbracoProject/.template.config/icon.png deleted file mode 100644 index 6e94105808e0f05cb21726b4f729c979ed7d33ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22265 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 diff --git a/build/templates/UmbracoProject/Views/_ViewImports.cshtml b/build/templates/UmbracoProject/Views/_ViewImports.cshtml deleted file mode 100644 index cb9a0b658e..0000000000 --- a/build/templates/UmbracoProject/Views/_ViewImports.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@using Umbraco.Web.UI -@using Umbraco.Extensions -@using Umbraco.Web.PublishedModels -@using Umbraco.Cms.Core.Models.PublishedContent -@using Microsoft.AspNetCore.Html -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, Smidge -@inject Smidge.SmidgeHelper SmidgeHelper diff --git a/build/templates/Umbraco.Templates.nuspec b/templates/Umbraco.Templates.nuspec similarity index 64% rename from build/templates/Umbraco.Templates.nuspec rename to templates/Umbraco.Templates.nuspec index 21201d8d55..6561a41060 100644 --- a/build/templates/Umbraco.Templates.nuspec +++ b/templates/Umbraco.Templates.nuspec @@ -14,8 +14,15 @@ umbraco - + + + + + + + + + - diff --git a/build/templates/UmbracoPackage/.template.config/dotnetcli.host.json b/templates/UmbracoPackage/.template.config/dotnetcli.host.json similarity index 100% rename from build/templates/UmbracoPackage/.template.config/dotnetcli.host.json rename to templates/UmbracoPackage/.template.config/dotnetcli.host.json diff --git a/build/templates/UmbracoPackage/.template.config/ide.host.json b/templates/UmbracoPackage/.template.config/ide.host.json similarity index 89% rename from build/templates/UmbracoPackage/.template.config/ide.host.json rename to templates/UmbracoPackage/.template.config/ide.host.json index 8d3bae3e3c..aa4eb34552 100644 --- a/build/templates/UmbracoPackage/.template.config/ide.host.json +++ b/templates/UmbracoPackage/.template.config/ide.host.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/vs-2017.3.host", "order" : 0, - "icon": "icon.png", + "icon": "../../icon.png", "description": { "id": "UmbracoPackage", "text": "Umbraco Package - An empty Umbraco CMS package (Plugin)" diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json similarity index 100% rename from build/templates/UmbracoPackage/.template.config/template.json rename to templates/UmbracoPackage/.template.config/template.json diff --git a/build/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest b/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest similarity index 100% rename from build/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest rename to templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest diff --git a/build/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj similarity index 100% rename from build/templates/UmbracoPackage/UmbracoPackage.csproj rename to templates/UmbracoPackage/UmbracoPackage.csproj diff --git a/build/templates/UmbracoPackage/build/UmbracoPackage.targets b/templates/UmbracoPackage/build/UmbracoPackage.targets similarity index 100% rename from build/templates/UmbracoPackage/build/UmbracoPackage.targets rename to templates/UmbracoPackage/build/UmbracoPackage.targets diff --git a/build/templates/UmbracoProject/.gitignore b/templates/UmbracoProject/.gitignore similarity index 100% rename from build/templates/UmbracoProject/.gitignore rename to templates/UmbracoProject/.gitignore diff --git a/build/templates/UmbracoProject/.template.config/dotnetcli.host.json b/templates/UmbracoProject/.template.config/dotnetcli.host.json similarity index 100% rename from build/templates/UmbracoProject/.template.config/dotnetcli.host.json rename to templates/UmbracoProject/.template.config/dotnetcli.host.json diff --git a/build/templates/UmbracoProject/.template.config/ide.host.json b/templates/UmbracoProject/.template.config/ide.host.json similarity index 98% rename from build/templates/UmbracoProject/.template.config/ide.host.json rename to templates/UmbracoProject/.template.config/ide.host.json index 1ee7a492aa..d44cb154c1 100644 --- a/build/templates/UmbracoProject/.template.config/ide.host.json +++ b/templates/UmbracoProject/.template.config/ide.host.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/vs-2017.3.host", "order" : 0, - "icon": "icon.png", + "icon": "../../icon.png", "description": { "id": "UmbracoProject", "text": "Umbraco Web Application - An empty Umbraco CMS web application" diff --git a/build/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json similarity index 100% rename from build/templates/UmbracoProject/.template.config/template.json rename to templates/UmbracoProject/.template.config/template.json diff --git a/build/templates/UmbracoProject/Properties/launchSettings.json b/templates/UmbracoProject/Properties/launchSettings.json similarity index 100% rename from build/templates/UmbracoProject/Properties/launchSettings.json rename to templates/UmbracoProject/Properties/launchSettings.json diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj similarity index 100% rename from build/templates/UmbracoProject/UmbracoProject.csproj rename to templates/UmbracoProject/UmbracoProject.csproj diff --git a/build/templates/UmbracoProject/appsettings.Development.json b/templates/UmbracoProject/appsettings.Development.json similarity index 100% rename from build/templates/UmbracoProject/appsettings.Development.json rename to templates/UmbracoProject/appsettings.Development.json diff --git a/build/templates/UmbracoProject/appsettings.json b/templates/UmbracoProject/appsettings.json similarity index 100% rename from build/templates/UmbracoProject/appsettings.json rename to templates/UmbracoProject/appsettings.json diff --git a/build/templates/UmbracoPackage/.template.config/icon.png b/templates/icon.png similarity index 100% rename from build/templates/UmbracoPackage/.template.config/icon.png rename to templates/icon.png From b59493646358ec453c1e6446aecfcadf0f511169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Hoskam?= Date: Sun, 27 Mar 2022 23:32:23 +0200 Subject: [PATCH 34/67] Update UmbracoProject.csproj (#12164) * Update UmbracoProject.csproj Changing the comment would make the intent of the comment more clear, as you cannot use InMemoryAuto on compiled Razor views, but you're still allowed to keep it as false if you're using a different ModelsBuilder mode. * Update UmbracoProject.csproj Hoping this kicks off the PR checks... --- build/templates/UmbracoProject/UmbracoProject.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/build/templates/UmbracoProject/UmbracoProject.csproj index 6b47686415..1f98d24041 100644 --- a/build/templates/UmbracoProject/UmbracoProject.csproj +++ b/build/templates/UmbracoProject/UmbracoProject.csproj @@ -31,7 +31,7 @@ true - + false false From 76c1bce7d77301ec9c601d0ac8782afdef24d549 Mon Sep 17 00:00:00 2001 From: Reiter Date: Sat, 26 Mar 2022 11:06:22 +0100 Subject: [PATCH 35/67] only show groups user has access to --- .../src/views/users/views/groups/groups.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js index 4b81e7c11c..f7e1590da8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js @@ -26,7 +26,7 @@ userService.getCurrentUser().then(function(user) { currentUser = user; // Get usergroups - userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) { + userGroupsResource.getUserGroups().then(function (userGroups) { // only allow editing and selection if user is member of the group or admin vm.userGroups = _.map(userGroups, function (ug) { From 9203cffd27cacacdb1409a274a021a4a21f0d4f3 Mon Sep 17 00:00:00 2001 From: Ji Pattison-Smith Date: Tue, 29 Mar 2022 11:30:22 +0100 Subject: [PATCH 36/67] Pass culture code into GetAtRoot method in Siblings extensions to ensure the passed culture code is respected --- src/Umbraco.Core/Extensions/PublishedContentExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs index 197f6f6d63..1731907be6 100644 --- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs @@ -1082,7 +1082,7 @@ namespace Umbraco.Extensions { return content.Parent != null ? content.Parent.Children(variationContextAccessor, culture) - : publishedSnapshot.Content.GetAtRoot().WhereIsInvariantOrHasCulture(variationContextAccessor, culture); + : publishedSnapshot.Content.GetAtRoot(culture).WhereIsInvariantOrHasCulture(variationContextAccessor, culture); } /// @@ -1098,7 +1098,7 @@ namespace Umbraco.Extensions { return content.Parent != null ? content.Parent.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture) - : publishedSnapshot.Content.GetAtRoot().OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(variationContextAccessor, culture); + : publishedSnapshot.Content.GetAtRoot(culture).OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(variationContextAccessor, culture); } /// @@ -1115,7 +1115,7 @@ namespace Umbraco.Extensions { return content.Parent != null ? content.Parent.Children(variationContextAccessor, culture) - : publishedSnapshot.Content.GetAtRoot().OfType().WhereIsInvariantOrHasCulture(variationContextAccessor, culture); + : publishedSnapshot.Content.GetAtRoot(culture).OfType().WhereIsInvariantOrHasCulture(variationContextAccessor, culture); } #endregion From db12594332bb11f453e3b0331028bf4b4954fe7e Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Wed, 30 Mar 2022 17:48:31 +0100 Subject: [PATCH 37/67] Making an update for appearances sake (#12199) * Update spelling of appearance in 'Editor appearance' key was incorrectly apperance * Update spelling of appearance in 'headlineEditorAppearance' key was incorrectly apperance --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 6b5d301c5f..f982f35769 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -2675,7 +2675,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Create new Element Type Custom stylesheet Add stylesheet - Editor apperance + Editor appearance Data models Catalogue appearance Background color diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index e056647ebc..2db73b4262 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -2764,7 +2764,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Create new Element Type Custom stylesheet Add stylesheet - Editor apperance + Editor appearance Data models Catalogue appearance Background color From c218c068bd6b2bd3daad0e8c6938a9acd7ee344b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Hoskam?= Date: Thu, 31 Mar 2022 17:12:16 +0200 Subject: [PATCH 38/67] Update nl.xml --- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index d9ecc89673..8f94e8694d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -82,6 +82,7 @@ Toegang toestaan om een node te vertalen Toegang toestaan om een node op te slaan Toegang toestaan om Inhoudssjabloon aan te maken + Toegang toestaan om meldingen voor content nodes aan te maken Inhoud @@ -256,6 +257,7 @@ Statistieken Titel (optioneel) Alternatieve tekst (optioneel) + Bijschrift (optioneel) Type Depubliceren Concept @@ -694,6 +696,7 @@ Wissen Sluiten Sluit venster + Sluit paneel Comment Bevestig Beperken @@ -2219,6 +2222,9 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Geen relaties voor dit relatietype. Relatietype Relaties + Is Afhankelijkheid + Ja + Nee Aan de slag From 6eebd9660530076a27a0f442edd6c35cad964499 Mon Sep 17 00:00:00 2001 From: Reiter Date: Tue, 29 Mar 2022 18:29:50 +0200 Subject: [PATCH 39/67] add null check to multiurlpickervalueconverter IsValue() function umbraco#12186 --- .../ValueConverters/MultiUrlPickerValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs index 281f6c58eb..8b9093c1ca 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs @@ -41,7 +41,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; - public override bool? IsValue(object value, PropertyValueLevel level) => value?.ToString() != "[]"; + public override bool? IsValue(object value, PropertyValueLevel level) => value != null && value.ToString() != "[]"; public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => source?.ToString(); From 146f938051de358983c44d8444c3d13c70522466 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Fri, 1 Apr 2022 13:30:32 +0200 Subject: [PATCH 40/67] Do a full save on first member login --- src/Umbraco.Infrastructure/Security/MemberUserStore.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 345a404fcf..d2b0db111c 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -597,7 +597,10 @@ namespace Umbraco.Cms.Core.Security || (member.LastLoginDate != default && identityUser.LastLoginDateUtc.HasValue == false) || (identityUser.LastLoginDateUtc.HasValue && member.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)) { - changeType = MemberDataChangeType.LoginOnly; + // If the LastLoginDate is default on the member we have to do a full save. + // This is because the umbraco property data for the member doesn't exist yet in this case + // meaning we can't just update that property data, but have to do a full save to create it + changeType = member.LastLoginDate == default ? MemberDataChangeType.FullSave : MemberDataChangeType.LoginOnly; // if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime DateTime dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime(); From ddddaeb113ed79014fe28cc3e6066bcbf9db2e34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Mar 2022 21:32:57 +0000 Subject: [PATCH 41/67] Bump minimist from 1.2.5 to 1.2.6 in /tests/Umbraco.Tests.AcceptanceTest Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: minimist dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tests/Umbraco.Tests.AcceptanceTest/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index fb622bbffb..478f3cc53e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -1194,9 +1194,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "ms": { From 534ca928b48c625e65a2c4c7edda0565aef01a5d Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sun, 3 Apr 2022 21:46:36 +0200 Subject: [PATCH 42/67] Use icon component for icons --- .../upload/umb-property-file-upload.html | 36 +++++++------------ .../src/views/packages/views/repo.html | 6 ++-- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html index a6df878997..f50722d421 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html @@ -1,20 +1,17 @@ -
+
+ ng-required="vm.required && !vm.files.length" />
+ ng-hide="vm.files.length > 0">

Click to upload

- + +
@@ -25,38 +22,31 @@ source="file.fileSrc" name="file.fileName" client-side="file.isClientSide" - client-side-data="file.fileData" - > + client-side-data="file.fileData"> +
diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html index 7dc3e499fe..f7e976de1e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html @@ -300,9 +300,9 @@
External sources
From 057b304a5a95ed45892ff3aa06a0a87f6449edc9 Mon Sep 17 00:00:00 2001 From: Johannes Lantz Date: Mon, 4 Apr 2022 17:14:03 +0200 Subject: [PATCH 43/67] Added "move" action for dictionaries (#12193) * Added "move" action for dictionaries * Replaced DictionaryMove with MoveOrCopy for PostMove * Removed int parse for dictionary postmove id & parentId, changed paramtype for move in dictionary.resource * Added localizedText for new dictionary validationProblems & adjusted nullcheck for move.ParentId * Fixed logic for move dictionary parent --- .../Controllers/DictionaryController.cs | 35 ++++++++++ .../Trees/DictionaryTreeController.cs | 4 ++ .../common/resources/dictionary.resource.js | 45 ++++++++++++- .../src/views/dictionary/move.controller.js | 66 +++++++++++++++++++ .../src/views/dictionary/move.html | 53 +++++++++++++++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 + .../umbraco/config/lang/en_us.xml | 2 + 7 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/dictionary/move.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/dictionary/move.html diff --git a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs index 4d0e2cfe14..6710d61d14 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net.Http; +using System.Net.Mime; +using System.Text; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -191,6 +193,39 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return _umbracoMapper.Map(dictionary); } + /// + /// Changes the structure for dictionary items + /// + /// + /// + public IActionResult PostMove(MoveOrCopy move) + { + var dictionaryItem = _localizationService.GetDictionaryItemById(move.Id); + if (dictionaryItem == null) + return ValidationProblem(_localizedTextService.Localize("dictionary", "itemDoesNotExists")); + + var parent = _localizationService.GetDictionaryItemById(move.ParentId); + if (parent == null) + { + if (move.ParentId == Constants.System.Root) + dictionaryItem.ParentId = null; + else + return ValidationProblem(_localizedTextService.Localize("dictionary", "parentDoesNotExists")); + } + else + { + dictionaryItem.ParentId = parent.Key; + if (dictionaryItem.Key == parent.ParentId) + return ValidationProblem(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath")); + } + + _localizationService.Save(dictionaryItem); + + var model = _umbracoMapper.Map(dictionaryItem); + + return Content(model.Path, MediaTypeNames.Text.Plain, Encoding.UTF8); + } + /// /// Saves a dictionary item /// diff --git a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs index c61fabfa7b..bd7db36ba1 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs @@ -126,7 +126,11 @@ namespace Umbraco.Cms.Web.BackOffice.Trees menu.Items.Add(LocalizedTextService, opensDialog: true); if (id != Constants.System.RootString) + { menu.Items.Add(LocalizedTextService, true, opensDialog: true); + menu.Items.Add(LocalizedTextService, true, opensDialog: true); + } + menu.Items.Add(new RefreshNode(LocalizedTextService, true)); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/dictionary.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/dictionary.resource.js index 3bb01fbe92..38a96fbcda 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/dictionary.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/dictionary.resource.js @@ -1,4 +1,4 @@ -/** +/** * @ngdoc service * @name umbraco.resources.dictionaryResource * @description Loads in data for dictionary items @@ -96,6 +96,48 @@ function dictionaryResource($q, $http, $location, umbRequestHelper, umbDataForma "Failed to get item " + id); } + /** + * @ngdoc method + * @name umbraco.resources.dictionaryResource#move + * @methodOf umbraco.resources.dictionaryResource + * + * @description + * Moves a dictionary item underneath a new parentId + * + * ##usage + *
+      * dictionaryResource.move({ parentId: 1244, id: 123 })
+      *    .then(function() {
+      *        alert("node was moved");
+      *    }, function(err){
+      *      alert("node didnt move:" + err.data.Message);
+      *    });
+      * 
+ * @param {Object} args arguments object + * @param {int} args.id the int of the dictionary item to move + * @param {int} args.parentId the int of the parent dictionary item to move to + * @returns {Promise} resourcePromise object. + * + */ + function move (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dictionaryApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }, { responseType: 'text' })); + } + /** * @ngdoc method * @name umbraco.resources.dictionaryResource#save @@ -151,6 +193,7 @@ function dictionaryResource($q, $http, $location, umbRequestHelper, umbDataForma create: create, getById: getById, save: save, + move: move, getList : getList }; diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/dictionary/move.controller.js new file mode 100644 index 0000000000..a99f09af21 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/move.controller.js @@ -0,0 +1,66 @@ +angular.module("umbraco") + .controller("Umbraco.Editors.Dictionary.MoveController", + function ($scope, dictionaryResource, treeService, navigationService, notificationsService, appState, eventsService) { + + $scope.dialogTreeApi = {}; + $scope.source = _.clone($scope.currentNode); + + function nodeSelectHandler(args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.move = function () { + + $scope.busy = true; + $scope.error = false; + + dictionaryResource.move({ parentId: $scope.target.id, id: $scope.source.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + + navigationService.syncTree({ tree: "dictionary", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "dictionary", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + eventsService.emit('app.refreshEditor'); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + + }); + }; + + $scope.onTreeInit = function () { + $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); + }; + + $scope.close = function() { + navigationService.hideDialog(); + }; + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/move.html b/src/Umbraco.Web.UI.Client/src/views/dictionary/move.html new file mode 100644 index 0000000000..d8accbb5d5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/move.html @@ -0,0 +1,53 @@ +
+ +
+
+ +

+ Choose where to move {{source.name}} to in the tree structure below +

+ + + +
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
+
+ +
+
+ {{source.name}} was moved underneath {{target.name}} +
+ +
+ +
+ +
+ + +
+ +
+
+
+ + +
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f982f35769..7140d0850a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -569,6 +569,8 @@ Modifying layout will result in loss of data for any existing content that is based on this configuration. + Dictionary item does not exist. + Parent item does not exist. There are no dictionary items. Create dictionary item diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 2db73b4262..8111463e68 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -579,6 +579,8 @@ Modifying layout will result in loss of data for any existing content that is based on this configuration. + Dictionary item does not exist. + Parent item does not exist. There are no dictionary items. Create dictionary item From 45e7c10cb83b48bff672ff12633dff97de146f3e Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Tue, 5 Apr 2022 08:14:17 +1000 Subject: [PATCH 44/67] busfy => busy --- src/Umbraco.Web.UI.Client/src/views/users/user.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 79164a2457..684ce6d2f0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -374,7 +374,7 @@ } function enableUser() { - vm.enableUserButtonState = "busfy"; + vm.enableUserButtonState = "busy"; usersResource.enableUsers([vm.user.id]).then(function (data) { vm.user.userState = "Active"; setUserDisplayState(); From 6423529dd0abb7c5755980fde62cac5f5bfc802f Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Mon, 4 Apr 2022 09:22:38 +1000 Subject: [PATCH 45/67] ensure proper cleanup to avoid lingering scopes causing memory leaks --- .../src/common/services/tinymce.service.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 98543a7c68..3273b8a2fb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -1599,6 +1599,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s syncContent(); }); + // When the element is removed from the DOM, we need to terminate + // any active watchers to ensure scopes are disposed and do not leak. + // No need to sync content as that has already happened. + args.editor.on('remove', () => stopWatch()); + args.editor.on('ObjectResized', function (e) { var srcAttr = $(e.target).attr("src"); var path = srcAttr.split("?")[0]; From 7d8a07e703cc87c1fc14b912bbbef0df790c6a0f Mon Sep 17 00:00:00 2001 From: Jeavon Date: Wed, 6 Apr 2022 00:35:53 +0100 Subject: [PATCH 46/67] Fix KeepAlive Config so that value from appsettings.json is used (#12224) * Fix KeepAlive Config so that value from appsettings.json is used if present * update comment to reflect get-set on KeepAlivePingUrl Co-authored-by: Nathan Woulfe --- src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs b/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs index 305df2be8c..297e1dff87 100644 --- a/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs @@ -12,6 +12,7 @@ namespace Umbraco.Cms.Core.Configuration.Models public class KeepAliveSettings { internal const bool StaticDisableKeepAliveTask = false; + internal const string StaticKeepAlivePingUrl = "~/api/keepalive/ping"; /// /// Gets or sets a value indicating whether the keep alive task is disabled. @@ -20,8 +21,9 @@ namespace Umbraco.Cms.Core.Configuration.Models public bool DisableKeepAliveTask { get; set; } = StaticDisableKeepAliveTask; /// - /// Gets a value for the keep alive ping URL. + /// Gets or sets a value for the keep alive ping URL. /// - public string KeepAlivePingUrl => "~/api/keepalive/ping"; + [DefaultValue(StaticKeepAlivePingUrl)] + public string KeepAlivePingUrl { get; set; } = StaticKeepAlivePingUrl; } } From 80c90f23d1499d7f7a4a2269d54610859df05921 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Wed, 6 Apr 2022 08:42:10 +0100 Subject: [PATCH 47/67] Fix issue - changing a document type broke the nucache data structure (#12209) (cherry picked from commit 15df448274edb291ae568148c61baa7541615247) --- src/Umbraco.PublishedCache.NuCache/ContentStore.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index 98fc4a3ffe..ddb8ea9057 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -443,10 +443,18 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache refreshedIdsA.Contains(x.ContentTypeId) && BuildKit(x, out _))) { - // replacing the node: must preserve the parents + // replacing the node: must preserve the relations var node = GetHead(_contentNodes, kit.Node.Id)?.Value; if (node != null) + { + // Preserve children kit.Node.FirstChildContentId = node.FirstChildContentId; + kit.Node.LastChildContentId = node.LastChildContentId; + + // Also preserve siblings + kit.Node.NextSiblingContentId = node.NextSiblingContentId; + kit.Node.PreviousSiblingContentId = node.PreviousSiblingContentId; + } SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); From 597d28b3991344dbb7b34c602531d0c964670ee0 Mon Sep 17 00:00:00 2001 From: patrickdemooij9 Date: Tue, 5 Apr 2022 11:55:11 +0200 Subject: [PATCH 48/67] Remove statuscodepages middleware --- .../ApplicationBuilder/UmbracoApplicationBuilder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index 58f66a6fb8..a62f00c54f 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -92,8 +92,6 @@ namespace Umbraco.Cms.Web.Common.ApplicationBuilder { UseUmbracoCoreMiddleware(); - AppBuilder.UseStatusCodePages(); - // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. AppBuilder.UseImageSharp(); From 5bac054311dc9a200e9f1a39a6b5cdb30161ec4f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 7 Apr 2022 16:36:30 +0200 Subject: [PATCH 49/67] Add discord badge --- .github/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/README.md b/.github/README.md index 95ba778db2..5fa412cae4 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,4 +1,4 @@ -# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/contrib)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco) +# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/contrib)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco) [![Discord](https://img.shields.io/discord/869656431308189746)](https://discord.gg/umbraco) Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social. From 20666218d268e0cb8f0a2d9ddb3e0bf0d0ff031e Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Mar 2022 13:48:43 +0100 Subject: [PATCH 50/67] Move templates to root --- build/azure-pipelines.yml | 4 +- build/build.ps1 | 63 +++++------------- .../UmbracoProject/.template.config/icon.png | Bin 22265 -> 0 bytes .../UmbracoProject/Views/_ViewImports.cshtml | 8 --- .../Umbraco.Templates.nuspec | 11 ++- .../.template.config/dotnetcli.host.json | 0 .../.template.config/ide.host.json | 2 +- .../.template.config/template.json | 0 .../UmbracoPackage/package.manifest | 0 .../UmbracoPackage/UmbracoPackage.csproj | 0 .../build/UmbracoPackage.targets | 0 .../UmbracoProject/.gitignore | 0 .../.template.config/dotnetcli.host.json | 0 .../.template.config/ide.host.json | 2 +- .../.template.config/template.json | 0 .../Properties/launchSettings.json | 0 .../UmbracoProject/UmbracoProject.csproj | 0 .../appsettings.Development.json | 0 .../UmbracoProject/appsettings.json | 0 .../.template.config => templates}/icon.png | Bin 20 files changed, 28 insertions(+), 62 deletions(-) delete mode 100644 build/templates/UmbracoProject/.template.config/icon.png delete mode 100644 build/templates/UmbracoProject/Views/_ViewImports.cshtml rename {build/templates => templates}/Umbraco.Templates.nuspec (64%) rename {build/templates => templates}/UmbracoPackage/.template.config/dotnetcli.host.json (100%) rename {build/templates => templates}/UmbracoPackage/.template.config/ide.host.json (89%) rename {build/templates => templates}/UmbracoPackage/.template.config/template.json (100%) rename {build/templates => templates}/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest (100%) rename {build/templates => templates}/UmbracoPackage/UmbracoPackage.csproj (100%) rename {build/templates => templates}/UmbracoPackage/build/UmbracoPackage.targets (100%) rename {build/templates => templates}/UmbracoProject/.gitignore (100%) rename {build/templates => templates}/UmbracoProject/.template.config/dotnetcli.host.json (100%) rename {build/templates => templates}/UmbracoProject/.template.config/ide.host.json (98%) rename {build/templates => templates}/UmbracoProject/.template.config/template.json (100%) rename {build/templates => templates}/UmbracoProject/Properties/launchSettings.json (100%) rename {build/templates => templates}/UmbracoProject/UmbracoProject.csproj (100%) rename {build/templates => templates}/UmbracoProject/appsettings.Development.json (100%) rename {build/templates => templates}/UmbracoProject/appsettings.json (100%) rename {build/templates/UmbracoPackage/.template.config => templates}/icon.png (100%) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 38d5e7673b..064b7aec0b 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -414,7 +414,7 @@ stages: #Update the version in templates also $templatePath = - 'build/templates/UmbracoProject/.template.config/template.json' + 'templates/UmbracoProject/.template.config/template.json' $a = Get-Content $templatePath -raw | ConvertFrom-Json @@ -424,7 +424,7 @@ stages: $templatePath = - 'build/templates/UmbracoPackage/.template.config/template.json' + 'templates/UmbracoPackage/.template.config/template.json' $a = Get-Content $templatePath -raw | ConvertFrom-Json diff --git a/build/build.ps1 b/build/build.ps1 index da4733d432..24fd548c61 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -11,7 +11,7 @@ [Alias("loc")] [switch] $local = $false, - # enable docfx + # enable docfx [Parameter(Mandatory=$false)] [Alias("doc")] [switch] $docfx = $false, @@ -40,7 +40,7 @@ @{ Continue = $continue }) if ($ubuild.OnError()) { return } - Write-Host "Umbraco Cms Build" + Write-Host "Umbraco CMS Build" Write-Host "Umbraco.Build v$($ubuild.BuildVersion)" # ################################################################ @@ -84,7 +84,7 @@ $this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache) $this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix) - $ignore = $this.ClearEnvVar("NODE_NO_HTTP2") + $this.ClearEnvVar("NODE_NO_HTTP2") }) $ubuild.DefineMethod("CompileBelle", @@ -171,11 +171,6 @@ $src = "$($this.SolutionRoot)\src" $log = "$($this.BuildTemp)\build.umbraco.log" - if ($this.BuildEnv.VisualStudio -eq $null) - { - throw "Build environment does not provide VisualStudio." - } - Write-Host "Compile Umbraco" Write-Host "Logging to $log" @@ -255,27 +250,21 @@ $buildConfiguration = "Release" $log = "$($this.BuildTemp)\msbuild.tests.log" - if ($this.BuildEnv.VisualStudio -eq $null) - { - throw "Build environment does not provide VisualStudio." - } - Write-Host "Compile Tests" Write-Host "Logging to $log" # beware of the weird double \\ at the end of paths # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ - &$this.BuildEnv.VisualStudio.MsBuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" ` - /p:WarningLevel=0 ` - /p:Configuration=$buildConfiguration ` - /p:Platform=AnyCPU ` - /p:UseWPP_CopyWebApplication=True ` - /p:PipelineDependsOnBuild=False ` - /p:OutDir="$($this.BuildTemp)\tests\\" ` - /p:Verbosity=minimal ` - /t:Build ` - /tv:"$($this.BuildEnv.VisualStudio.ToolsVersion)" ` - /p:UmbracoBuild=True ` + &dotnet msbuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" ` + -target:Build ` + -property:WarningLevel=0 ` + -property:Configuration=$buildConfiguration ` + -property:Platform=AnyCPU ` + -property:UseWPP_CopyWebApplication=True ` + -property:PipelineDependsOnBuild=False ` + -property:OutDir="$($this.BuildTemp)\tests\\" ` + -property:Verbosity=minimal ` + -property:UmbracoBuild=True ` > $log # copy Umbraco.Persistence.SqlCe files into WebApp @@ -292,10 +281,6 @@ $src = "$($this.SolutionRoot)\src" $tmp = "$($this.BuildTemp)" - $out = "$($this.BuildOutput)" - $templates = "$($this.SolutionRoot)\build\templates" - - $buildConfiguration = "Release" # cleanup build Write-Host "Clean build" @@ -309,7 +294,6 @@ # create directories Write-Host "Create directories" mkdir "$tmp\WebApp\App_Data" > $null - mkdir "$tmp\Templates" > $null #mkdir "$tmp\WebApp\Media" > $null #mkdir "$tmp\WebApp\Views" > $null @@ -332,10 +316,6 @@ { $nugetPackages = [System.Environment]::ExpandEnvironmentVariables("%userprofile%\.nuget\packages") } - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\bin\x86") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\bin\amd64") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\WebApp\bin\x86") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\WebApp\bin\amd64") # copy Belle Write-Host "Copy Belle" @@ -343,19 +323,6 @@ $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\js", "*", "$tmp\WebApp\wwwroot\umbraco\js") $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\lib", "*", "$tmp\WebApp\wwwroot\umbraco\lib") $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\views", "*", "$tmp\WebApp\wwwroot\umbraco\views") - - - - # Prepare templates - Write-Host "Copy template files" - $this.CopyFiles("$templates", "*", "$tmp\Templates") - - Write-Host "Copy files for dotnet templates" - $this.CopyFiles("$src\Umbraco.Web.UI", "Program.cs", "$tmp\Templates\UmbracoProject") - $this.CopyFiles("$src\Umbraco.Web.UI", "Startup.cs", "$tmp\Templates\UmbracoProject") - $this.CopyFiles("$src\Umbraco.Web.UI\Views", "*", "$tmp\Templates\UmbracoProject\Views") - - $this.RemoveDirectory("$tmp\Templates\UmbracoProject\bin") }) @@ -389,7 +356,7 @@ { Write-Host "Restore NuGet" Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log" - $params = "-Source", $nugetsourceUmbraco + $params = "-Source", $nugetsourceUmbraco &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\umbraco-netcore-only.sln" > "$($this.BuildTemp)\nuget.restore.log" @params if (-not $?) { throw "Failed to restore NuGet packages." } }) @@ -397,7 +364,7 @@ $ubuild.DefineMethod("PackageNuGet", { $nuspecs = "$($this.SolutionRoot)\build\NuSpecs" - $templates = "$($this.BuildTemp)\Templates" + $templates = "$($this.SolutionRoot)\templates" Write-Host "Create NuGet packages" diff --git a/build/templates/UmbracoProject/.template.config/icon.png b/build/templates/UmbracoProject/.template.config/icon.png deleted file mode 100644 index 6e94105808e0f05cb21726b4f729c979ed7d33ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22265 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 diff --git a/build/templates/UmbracoProject/Views/_ViewImports.cshtml b/build/templates/UmbracoProject/Views/_ViewImports.cshtml deleted file mode 100644 index cb9a0b658e..0000000000 --- a/build/templates/UmbracoProject/Views/_ViewImports.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@using Umbraco.Web.UI -@using Umbraco.Extensions -@using Umbraco.Web.PublishedModels -@using Umbraco.Cms.Core.Models.PublishedContent -@using Microsoft.AspNetCore.Html -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, Smidge -@inject Smidge.SmidgeHelper SmidgeHelper diff --git a/build/templates/Umbraco.Templates.nuspec b/templates/Umbraco.Templates.nuspec similarity index 64% rename from build/templates/Umbraco.Templates.nuspec rename to templates/Umbraco.Templates.nuspec index 21201d8d55..6561a41060 100644 --- a/build/templates/Umbraco.Templates.nuspec +++ b/templates/Umbraco.Templates.nuspec @@ -14,8 +14,15 @@ umbraco - + + + + + + + + + - diff --git a/build/templates/UmbracoPackage/.template.config/dotnetcli.host.json b/templates/UmbracoPackage/.template.config/dotnetcli.host.json similarity index 100% rename from build/templates/UmbracoPackage/.template.config/dotnetcli.host.json rename to templates/UmbracoPackage/.template.config/dotnetcli.host.json diff --git a/build/templates/UmbracoPackage/.template.config/ide.host.json b/templates/UmbracoPackage/.template.config/ide.host.json similarity index 89% rename from build/templates/UmbracoPackage/.template.config/ide.host.json rename to templates/UmbracoPackage/.template.config/ide.host.json index 8d3bae3e3c..aa4eb34552 100644 --- a/build/templates/UmbracoPackage/.template.config/ide.host.json +++ b/templates/UmbracoPackage/.template.config/ide.host.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/vs-2017.3.host", "order" : 0, - "icon": "icon.png", + "icon": "../../icon.png", "description": { "id": "UmbracoPackage", "text": "Umbraco Package - An empty Umbraco CMS package (Plugin)" diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json similarity index 100% rename from build/templates/UmbracoPackage/.template.config/template.json rename to templates/UmbracoPackage/.template.config/template.json diff --git a/build/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest b/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest similarity index 100% rename from build/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest rename to templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest diff --git a/build/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj similarity index 100% rename from build/templates/UmbracoPackage/UmbracoPackage.csproj rename to templates/UmbracoPackage/UmbracoPackage.csproj diff --git a/build/templates/UmbracoPackage/build/UmbracoPackage.targets b/templates/UmbracoPackage/build/UmbracoPackage.targets similarity index 100% rename from build/templates/UmbracoPackage/build/UmbracoPackage.targets rename to templates/UmbracoPackage/build/UmbracoPackage.targets diff --git a/build/templates/UmbracoProject/.gitignore b/templates/UmbracoProject/.gitignore similarity index 100% rename from build/templates/UmbracoProject/.gitignore rename to templates/UmbracoProject/.gitignore diff --git a/build/templates/UmbracoProject/.template.config/dotnetcli.host.json b/templates/UmbracoProject/.template.config/dotnetcli.host.json similarity index 100% rename from build/templates/UmbracoProject/.template.config/dotnetcli.host.json rename to templates/UmbracoProject/.template.config/dotnetcli.host.json diff --git a/build/templates/UmbracoProject/.template.config/ide.host.json b/templates/UmbracoProject/.template.config/ide.host.json similarity index 98% rename from build/templates/UmbracoProject/.template.config/ide.host.json rename to templates/UmbracoProject/.template.config/ide.host.json index 1ee7a492aa..d44cb154c1 100644 --- a/build/templates/UmbracoProject/.template.config/ide.host.json +++ b/templates/UmbracoProject/.template.config/ide.host.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/vs-2017.3.host", "order" : 0, - "icon": "icon.png", + "icon": "../../icon.png", "description": { "id": "UmbracoProject", "text": "Umbraco Web Application - An empty Umbraco CMS web application" diff --git a/build/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json similarity index 100% rename from build/templates/UmbracoProject/.template.config/template.json rename to templates/UmbracoProject/.template.config/template.json diff --git a/build/templates/UmbracoProject/Properties/launchSettings.json b/templates/UmbracoProject/Properties/launchSettings.json similarity index 100% rename from build/templates/UmbracoProject/Properties/launchSettings.json rename to templates/UmbracoProject/Properties/launchSettings.json diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj similarity index 100% rename from build/templates/UmbracoProject/UmbracoProject.csproj rename to templates/UmbracoProject/UmbracoProject.csproj diff --git a/build/templates/UmbracoProject/appsettings.Development.json b/templates/UmbracoProject/appsettings.Development.json similarity index 100% rename from build/templates/UmbracoProject/appsettings.Development.json rename to templates/UmbracoProject/appsettings.Development.json diff --git a/build/templates/UmbracoProject/appsettings.json b/templates/UmbracoProject/appsettings.json similarity index 100% rename from build/templates/UmbracoProject/appsettings.json rename to templates/UmbracoProject/appsettings.json diff --git a/build/templates/UmbracoPackage/.template.config/icon.png b/templates/icon.png similarity index 100% rename from build/templates/UmbracoPackage/.template.config/icon.png rename to templates/icon.png From 75613cf06174ecdd39f53d332598c1346a3df631 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 12 Apr 2022 13:45:15 +0200 Subject: [PATCH 51/67] Revert "Block List Settings throws exception if Models builder mode is set to "Nothing" (#11725)" This reverts commit 62b289e1790c721bb9bc3c717c21a4d13de7a173. --- .../BlockListPropertyValueConverter.cs | 15 +-------------- .../BlockListPropertyValueConverterTests.cs | 5 +---- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs index 1c8c7c7d4f..6916f2ea3f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs @@ -4,14 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters @@ -22,20 +17,12 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters private readonly IProfilingLogger _proflog; private readonly BlockEditorConverter _blockConverter; private readonly BlockListEditorDataConverter _blockListEditorDataConverter; - private readonly ModelsBuilderSettings _modelsBuilderSettings; - [Obsolete("Use ctor injecting ModelsBuilderSettings")] public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter) - : this(proflog, blockConverter,StaticServiceProvider.Instance.GetRequiredService>()) - { - } - - public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IOptions modelsBuilderOptions) { _proflog = proflog; _blockConverter = blockConverter; _blockListEditorDataConverter = new BlockListEditorDataConverter(); - _modelsBuilderSettings = modelsBuilderOptions?.Value; } /// @@ -129,7 +116,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters } // Get settings type from configuration - var settingsType = blockConfig.SettingsElementTypeKey.HasValue && _modelsBuilderSettings.ModelsMode != ModelsMode.Nothing + var settingsType = blockConfig.SettingsElementTypeKey.HasValue ? _blockConverter.GetModelType(blockConfig.SettingsElementTypeKey.Value) : typeof(IPublishedElement); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs index 09fc427d12..084bf03370 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs @@ -2,11 +2,9 @@ // See LICENSE for more details. using System; -using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.PublishedContent; @@ -64,10 +62,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors { IPublishedSnapshotAccessor publishedSnapshotAccessor = GetPublishedSnapshotAccessor(); var publishedModelFactory = new NoopPublishedModelFactory(); - var modelsBuilderSettings = Mock.Of>(x => x.Value == new ModelsBuilderSettings()); var editor = new BlockListPropertyValueConverter( Mock.Of(), - new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory), modelsBuilderSettings); + new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory)); return editor; } From 8969bd23bb0c21d5f189e74ca43bdbddc8953493 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 12 Apr 2022 16:27:55 +0200 Subject: [PATCH 52/67] Fix UseHttpsRedirect spacing --- src/Umbraco.Web.UI/Startup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/Startup.cs b/src/Umbraco.Web.UI/Startup.cs index e0a48216e0..0176974b6b 100644 --- a/src/Umbraco.Web.UI/Startup.cs +++ b/src/Umbraco.Web.UI/Startup.cs @@ -56,11 +56,11 @@ namespace Umbraco.Cms.Web.UI { app.UseDeveloperExceptionPage(); } - #if (UseHttpsRedirect) - app.UseHttpsRedirection(); + app.UseHttpsRedirection(); #endif + app.UseUmbraco() .WithMiddleware(u => { From faa561da2bb0fc8facb552ae2c9ec81188865250 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 14 Apr 2022 10:06:01 +0200 Subject: [PATCH 53/67] Fixes error on first running the Web.UI because of an invalid config --- src/Umbraco.Web.UI/appsettings.template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/appsettings.template.json b/src/Umbraco.Web.UI/appsettings.template.json index cef926fad2..e9a69481d6 100644 --- a/src/Umbraco.Web.UI/appsettings.template.json +++ b/src/Umbraco.Web.UI/appsettings.template.json @@ -33,7 +33,7 @@ }, "KeepAlive": { "DisableKeepAliveTask": false, - "KeepAlivePingUrl": "{umbracoApplicationUrl}/api/keepalive/ping" + "KeepAlivePingUrl": "~/{umbracoApplicationUrl}/api/keepalive/ping" }, "RequestHandler": { "ConvertUrlsToAscii": "try" From 70a48596baef47d0b558db57b0910204685a701c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 14 Apr 2022 10:39:30 +0200 Subject: [PATCH 54/67] The `{umbracoApplicationUrl}` magic string does nothing here --- src/Umbraco.Web.UI/appsettings.template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/appsettings.template.json b/src/Umbraco.Web.UI/appsettings.template.json index e9a69481d6..2a291c5bc6 100644 --- a/src/Umbraco.Web.UI/appsettings.template.json +++ b/src/Umbraco.Web.UI/appsettings.template.json @@ -33,7 +33,7 @@ }, "KeepAlive": { "DisableKeepAliveTask": false, - "KeepAlivePingUrl": "~/{umbracoApplicationUrl}/api/keepalive/ping" + "KeepAlivePingUrl": "~/api/keepalive/ping" }, "RequestHandler": { "ConvertUrlsToAscii": "try" From 6519b88dfc3959e73f4db78372c5a6f9559d7d7b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 14 Apr 2022 14:15:25 +0200 Subject: [PATCH 55/67] Fix wrong picker, related to changes in #12122 --- .../Migrations/Install/DatabaseDataCreator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index e13764140a..f9fbf40345 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -859,7 +859,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install if (_database.Exists(1048)) { - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MemberPicker, DbType = "Ntext" }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext" }); } if (_database.Exists(1049)) From 184397a3272a080e5618c1798819fce470424ced Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 15 Apr 2022 19:14:23 +0200 Subject: [PATCH 56/67] Adds PR first response action --- .github/workflows/pr-first-response.yml | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/pr-first-response.yml diff --git a/.github/workflows/pr-first-response.yml b/.github/workflows/pr-first-response.yml new file mode 100644 index 0000000000..f54c8b91ba --- /dev/null +++ b/.github/workflows/pr-first-response.yml @@ -0,0 +1,26 @@ +name: pr-first-response + +on: + pull_request: + types: [opened] + +jobs: + send-response: + runs-on: ubuntu-latest + steps: + - name: Fetch random comment 🗣️ + uses: JamesIves/fetch-api-data-action@v2.1.0 + with: + ENDPOINT: https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment + CONFIGURATION: '{ "method": "POST", "headers": {"Authorization": "Bearer ${{ secrets.OUR_BOT_API_TOKEN }}", "Content-Type": "application/json" }, "body": { "repo": "${{ github.repository }}", "number": "${{ github.event.number }}", "actor": "${{ github.actor }}", "commentType": "opened-pr-first-comment"} }' + - name: Add PR comment + if: "${{ env.fetch-api-data != '' }}" + uses: actions/github-script@v5 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `${{ env.fetch-api-data }}` + }) \ No newline at end of file From f7615a93d56f9a835dd9eea50dddd7cf8a294537 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 18 Apr 2022 02:17:05 +0200 Subject: [PATCH 57/67] Ignore the views folder apart from required files (#12260) * Ignore the views folder apart from required files * Only ship the views that we know we want --- .gitignore | 4 ++++ templates/Umbraco.Templates.nuspec | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c69474ac30..ea3bd3cd0f 100644 --- a/.gitignore +++ b/.gitignore @@ -85,6 +85,10 @@ src/Umbraco.Web.UI/wwwroot/[Uu]mbraco/lib/* src/Umbraco.Web.UI/wwwroot/[Uu]mbraco/views/* src/Umbraco.Web.UI/wwwroot/Media/* src/Umbraco.Web.UI/Smidge/ +src/Umbraco.Web.UI/Views/ +!src/Umbraco.Web.UI/Views/Partials/blocklist/ +!src/Umbraco.Web.UI/Views/Partials/grid/ +!src/Umbraco.Web.UI/Views/_ViewImports.cshtml # Tests cypress.env.json diff --git a/templates/Umbraco.Templates.nuspec b/templates/Umbraco.Templates.nuspec index 558994fbcf..823a925de0 100644 --- a/templates/Umbraco.Templates.nuspec +++ b/templates/Umbraco.Templates.nuspec @@ -24,6 +24,8 @@ - + + + From 68353f9d0655830f94626813eb7e94652bbd1454 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 19 Apr 2022 08:13:24 +0200 Subject: [PATCH 58/67] Fixes RedirectToUmbracoPageResult to handle redirects to pages with domains defined on them (#12259) * Fixes RedirectToUmbracoPageResult to handle redirects to pages with domains defined on them. * Renamed variable to match with updated service type. * Apply suggestions from code review Co-authored-by: Ronald Barendse * Fixed usage of IUrlHelper. Co-authored-by: Ronald Barendse --- .../ActionResults/RedirectToUmbracoPageResult.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs b/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs index 62d0dc7a10..9cbbe91f11 100644 --- a/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs +++ b/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs @@ -2,9 +2,9 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Web; @@ -122,8 +122,9 @@ namespace Umbraco.Cms.Web.Website.ActionResults } HttpContext httpContext = context.HttpContext; - IIOHelper ioHelper = httpContext.RequestServices.GetRequiredService(); - string destinationUrl = ioHelper.ResolveUrl(Url); + IUrlHelperFactory urlHelperFactory = httpContext.RequestServices.GetRequiredService(); + IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(context); + string destinationUrl = urlHelper.Content(Url); if (_queryString.HasValue) { @@ -134,6 +135,5 @@ namespace Umbraco.Cms.Web.Website.ActionResults return Task.CompletedTask; } - } } From 852305b7d1219f72848f34c0d8e58b3a3c8c9e38 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 19 Apr 2022 08:33:03 +0200 Subject: [PATCH 59/67] Simplified setup of 2FA for users (#12142) * Added functionality to enable 2FA for users.. * Do not use the obsolete ctor in tests * cleanup * Cleanup * Convert User view from overlay to infinite editor * Add support for having additional editors on top of the user (2fa) which overlay does not support * Add controllerAs syntax in the template * Remove unused dependencies * Adjustments to 2fa login view * organize elements * add translations * add a11y helpers * add autocompletion = one-time-code * change to controllerAs syntax * add callback to cancel 2fa and fix error where submit button was not reset when all other validations were * add a cancel/go back button to the 2fa view * replace header with something less obstrusive * move logout button to the footer in the new editor view * change 'edit profile' to an umb-box and move ng-if for password fields out to reduce amount of checks * Add umb-box to external login provider section * add umb-box to user history section * bug: fix bug where notificationsService would not allow new notifications if removeAll had been called * add styling and a11y to configureTwoFactor view - also ensure that the view reloads when changes happen in the custom user view to enable 2fa - ensure that view updates when disabling 2fa - add extra button to show options (disable) for each 2fa provider * add notification when 2fa is disabled * add data-element to support the intro tour also changed a minor selector in the cypress test * correct usage of umb-box with umb-box-content * do not use the .form class twice to prevent double box-shadow * make tranlastion for 2fa placeholder shorter * ensure that field with 2fa provider is always visible when more than 1 provider * move error state of 2fa field to token field * update translation of multiple 2fa providers * move CTA buttons to right side to follow general UI practices * rename options to disable * add disabled state * add helper folders to gitignore so you can work with plugins and custom code without committing it accidentally * move the disable functionality to its own infinite editor view * use properties from umb-control-group correctly * add 'track by' to repeater * make use of umb-control-group * remove unused functions * clean up translations * add Danish translations * copy translations to english * Only return enabled 2fa providers as expected Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> --- .gitignore | 2 + .../Models/UserTwoFactorProviderModel.cs | 20 + .../Services/ITwoFactorLoginService.cs | 8 + .../Security/BackOfficeUserStore.cs | 46 +- .../Security/UmbracoUserManager.cs | 4 +- .../Implement/TwoFactorLoginService.cs | 78 +++- .../Controllers/AuthenticationController.cs | 58 ++- .../Controllers/BackOfficeServerVariables.cs | 4 + .../Controllers/TwoFactorLoginController.cs | 122 +++++ .../UmbracoBuilder.BackOfficeAuth.cs | 5 + .../UmbracoBuilder.BackOfficeIdentity.cs | 5 +- .../DefaultBackOfficeTwoFactorOptions.cs | 10 + .../NoopBackOfficeTwoFactorOptions.cs | 5 +- .../Security/TwoFactorLoginViewOptions.cs | 13 + .../Security/BackOfficeUserManager.cs | 1 + .../Security/MemberManager.cs | 3 - .../application/umbappheader.directive.js | 18 +- .../application/umblogin.directive.js | 4 + .../resources/twofactorlogin.resource.js | 136 ++++++ .../common/services/notifications.service.js | 2 +- .../configuretwofactor.controller.js | 79 ++++ .../twofactor/configuretwofactor.html | 58 +++ .../twofactor/disabletwofactor.controller.js | 53 +++ .../twofactor/disabletwofactor.html | 56 +++ .../infiniteeditors/user/user.controller.js | 219 +++++++++ .../common/infiniteeditors/user/user.html | 145 ++++++ .../src/views/common/login-2fa.controller.js | 38 ++ .../src/views/common/login-2fa.html | 44 ++ .../common/overlays/user/user.controller.js | 194 -------- .../src/views/common/overlays/user/user.html | 143 ------ .../src/views/users/user.controller.js | 25 +- .../users/views/user/details.controller.js | 1 + .../src/views/users/views/user/details.html | 10 + src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 + src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 33 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 9 + .../umbraco/config/lang/en_us.xml | 421 +++++++++--------- .../integration/Tours/backofficeTour.ts | 2 +- .../Security/BackOfficeUserStoreTests.cs | 5 +- 39 files changed, 1497 insertions(+), 586 deletions(-) create mode 100644 src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs create mode 100644 src/Umbraco.Web.BackOffice/Controllers/TwoFactorLoginController.cs create mode 100644 src/Umbraco.Web.BackOffice/Security/DefaultBackOfficeTwoFactorOptions.cs create mode 100644 src/Umbraco.Web.BackOffice/Security/TwoFactorLoginViewOptions.cs create mode 100644 src/Umbraco.Web.UI.Client/src/common/resources/twofactorlogin.resource.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/configuretwofactor.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/configuretwofactor.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/disabletwofactor.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/disabletwofactor.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/user/user.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/user/user.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/login-2fa.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html delete mode 100644 src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js delete mode 100644 src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html diff --git a/.gitignore b/.gitignore index c69474ac30..fd7951db17 100644 --- a/.gitignore +++ b/.gitignore @@ -85,6 +85,8 @@ src/Umbraco.Web.UI/wwwroot/[Uu]mbraco/lib/* src/Umbraco.Web.UI/wwwroot/[Uu]mbraco/views/* src/Umbraco.Web.UI/wwwroot/Media/* src/Umbraco.Web.UI/Smidge/ +src/Umbraco.Web.UI/App_Code/ +src/Umbraco.Web.UI/App_Plugins/ # Tests cypress.env.json diff --git a/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs b/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs new file mode 100644 index 0000000000..095d4f50a9 --- /dev/null +++ b/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs @@ -0,0 +1,20 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Models +{ + [DataContract] + public class UserTwoFactorProviderModel + { + public UserTwoFactorProviderModel(string providerName, bool isEnabledOnUser) + { + ProviderName = providerName; + IsEnabledOnUser = isEnabledOnUser; + } + + [DataMember(Name = "providerName")] + public string ProviderName { get; } + + [DataMember(Name = "isEnabledOnUser")] + public bool IsEnabledOnUser { get; } + } +} diff --git a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs index 33a96ad751..1855d03fe1 100644 --- a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs @@ -58,4 +58,12 @@ namespace Umbraco.Cms.Core.Services /// Task> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey); } + + [Obsolete("This will be merged into ITwoFactorLoginService in Umbraco 11")] + public interface ITwoFactorLoginService2 : ITwoFactorLoginService + { + Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code); + + Task ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code); + } } diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs index 32c0500a79..330110721e 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs @@ -35,6 +35,7 @@ namespace Umbraco.Cms.Core.Security private readonly GlobalSettings _globalSettings; private readonly IUmbracoMapper _mapper; private readonly AppCaches _appCaches; + private readonly ITwoFactorLoginService _twoFactorLoginService; /// /// Initializes a new instance of the class. @@ -48,7 +49,8 @@ namespace Umbraco.Cms.Core.Security IOptions globalSettings, IUmbracoMapper mapper, BackOfficeErrorDescriber describer, - AppCaches appCaches) + AppCaches appCaches, + ITwoFactorLoginService twoFactorLoginService) : base(describer) { _scopeProvider = scopeProvider; @@ -58,11 +60,36 @@ namespace Umbraco.Cms.Core.Security _globalSettings = globalSettings.Value; _mapper = mapper; _appCaches = appCaches; + _twoFactorLoginService = twoFactorLoginService; _userService = userService; _externalLoginService = externalLoginService; } - [Obsolete("Use ctor injecting IExternalLoginWithKeyService ")] + [Obsolete("Use non obsolete ctor")] + public BackOfficeUserStore( + IScopeProvider scopeProvider, + IUserService userService, + IEntityService entityService, + IExternalLoginWithKeyService externalLoginService, + IOptions globalSettings, + IUmbracoMapper mapper, + BackOfficeErrorDescriber describer, + AppCaches appCaches) + : this( + scopeProvider, + userService, + entityService, + externalLoginService, + globalSettings, + mapper, + describer, + appCaches, + StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + [Obsolete("Use non obsolete ctor")] public BackOfficeUserStore( IScopeProvider scopeProvider, IUserService userService, @@ -80,11 +107,24 @@ namespace Umbraco.Cms.Core.Security globalSettings, mapper, describer, - appCaches) + appCaches, + StaticServiceProvider.Instance.GetRequiredService()) { } + /// + public override async Task GetTwoFactorEnabledAsync(BackOfficeIdentityUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (!int.TryParse(user.Id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intUserId)) + { + return await base.GetTwoFactorEnabledAsync(user, cancellationToken); + } + + return await _twoFactorLoginService.IsTwoFactorEnabledAsync(user.Key); + } + /// public override Task CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) { diff --git a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs index 1410473f6a..81b541ecc9 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs @@ -49,10 +49,10 @@ namespace Umbraco.Cms.Core.Security public override bool SupportsQueryableUsers => false; // It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository /// - /// Developers will need to override this to support custom 2 factor auth + /// Both users and members supports 2FA /// /// - public override bool SupportsUserTwoFactor => false; + public override bool SupportsUserTwoFactor => true; /// public override bool SupportsUserPhoneNumber => false; // We haven't needed to support this yet, though might be necessary for 2FA diff --git a/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs b/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs index cdcc6b19e9..11b325e350 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs @@ -3,22 +3,26 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Core.Services { /// - public class TwoFactorLoginService : ITwoFactorLoginService + public class TwoFactorLoginService : ITwoFactorLoginService2 { private readonly ITwoFactorLoginRepository _twoFactorLoginRepository; private readonly IScopeProvider _scopeProvider; private readonly IOptions _identityOptions; private readonly IOptions _backOfficeIdentityOptions; private readonly IDictionary _twoFactorSetupGenerators; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. @@ -28,16 +32,34 @@ namespace Umbraco.Cms.Core.Services IScopeProvider scopeProvider, IEnumerable twoFactorSetupGenerators, IOptions identityOptions, - IOptions backOfficeIdentityOptions - ) + IOptions backOfficeIdentityOptions, + ILogger logger) { _twoFactorLoginRepository = twoFactorLoginRepository; _scopeProvider = scopeProvider; _identityOptions = identityOptions; _backOfficeIdentityOptions = backOfficeIdentityOptions; + _logger = logger; _twoFactorSetupGenerators = twoFactorSetupGenerators.ToDictionary(x =>x.ProviderName); } + [Obsolete("Use ctor with all params - This will be removed in v11")] + public TwoFactorLoginService( + ITwoFactorLoginRepository twoFactorLoginRepository, + IScopeProvider scopeProvider, + IEnumerable twoFactorSetupGenerators, + IOptions identityOptions, + IOptions backOfficeIdentityOptions) + : this(twoFactorLoginRepository, + scopeProvider, + twoFactorSetupGenerators, + identityOptions, + backOfficeIdentityOptions, + StaticServiceProvider.Instance.GetRequiredService>()) + { + + } + /// public async Task DeleteUserLoginsAsync(Guid userOrMemberKey) { @@ -51,6 +73,56 @@ namespace Umbraco.Cms.Core.Services return await GetEnabledProviderNamesAsync(userOrMemberKey); } + public async Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code) + { + var secret = await GetSecretForUserAndProviderAsync(userOrMemberKey, providerName); + + if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider generator)) + { + throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}"); + } + + var isValid = generator.ValidateTwoFactorPIN(secret, code); + + if (!isValid) + { + return false; + } + + return await DisableAsync(userOrMemberKey, providerName); + } + + public async Task ValidateAndSaveAsync(string providerName, Guid userOrMemberKey, string secret, string code) + { + + try + { + var isValid = ValidateTwoFactorSetup(providerName, secret, code); + if (isValid == false) + { + return false; + } + + var twoFactorLogin = new TwoFactorLogin() + { + Confirmed = true, + Secret = secret, + UserOrMemberKey = userOrMemberKey, + ProviderName = providerName + }; + + await SaveAsync(twoFactorLogin); + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not log in with the provided one-time-password"); + } + + return false; + } + private async Task> GetEnabledProviderNamesAsync(Guid userOrMemberKey) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index cdc9d2e913..43e691a6f3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -74,11 +74,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions; private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ITwoFactorLoginService _twoFactorLoginService; private readonly WebRoutingSettings _webRoutingSettings; // TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here [ActivatorUtilitiesConstructor] - public AuthenticationController( + public AuthenticationController( IBackOfficeSecurityAccessor backofficeSecurityAccessor, IBackOfficeUserManager backOfficeUserManager, IBackOfficeSignInManager signInManager, @@ -97,7 +98,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IBackOfficeExternalLoginProviders externalAuthenticationOptions, IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, IHttpContextAccessor httpContextAccessor, - IOptions webRoutingSettings) + IOptions webRoutingSettings, + ITwoFactorLoginService twoFactorLoginService) { _backofficeSecurityAccessor = backofficeSecurityAccessor; _userManager = backOfficeUserManager; @@ -118,9 +120,56 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; _httpContextAccessor = httpContextAccessor; _webRoutingSettings = webRoutingSettings.Value; + _twoFactorLoginService = twoFactorLoginService; } - [Obsolete("Use constructor that also takes IHttpAccessor and IOptions, scheduled for removal in V11")] + [Obsolete("Use constructor that takes all params, scheduled for removal in V11")] + public AuthenticationController( + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeUserManager backOfficeUserManager, + IBackOfficeSignInManager signInManager, + IUserService userService, + ILocalizedTextService textService, + IUmbracoMapper umbracoMapper, + IOptions globalSettings, + IOptions securitySettings, + ILogger logger, + IIpResolver ipResolver, + IOptions passwordConfiguration, + IEmailSender emailSender, + ISmsSender smsSender, + IHostingEnvironment hostingEnvironment, + LinkGenerator linkGenerator, + IBackOfficeExternalLoginProviders externalAuthenticationOptions, + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, + IHttpContextAccessor httpContextAccessor, + IOptions webRoutingSettings) + : this( + backofficeSecurityAccessor, + backOfficeUserManager, + signInManager, + userService, + textService, + umbracoMapper, + globalSettings, + securitySettings, + logger, + ipResolver, + passwordConfiguration, + emailSender, + smsSender, + hostingEnvironment, + linkGenerator, + externalAuthenticationOptions, + backOfficeTwoFactorOptions, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService>(), + StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + [Obsolete("Use constructor that takes all params, scheduled for removal in V11")] public AuthenticationController( IBackOfficeSecurityAccessor backofficeSecurityAccessor, IBackOfficeUserManager backOfficeUserManager, @@ -469,7 +518,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return NotFound(); } - var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user); + var userFactors = await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(user.Key); + return new ObjectResult(userFactors); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index b74c4ea7b0..cbb17dce99 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -235,6 +235,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers "authenticationApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.PostLogin(null)) }, + { + "twoFactorLoginApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( + controller => controller.SetupInfo(null)) + }, { "currentUserApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.PostChangePassword(null)) diff --git a/src/Umbraco.Web.BackOffice/Controllers/TwoFactorLoginController.cs b/src/Umbraco.Web.BackOffice/Controllers/TwoFactorLoginController.cs new file mode 100644 index 0000000000..41c25e3b47 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/TwoFactorLoginController.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.BackOffice.Security; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Web.BackOffice.Controllers +{ + public class TwoFactorLoginController : UmbracoAuthorizedJsonController + { + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly ILogger _logger; + private readonly ITwoFactorLoginService2 _twoFactorLoginService; + private readonly IBackOfficeSignInManager _backOfficeSignInManager; + private readonly IBackOfficeUserManager _backOfficeUserManager; + private readonly IOptionsSnapshot _twoFactorLoginViewOptions; + + public TwoFactorLoginController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + ILogger logger, + ITwoFactorLoginService twoFactorLoginService, + IBackOfficeSignInManager backOfficeSignInManager, + IBackOfficeUserManager backOfficeUserManager, + IOptionsSnapshot twoFactorLoginViewOptions) + { + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _logger = logger; + + if (twoFactorLoginService is not ITwoFactorLoginService2 twoFactorLoginService2) + { + throw new ArgumentException("twoFactorLoginService needs to implement ITwoFactorLoginService2 until the interfaces are merged", nameof(twoFactorLoginService)); + } + _twoFactorLoginService = twoFactorLoginService2; + _backOfficeSignInManager = backOfficeSignInManager; + _backOfficeUserManager = backOfficeUserManager; + _twoFactorLoginViewOptions = twoFactorLoginViewOptions; + } + + /// + /// Used to retrieve the 2FA providers for code submission + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task>> GetEnabled2FAProvidersForCurrentUser() + { + var user = await _backOfficeSignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + _logger.LogWarning("No verified user found, returning 404"); + return NotFound(); + } + + var userFactors = await _backOfficeUserManager.GetValidTwoFactorProvidersAsync(user); + return new ObjectResult(userFactors); + } + + + [HttpGet] + public async Task>> Get2FAProvidersForUser(int userId) + { + var user = await _backOfficeUserManager.FindByIdAsync(userId.ToString()); + + var enabledProviderNameHashSet = new HashSet(await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(user.Key)); + + var providerNames = await _backOfficeUserManager.GetValidTwoFactorProvidersAsync(user); + + return providerNames.Select(providerName => + new UserTwoFactorProviderModel(providerName, enabledProviderNameHashSet.Contains(providerName))).ToArray(); + } + + [HttpGet] + public async Task> SetupInfo(string providerName) + { + var user = _backOfficeSecurityAccessor?.BackOfficeSecurity.CurrentUser; + + var setupInfo = await _twoFactorLoginService.GetSetupInfoAsync(user.Key, providerName); + + return setupInfo; + } + + + [HttpPost] + public async Task> ValidateAndSave(string providerName, string secret, string code) + { + var user = _backOfficeSecurityAccessor?.BackOfficeSecurity.CurrentUser; + + return await _twoFactorLoginService.ValidateAndSaveAsync(providerName, user.Key, secret, code); + } + + [HttpPost] + [Authorize(Policy = AuthorizationPolicies.SectionAccessUsers)] + public async Task> Disable(string providerName, Guid userKey) + { + return await _twoFactorLoginService.DisableAsync(userKey, providerName); + } + + [HttpPost] + public async Task> DisableWithCode(string providerName, string code) + { + Guid key = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Key; + + return await _twoFactorLoginService.DisableWithCodeAsync(providerName, key, code); + } + + [HttpGet] + public ActionResult ViewPathForProviderName(string providerName) + { + var options = _twoFactorLoginViewOptions.Get(providerName); + return options.SetupViewPath; + } + } +} diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs index 24ecee08ef..0e878aef8b 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs @@ -45,6 +45,11 @@ namespace Umbraco.Extensions { o.Cookie.Name = Constants.Security.BackOfficeTwoFactorAuthenticationType; o.ExpireTimeSpan = TimeSpan.FromMinutes(5); + }) + .AddCookie(Constants.Security.BackOfficeTwoFactorRememberMeAuthenticationType, o => + { + o.Cookie.Name = Constants.Security.BackOfficeTwoFactorRememberMeAuthenticationType; + o.ExpireTimeSpan = TimeSpan.FromMinutes(5); }); builder.Services.ConfigureOptions(); diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs index 1dc5bda7a9..68664d35e7 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs @@ -42,7 +42,8 @@ namespace Umbraco.Extensions factory.GetRequiredService>(), factory.GetRequiredService(), factory.GetRequiredService(), - factory.GetRequiredService() + factory.GetRequiredService(), + factory.GetRequiredService() )) .AddUserManager() .AddSignInManager() @@ -64,7 +65,7 @@ namespace Umbraco.Extensions services.TryAddScoped(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); return new BackOfficeIdentityBuilder(services); } diff --git a/src/Umbraco.Web.BackOffice/Security/DefaultBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web.BackOffice/Security/DefaultBackOfficeTwoFactorOptions.cs new file mode 100644 index 0000000000..5ef0f53695 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/DefaultBackOfficeTwoFactorOptions.cs @@ -0,0 +1,10 @@ +using System; + +namespace Umbraco.Cms.Web.BackOffice.Security +{ + public class DefaultBackOfficeTwoFactorOptions : IBackOfficeTwoFactorOptions + { + public string GetTwoFactorView(string username) => "views\\common\\login-2fa.html"; + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs index 05cc7970b4..df9bf38e79 100644 --- a/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs @@ -1,5 +1,8 @@ -namespace Umbraco.Cms.Web.BackOffice.Security +using System; + +namespace Umbraco.Cms.Web.BackOffice.Security { + [Obsolete("Not used anymore")] public class NoopBackOfficeTwoFactorOptions : IBackOfficeTwoFactorOptions { public string GetTwoFactorView(string username) => null; diff --git a/src/Umbraco.Web.BackOffice/Security/TwoFactorLoginViewOptions.cs b/src/Umbraco.Web.BackOffice/Security/TwoFactorLoginViewOptions.cs new file mode 100644 index 0000000000..da06dd9e67 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/TwoFactorLoginViewOptions.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Cms.Web.BackOffice.Security +{ + /// + /// Options used as named options for 2fa providers + /// + public class TwoFactorLoginViewOptions + { + /// + /// Gets or sets the path of the view to show when setting up this 2fa provider + /// + public string SetupViewPath { get; set; } + } +} diff --git a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs index 505abdbe0e..9d68da047c 100644 --- a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs @@ -74,6 +74,7 @@ namespace Umbraco.Cms.Web.Common.Security return await base.VerifyPasswordAsync(store, user, password); } + /// /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date /// diff --git a/src/Umbraco.Web.Common/Security/MemberManager.cs b/src/Umbraco.Web.Common/Security/MemberManager.cs index 8124b9c50e..cb7a2b0835 100644 --- a/src/Umbraco.Web.Common/Security/MemberManager.cs +++ b/src/Umbraco.Web.Common/Security/MemberManager.cs @@ -45,9 +45,6 @@ namespace Umbraco.Cms.Web.Common.Security _httpContextAccessor = httpContextAccessor; } - /// - public override bool SupportsUserTwoFactor => true; - /// public async Task IsMemberAuthorizedAsync(IEnumerable allowTypes = null, IEnumerable allowGroups = null, IEnumerable allowMembers = null) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js index 01e199c572..dd83f6546b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function AppHeaderDirective(eventsService, appState, userService, focusService, overlayService, $timeout) { + function AppHeaderDirective(eventsService, appState, userService, focusService, $timeout, editorService) { function link(scope, element) { @@ -72,17 +72,15 @@ }; scope.avatarClick = function () { - - const dialog = { - view: "user", - position: "right", - name: "overlay-user", - close: function () { - overlayService.close(); - } + const userEditor = { + size: "small", + view: "views/common/infiniteeditors/user/user.html", + close: function() { + editorService.close(); + } }; - overlayService.open(dialog); + editorService.open(userEditor); }; scope.logoModal = { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index 9986b9ce8a..d1c8d1ac85 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -443,12 +443,16 @@ vm.twoFactor.submitCallback = function submitCallback() { vm.onLogin(); } + vm.twoFactor.cancelCallback = function cancelCallback() { + vm.showLogin(); + } vm.twoFactor.view = viewPath; vm.view = "2fa-login"; SetTitle(); } function resetInputValidation() { + vm.loginStates.submitButton = "init"; vm.confirmPassword = ""; vm.password = ""; vm.login = ""; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/twofactorlogin.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/twofactorlogin.resource.js new file mode 100644 index 0000000000..8ed308812e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/twofactorlogin.resource.js @@ -0,0 +1,136 @@ +/** + * @ngdoc service + * @name umbraco.resources.twoFactorLoginResource + * @function + * + * @description + * Used by the users section to get users 2FA information + */ +(function () { + 'use strict'; + + function twoFactorLoginResource($http, umbRequestHelper) { + + /** + * @ngdoc method + * @name umbraco.resources.twoFactorLoginResource#viewPathForProviderName + * @methodOf umbraco.resources.twoFactorLoginResource + * + * @description + * Gets the view path for the specified two factor provider + * + * ##usage + *
+         * twoFactorLoginResource.viewPathForProviderName(providerName)
+         *    .then(function(viewPath) {
+         *        alert("It's here");
+         *    });
+         * 
+ * + * @returns {Promise} resourcePromise object containing the view path. + * + */ + function viewPathForProviderName(providerName) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "twoFactorLoginApiBaseUrl", + "ViewPathForProviderName", + {providerName : providerName })), + "Failed to retrieve data"); + } + + /** + * @ngdoc method + * @name umbraco.resources.twoFactorLoginResource#get2FAProvidersForUser + * @methodOf umbraco.resources.twoFactorLoginResource + * + * @description + * Gets the 2fa provider names that is available + * + * ##usage + *
+       * twoFactorLoginResource.get2FAProvidersForUser(userKey)
+       *    .then(function(providers) {
+       *        alert("It's here");
+       *    });
+       * 
+ * + * @returns {Promise} resourcePromise object containing the an array of { providerName, isEnabledOnUser} . + * + */ + function get2FAProvidersForUser(userId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "twoFactorLoginApiBaseUrl", + "get2FAProvidersForUser", + { userId: userId })), + "Failed to retrieve data"); + } + + function setupInfo(providerName) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "twoFactorLoginApiBaseUrl", + "setupInfo", + { providerName: providerName })), + "Failed to retrieve data"); + } + + function validateAndSave(providerName, secret, code) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "twoFactorLoginApiBaseUrl", + "validateAndSave", + { + providerName: providerName, + secret: secret, + code: code + })), + "Failed to retrieve data"); + } + function disable(providerName, userKey) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "twoFactorLoginApiBaseUrl", + "disable", + { + providerName: providerName, + userKey: userKey + })), + "Failed to retrieve data"); + } + function disableWithCode(providerName, code) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "twoFactorLoginApiBaseUrl", + "disableWithCode", + { + providerName: providerName, + code: code + })), + "Failed to retrieve data"); + } + + var resource = { + viewPathForProviderName: viewPathForProviderName, + get2FAProvidersForUser:get2FAProvidersForUser, + setupInfo:setupInfo, + validateAndSave:validateAndSave, + disable: disable, + disableWithCode: disableWithCode + }; + + return resource; + + } + + angular.module('umbraco.resources').factory('twoFactorLoginResource', twoFactorLoginResource); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js index ad02520c5a..c9db8cf00e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js @@ -273,7 +273,7 @@ angular.module('umbraco.services') */ removeAll: function () { angularHelper.safeApply($rootScope, function() { - nArray = []; + nArray.length = 0; }); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/configuretwofactor.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/configuretwofactor.controller.js new file mode 100644 index 0000000000..382d9ef35e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/configuretwofactor.controller.js @@ -0,0 +1,79 @@ +//used for the user editor overlay +angular.module("umbraco").controller("Umbraco.Editors.ConfigureTwoFactorController", + function ($scope, + localizationService, + notificationsService, + overlayService, + twoFactorLoginResource, + editorService) { + + + let vm = this; + vm.close = close; + vm.enable = enable; + vm.disable = disable; + vm.code = ""; + vm.buttonState = "init"; + + localizationService.localize("user_configureTwoFactor").then(function (value) { + vm.title = value; + }); + + function onInit() { + vm.code = ""; + + twoFactorLoginResource.get2FAProvidersForUser($scope.model.user.id) + .then(function (providers) { + vm.providers = providers; + }); + } + + function close() { + if ($scope.model.close) { + $scope.model.close(); + } + } + + function enable(providerName) { + twoFactorLoginResource.viewPathForProviderName(providerName) + .then(function (viewPath) { + var providerSettings = { + user: $scope.model.user, + providerName: providerName, + size: "small", + view: viewPath, + close: function () { + notificationsService.removeAll(); + editorService.close(); + onInit(); + } + }; + + editorService.open(providerSettings); + }).catch(onError); + } + + function disable(provider) { + + const disableTwoFactorSettings = { + provider, + user: vm.user, + size: "small", + view: "views/common/infiniteeditors/twofactor/disabletwofactor.html", + close: function () { + editorService.close(); + onInit(); + } + }; + + editorService.open(disableTwoFactorSettings); + } + + function onError(error) { + vm.buttonState = "error"; + overlayService.ysod(error); + } + + //initialize + onInit(); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/configuretwofactor.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/configuretwofactor.html new file mode 100644 index 0000000000..15bb70b7f9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/configuretwofactor.html @@ -0,0 +1,58 @@ +
+ + + + + + + + + + + + + + + + +
+

+ + +

+ + + + +
+ + +
+
+ + + + +
+ +
+
+
+ + + + + + + + + + + + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/disabletwofactor.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/disabletwofactor.controller.js new file mode 100644 index 0000000000..b8e909e633 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/disabletwofactor.controller.js @@ -0,0 +1,53 @@ +//used for the user editor overlay +angular.module("umbraco").controller("Umbraco.Editors.DisableTwoFactorController", + function ($scope, + localizationService, + notificationsService, + overlayService, + twoFactorLoginResource) { + + let vm = this; + vm.close = close; + vm.disableWithCode = disableWithCode; + vm.code = ""; + vm.buttonState = "init"; + vm.authForm = {}; + + if (!$scope.model.provider) { + notificationsService.error("No provider specified"); + } + vm.provider = $scope.model.provider; + vm.title = vm.provider.providerName; + + function close() { + if ($scope.model.close) { + $scope.model.close(); + } + } + + function disableWithCode() { + vm.authForm.token.$setValidity("token", true); + vm.buttonState = "busy"; + twoFactorLoginResource.disableWithCode(vm.provider.providerName, vm.code) + .then(onResponse) + .catch(onError); + } + + function onResponse(response) { + if (response) { + vm.buttonState = "success"; + localizationService.localize("user_2faProviderIsDisabledMsg").then(function (value) { + notificationsService.info(value); + }); + close(); + } else { + vm.buttonState = "error"; + vm.authForm.token.$setValidity("token", false); + } + } + + function onError(error) { + vm.buttonState = "error"; + overlayService.ysod(error); + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/disabletwofactor.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/disabletwofactor.html new file mode 100644 index 0000000000..11c2c813fd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/twofactor/disabletwofactor.html @@ -0,0 +1,56 @@ +
+ + +
+ + + + + + + + + + + + + + + + +
+ + Invalid code entered + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + +
+ +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/user/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/user/user.controller.js new file mode 100644 index 0000000000..7f9e546709 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/user/user.controller.js @@ -0,0 +1,219 @@ +angular.module("umbraco") + .controller("Umbraco.Editors.UserController", function ($scope, $location, $timeout, + dashboardResource, userService, historyService, eventsService, + externalLoginInfoService, authResource, + currentUserResource, formHelper, localizationService, editorService, twoFactorLoginResource) { + + let vm = this; + + vm.history = historyService.getCurrent(); + vm.showPasswordFields = false; + vm.changePasswordButtonState = "init"; + vm.hasTwoFactorProviders = false; + + localizationService.localize("general_user").then(function (value) { + vm.title = value; + }); + + // Set flag if any have deny local login, in which case we must disable all password functionality + vm.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin(); + // Only include login providers that have editable options + vm.externalLoginProviders = externalLoginInfoService.getLoginProvidersWithOptions(); + + vm.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl; + var evts = []; + evts.push(eventsService.on("historyService.add", function (e, args) { + vm.history = args.all; + })); + evts.push(eventsService.on("historyService.remove", function (e, args) { + vm.history = args.all; + })); + evts.push(eventsService.on("historyService.removeAll", function (e, args) { + vm.history = []; + })); + + vm.logout = function () { + + //Add event listener for when there are pending changes on an editor which means our route was not successful + var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) { + //one time listener, remove the event + pendingChangeEvent(); + vm.close(); + }); + + + //perform the path change, if it is successful then the promise will resolve otherwise it will fail + vm.close(); + $location.path("/logout").search(''); + }; + + vm.gotoHistory = function (link) { + $location.path(link); + vm.close(); + }; + /* + //Manually update the remaining timeout seconds + function updateTimeout() { + $timeout(function () { + if (vm.remainingAuthSeconds > 0) { + vm.remainingAuthSeconds--; + $scope.$digest(); + //recurse + updateTimeout(); + } + + }, 1000, false); // 1 second, do NOT execute a global digest + } + */ + function updateUserInfo() { + //get the user + userService.getCurrentUser().then(function (user) { + vm.user = user; + if (vm.user) { + vm.remainingAuthSeconds = vm.user.remainingAuthSeconds; + vm.canEditProfile = _.indexOf(vm.user.allowedSections, "users") > -1; + //set the timer + //updateTimeout(); + + currentUserResource.getCurrentUserLinkedLogins().then(function (logins) { + + //reset all to be un-linked + vm.externalLoginProviders.forEach(provider => provider.linkedProviderKey = undefined); + + //set the linked logins + for (var login in logins) { + var found = _.find(vm.externalLoginProviders, function (i) { + return i.authType == login; + }); + if (found) { + found.linkedProviderKey = logins[login]; + } + } + }); + + //go get the config for the membership provider and add it to the model + authResource.getPasswordConfig(user.id).then(function (data) { + vm.changePasswordModel.config = data; + //ensure the hasPassword config option is set to true (the user of course has a password already assigned) + //this will ensure the oldPassword is shown so they can change it + // disable reset password functionality beacuse it does not make sense inside the backoffice + vm.changePasswordModel.config.hasPassword = true; + vm.changePasswordModel.config.disableToggle = true; + }); + + twoFactorLoginResource.get2FAProvidersForUser(vm.user.id).then(function (providers) { + vm.hasTwoFactorProviders = providers.length > 0; + }); + + } + }); + + + } + + vm.linkProvider = function (e) { + e.target.submit(); + } + + vm.unlink = function (e, loginProvider, providerKey) { + var result = confirm("Are you sure you want to unlink this account?"); + if (!result) { + e.preventDefault(); + return; + } + + authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) { + updateUserInfo(); + }); + } + + //create the initial model for change password + vm.changePasswordModel = { + config: {}, + value: {} + }; + + updateUserInfo(); + + + //remove all event handlers + $scope.$on('$destroy', function () { + for (var e = 0; e < evts.length; e++) { + evts[e](); + } + + }); + + vm.changePassword = function () { + + if (formHelper.submitForm({ scope: $scope })) { + + vm.changePasswordButtonState = "busy"; + + currentUserResource.changePassword(vm.changePasswordModel.value).then(function (data) { + + //reset old data + clearPasswordFields(); + + formHelper.resetForm({ scope: $scope }); + + vm.changePasswordButtonState = "success"; + $timeout(function () { + vm.togglePasswordFields(); + }, 2000); + + }, function (err) { + formHelper.resetForm({ scope: $scope, hasErrors: true }); + formHelper.handleError(err); + + vm.changePasswordButtonState = "error"; + + }); + + } + + }; + + vm.togglePasswordFields = function () { + clearPasswordFields(); + vm.showPasswordFields = !vm.showPasswordFields; + } + + function clearPasswordFields() { + vm.changePasswordModel.value.oldPassword = ""; + vm.changePasswordModel.value.newPassword = ""; + vm.changePasswordModel.value.confirm = ""; + } + + vm.editUser = function () { + $location + .path('/users/users/user/' + vm.user.id); + vm.close(); + } + + vm.toggleConfigureTwoFactor = function () { + + const configureTwoFactorSettings = { + create: true, + user: vm.user, + isCurrentUser: true,// From this view we are always current user (used by the overlay) + size: "small", + view: "views/common/infiniteeditors/twofactor/configuretwofactor.html", + close: function () { + editorService.close(); + } + }; + + editorService.open(configureTwoFactorSettings); + } + + vm.close = function () { + if ($scope.model.close) { + $scope.model.close(); + } + } + + dashboardResource.getDashboard("user-dialog").then(function (dashboard) { + vm.dashboard = dashboard; + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/user/user.html new file mode 100644 index 0000000000..c67f65a7d2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/user/user.html @@ -0,0 +1,145 @@ +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+
+
+ + + + + + + +
+ +
+ +
+
+ + +
+ + + +
+ +
+ +
+ +
+ +
+ + + + + + + + + +
+ +
+ +
+ Change password +
+ +
+ + + + + + + + + + +
+ +
+ +
+
+
{{tab.label}}
+
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.controller.js new file mode 100644 index 0000000000..6c57085c8b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.controller.js @@ -0,0 +1,38 @@ +angular.module("umbraco").controller("Umbraco.Login2faController", + function ($scope, userService, authResource) { + let vm = this; + vm.code = ""; + vm.provider = ""; + vm.providers = []; + vm.stateValidateButton = "init"; + vm.authForm = {}; + + authResource.get2FAProviders() + .then(function (data) { + vm.providers = data; + if (vm.providers.length > 0) { + vm.provider = vm.providers[0]; + } + }); + + vm.validate = function () { + vm.error = ""; + vm.stateValidateButton = "busy"; + vm.authForm.token.$setValidity('token', true); + + authResource.verify2FACode(vm.provider, vm.code) + .then(function (data) { + vm.stateValidateButton = "success"; + userService.setAuthenticationSuccessful(data); + $scope.vm.twoFactor.submitCallback(); + }) + .catch(function () { + vm.stateValidateButton = "error"; + vm.authForm.token.$setValidity('token', false); + }); + }; + + vm.goBack = function () { + $scope.vm.twoFactor.cancelCallback(); + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html new file mode 100644 index 0000000000..fd444f180c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html @@ -0,0 +1,44 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js deleted file mode 100644 index a98eacd702..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js +++ /dev/null @@ -1,194 +0,0 @@ -angular.module("umbraco") - .controller("Umbraco.Overlays.UserController", function ($scope, $location, $timeout, - dashboardResource, userService, historyService, eventsService, - externalLoginInfo, externalLoginInfoService, authResource, - currentUserResource, formHelper, localizationService) { - - $scope.history = historyService.getCurrent(); - //$scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; - $scope.showPasswordFields = false; - $scope.changePasswordButtonState = "init"; - $scope.model.title = "user.name"; - //$scope.model.subtitle = "Umbraco version" + " " + $scope.version; - /* - if(!$scope.model.title) { - localizationService.localize("general_user").then(function(value){ - $scope.model.title = value; - }); - } - */ - - // Set flag if any have deny local login, in which case we must disable all password functionality - $scope.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin(); - // Only include login providers that have editable options - $scope.externalLoginProviders = externalLoginInfoService.getLoginProvidersWithOptions(); - - $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl; - var evts = []; - evts.push(eventsService.on("historyService.add", function (e, args) { - $scope.history = args.all; - })); - evts.push(eventsService.on("historyService.remove", function (e, args) { - $scope.history = args.all; - })); - evts.push(eventsService.on("historyService.removeAll", function (e, args) { - $scope.history = []; - })); - - $scope.logout = function () { - - //Add event listener for when there are pending changes on an editor which means our route was not successful - var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) { - //one time listener, remove the event - pendingChangeEvent(); - $scope.model.close(); - }); - - - //perform the path change, if it is successful then the promise will resolve otherwise it will fail - $scope.model.close(); - $location.path("/logout").search(''); - }; - - $scope.gotoHistory = function (link) { - $location.path(link); - $scope.model.close(); - }; - /* - //Manually update the remaining timeout seconds - function updateTimeout() { - $timeout(function () { - if ($scope.remainingAuthSeconds > 0) { - $scope.remainingAuthSeconds--; - $scope.$digest(); - //recurse - updateTimeout(); - } - - }, 1000, false); // 1 second, do NOT execute a global digest - } - */ - function updateUserInfo() { - //get the user - userService.getCurrentUser().then(function (user) { - $scope.user = user; - if ($scope.user) { - $scope.model.title = user.name; - $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds; - $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1; - //set the timer - //updateTimeout(); - - currentUserResource.getCurrentUserLinkedLogins().then(function(logins) { - - //reset all to be un-linked - $scope.externalLoginProviders.forEach(provider => provider.linkedProviderKey = undefined); - - //set the linked logins - for (var login in logins) { - var found = _.find($scope.externalLoginProviders, function (i) { - return i.authType == login; - }); - if (found) { - found.linkedProviderKey = logins[login]; - } - } - }); - - //go get the config for the membership provider and add it to the model - authResource.getPasswordConfig(user.id).then(function (data) { - $scope.changePasswordModel.config = data; - //ensure the hasPassword config option is set to true (the user of course has a password already assigned) - //this will ensure the oldPassword is shown so they can change it - // disable reset password functionality beacuse it does not make sense inside the backoffice - $scope.changePasswordModel.config.hasPassword = true; - $scope.changePasswordModel.config.disableToggle = true; - }); - - } - }); - } - - $scope.linkProvider = function (e) { - e.target.submit(); - } - - $scope.unlink = function (e, loginProvider, providerKey) { - var result = confirm("Are you sure you want to unlink this account?"); - if (!result) { - e.preventDefault(); - return; - } - - authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) { - updateUserInfo(); - }); - } - - //create the initial model for change password - $scope.changePasswordModel = { - config: {}, - value: {} - }; - - updateUserInfo(); - - //remove all event handlers - $scope.$on('$destroy', function () { - for (var e = 0; e < evts.length; e++) { - evts[e](); - } - - }); - - $scope.changePassword = function() { - - if (formHelper.submitForm({ scope: $scope })) { - - $scope.changePasswordButtonState = "busy"; - - currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) { - - //reset old data - clearPasswordFields(); - - formHelper.resetForm({ scope: $scope }); - - $scope.changePasswordButtonState = "success"; - $timeout(function() { - $scope.togglePasswordFields(); - }, 2000); - - }, function (err) { - formHelper.resetForm({ scope: $scope, hasErrors: true }); - formHelper.handleError(err); - - $scope.changePasswordButtonState = "error"; - - }); - - } - - }; - - $scope.togglePasswordFields = function() { - clearPasswordFields(); - $scope.showPasswordFields = !$scope.showPasswordFields; - } - - function clearPasswordFields() { - $scope.changePasswordModel.value.oldPassword = ""; - $scope.changePasswordModel.value.newPassword = ""; - $scope.changePasswordModel.value.confirm = ""; - } - - $scope.editUser = function() { - $location - .path('/users/users/user/' + $scope.user.id); - $scope.model.close(); - } - - dashboardResource.getDashboard("user-dialog").then(function (dashboard) { - $scope.dashboard = dashboard; - }); - }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html deleted file mode 100644 index 24acef995e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ /dev/null @@ -1,143 +0,0 @@ -
-
- -
- Your profile -
- - - - - - - - - - -
- -
- -
- External login providers -
- -
- -
- -
-
- - -
- - -
- -
- -
- - -
-
- Your recent history -
- -
- -
- -
- Change password -
- -
- - - - - - - - - - -
- -
- -
-
-
{{tab.label}}
-
-
-
-
-
-
diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 684ce6d2f0..f9d48d95e9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function UserEditController($scope, eventsService, $q, $location, $routeParams, formHelper, usersResource, + function UserEditController($scope, eventsService, $q, $location, $routeParams, formHelper, usersResource, twoFactorLoginResource, userService, contentEditingHelper, localizationService, mediaHelper, Upload, umbRequestHelper, usersHelper, authResource, dateHelper, editorService, overlayService, externalLoginInfoService) { @@ -16,6 +16,7 @@ }; vm.breadcrumbs = []; vm.showBackButton = true; + vm.hasTwoFactorProviders = false; vm.avatarFile = {}; vm.labels = {}; vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; @@ -38,6 +39,7 @@ vm.disableUser = disableUser; vm.enableUser = enableUser; vm.unlockUser = unlockUser; + vm.toggleConfigureTwoFactor = toggleConfigureTwoFactor; vm.resendInvite = resendInvite; vm.deleteNonLoggedInUser = deleteNonLoggedInUser; vm.changeAvatar = changeAvatar; @@ -101,6 +103,10 @@ $scope.$emit("$setAccessibleHeader", false, "general_user", false, vm.user.name, "", true); vm.loading = false; }); + + twoFactorLoginResource.get2FAProvidersForUser(vm.user.id).then(function (providers) { + vm.hasTwoFactorProviders = providers.length > 0; + }); }); } @@ -397,6 +403,23 @@ }); } + function toggleConfigureTwoFactor() { + + var configureTwoFactorSettings = { + create: true, + user: vm.user, + isCurrentUser: vm.user.isCurrentUser, + size: "small", + view: "views/common/infiniteeditors/twofactor/configuretwofactor.html", + close: function() { + editorService.close(); + } + }; + + editorService.open(configureTwoFactorSettings); + } + + function resendInvite() { vm.resendInviteButtonState = "busy"; diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.controller.js index f1df6c3228..67d1efec85 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.controller.js @@ -6,6 +6,7 @@ var vm = this; vm.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin(); + } angular.module("umbraco").controller("Umbraco.Editors.Users.DetailsController", DetailsController); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index 5a4181c9f3..eaa92b7a6e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -274,6 +274,16 @@ size="s">
+
+ + +
+
+ + <_ContentIncludedByDefault Remove="wwwroot\umbraco\views\common\infiniteeditors\twofactor\enabletwofactor.html" /> + + false false diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 5ec919874f..9916973405 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -565,11 +565,11 @@ Du tilføjer flere sprog under 'sprog' i menuen til venstre - ]]> + ]]> Kulturnavn + ]]> Ordbogsoversigt @@ -902,7 +902,7 @@ Database konfiguration installér knappen for at installere Umbraco %0% databasen - ]]> + ]]> Næste for at fortsætte.]]> Databasen er ikke fundet. Kontrollér venligst at informationen i database forbindelsesstrengen i "web.config" filen er korrekt.

@@ -981,7 +981,7 @@ /web.config filen og opdatére 'AppSetting' feltet UmbracoConfigurationStatus i bunden til '%0%'.]]> komme igang med det samme ved at klikke på "Start Umbraco" knappen nedenfor.
Hvis du er ny med Umbraco, kan du finde masser af ressourcer på vores 'getting started' sider. -]]>
+]]>
Start UmbracoFor at administrere dit website skal du blot åbne Umbraco administrationen og begynde at tilføje indhold, opdatere skabelonerne og stylesheets'ene eller tilføje ny funktionalitet.]]> Forbindelse til databasen fejlede. @@ -1028,6 +1028,12 @@ Umbraco: Nulstil adgangskode Dit brugernavn til at logge på Umbraco backoffice er: %0%

Klik her for at nulstille din adgangskode eller kopier/indsæt denne URL i din browser:

%1%

]]>
+ Sidste skridt + Det er påkrævet at du verificerer din identitet. + Vælg venligst en autentificeringsmetode + Kode + Indtast venligst koden fra dit device + Koden kunne ikke genkendes Skrivebord @@ -1065,7 +1071,7 @@ Gå til http://%4%/#/content/content/edit/%5% for at redigere. Ha' en dejlig dag! Mange hilsner fra Umbraco robotten - ]]> + ]]> Hej %0%

Dette er en automatisk mail for at informere dig om at opgaven '%1%' er blevet udførtpå siden '%2%' af brugeren '%3%'

@@ -1166,14 +1172,14 @@ Mange hilsner fra Umbraco robotten Udgivelsen kunne ikke udgives da publiceringsdato er sat + ]]>
+ ]]> + ]]> %0% kunne ikke udgives, fordi et 3. parts modul annullerede handlingen @@ -1453,24 +1459,24 @@ Mange hilsner fra Umbraco robotten @RenderBody() element. - ]]> + ]]> Definer en sektion @section { ... }. Herefter kan denne sektion flettes ind i overliggende skabelon ved at indsætte et @RenderSection element. - ]]> + ]]> Indsæt en sektion @RenderSection(name) element. Den underliggende skabelon skal have defineret en sektion via et @section [name]{ ... } element. - ]]> + ]]> Sektionsnavn Sektionen er obligatorisk @section -definition. - ]]> + ]]> Query builder sider returneret, på Returner @@ -1918,6 +1924,9 @@ Mange hilsner fra Umbraco robotten Ældste Sidst logget ind Ingen brugere er blevet tilføjet + Hvis du ønsker at slå denne autentificeringsmetode fra, så skal du nu indtaste koden fra dit device: + Denne autentificeringsmetode er slået til + Den valgte autentificeringsmetode er nu slået fra Validering diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 23277ee296..6670f692b8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1149,6 +1149,12 @@ To manage your website, simply open the Umbraco backoffice and start adding cont ]]> Umbraco: Security Code Your security code is: %0% + One last step + You have enabled 2-factor authentication and must verify your identity. + Please choose a 2-factor provider + Verification code + Please enter the verification code + Invalid code entered Dashboard @@ -2219,6 +2225,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Oldest Last login No user groups have been added + If you wish to disable this two-factor provider, then you must enter the code shown on your authentication device: + This two-factor provider is enabled + This two-factor provider is now disabled Validation diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 5e2935b395..c901b1040a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -587,11 +587,11 @@ %0%' below - ]]> + ]]> Culture Name + ]]> Dictionary overview @@ -862,6 +862,7 @@ URL User Username + Validate Value View Welcome... @@ -933,7 +934,7 @@ Database configuration install button to install the Umbraco %0% database - ]]> + ]]> Next to proceed.]]> Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

@@ -951,7 +952,7 @@

Don't worry - no content will be deleted and everything will continue working afterwards!

- ]]>
+ ]]> Press Next to proceed. ]]> @@ -997,19 +998,19 @@ + ]]> I want to start from scratch learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. - ]]> + ]]> You've just set up a clean Umbraco platform. What do you want to do next? Runway is installed This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules - ]]> + ]]> Only recommended for experienced users I want to start with a simple website Included with Runway: Home page, Getting Started page, Installing Modules page.
Optional Modules: Top Navigation, Sitemap, Contact, Gallery. - ]]>
+ ]]> What is Runway Step 1/5 Accept license Step 2/5: Database configuration @@ -1100,72 +1101,78 @@ To manage your website, simply open the Umbraco backoffice and start adding cont -
- - - - - - - - - - - - + +
-
-
- - - + +
- - - + + +
-

+
+ +

+
+ + + + + + +
+
+

+

If you cannot click on the link, copy and paste this URL into your browser window:

+ + + + +
+ +%1% + +
+

+
+ + + +


+
+ + + + + + + ]]> + One last step + You have enabled 2-factor authentication and must verify your identity. + Please choose a 2-factor provider + Verification code + Please enter the verification code + Invalid code entered Dashboard @@ -1205,7 +1212,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Have a nice day! Cheers from the Umbraco robot - ]]> + ]]> The following languages have been modified %0% @@ -1222,70 +1229,70 @@ To manage your website, simply open the Umbraco backoffice and start adding cont -
- - - - - - - - - - - - + +
-
-
- - - + +
- - - + + +
-

+
+ +

+
+ + + + - - -
+
+
+ + + - -
+ + + - -
+

Hi %0%,

-

+

This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' -

- - - - - - -
- -
- EDIT
-
-

-

Update summary:

+

+ + + + + + +
+ +
+EDIT
+
+

+

Update summary:

%6%

-

+

Have a nice day!

Cheers from the Umbraco robot

-
-
-


-
-
- - - ]]> +
+ + + +


+ + + + + + + + ]]>
The following languages have been modified:

%0% - ]]>
+ ]]> [%0%] Notification about %1% performed on %2% Notifications @@ -1296,7 +1303,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension. - ]]> + ]]> This will delete the package Include all child nodes Installed @@ -1388,22 +1395,22 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Insufficient user permissions to publish all descendant documents + ]]> + ]]> + ]]> + ]]> + ]]> + ]]> Validation failed for required language '%0%'. This @@ -1416,7 +1423,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Publish %0% and all its subpages Publish to publish %0% and thereby making its content publicly available.

You can publish this page and all its subpages by checking Include unpublished subpages below. - ]]>
+ ]]>
You have not configured any approved colors @@ -1694,23 +1701,23 @@ To manage your website, simply open the Umbraco backoffice and start adding cont @RenderBody() placeholder. - ]]> + ]]> Define a named section @section { ... }. This can be rendered in a specific area of the parent of this template, by using @RenderSection. - ]]> + ]]> Render a named section @RenderSection(name) placeholder. This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. - ]]> + ]]> Section Name Section is mandatory @section definition, otherwise an error is shown. - ]]> + ]]> Query builder items returned, in I want @@ -2002,7 +2009,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Have a nice day! Cheers from the Umbraco robot - ]]> + ]]> No translator users found. Please create a translator user before you start sending content to translation @@ -2178,6 +2185,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont you. Click the circle above to upload your photo. Writer + Configure Two-Factor Change Your profile Your recent history @@ -2202,82 +2210,82 @@ To manage your website, simply open the Umbraco backoffice and start adding cont -
- - - - - - - - - - - - + +
-
-
- - - + +
- - - + + +
-

+
+ +

+
+ + + + + + +
+
+

If you cannot click on the link, copy and paste this URL into your browser window:

+ + + + +
+ +%3% + +
+

+
+ + + +


+ + + + + + +]]> Resending invitation... Delete User Are you sure you wish to delete this user account? @@ -2293,6 +2301,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Oldest Last login No user groups have been added + If you wish to disable this two-factor provider, then you must enter the code shown on your authentication device: + This two-factor provider is enabled + This two-factor provider is now disabled Validation diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Tours/backofficeTour.ts b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Tours/backofficeTour.ts index dc1232e148..dbb82f53f4 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Tours/backofficeTour.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Tours/backofficeTour.ts @@ -93,7 +93,7 @@ function runBackOfficeIntroTour(percentageComplete, buttonText, timeout) { cy.get('.umb-tour-step__counter', { timeout: timeout }).contains('9/13'); cy.get('.umb-tour-step__footer .umb-button').should('be.visible').click(); cy.get('.umb-tour-step__counter', { timeout: timeout }).contains('10/13'); - cy.get('.umb-overlay-drawer__align-right .umb-button').should('be.visible').click(); + cy.get('[data-element~="overlay-user"] [data-element="button-overlayClose"]').should('be.visible').click(); cy.get('.umb-tour-step__counter', { timeout: timeout }).contains('11/13'); cy.umbracoGlobalHelp().click() diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Security/BackOfficeUserStoreTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Security/BackOfficeUserStoreTests.cs index ac0d19040e..d794af09e4 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Security/BackOfficeUserStoreTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Security/BackOfficeUserStoreTests.cs @@ -26,6 +26,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Security private IExternalLoginWithKeyService ExternalLoginService => GetRequiredService(); private IUmbracoMapper UmbracoMapper => GetRequiredService(); private ILocalizedTextService TextService => GetRequiredService(); + private ITwoFactorLoginService TwoFactorLoginService => GetRequiredService(); private BackOfficeUserStore GetUserStore() => new BackOfficeUserStore( @@ -36,7 +37,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Security Options.Create(GlobalSettings), UmbracoMapper, new BackOfficeErrorDescriber(TextService), - AppCaches); + AppCaches, + TwoFactorLoginService + ); [Test] public async Task Can_Persist_Is_Approved() From c7c3a68691b3f2a5bf38d221db8ea0680133b109 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 19 Apr 2022 08:55:13 +0200 Subject: [PATCH 60/67] fixes breaking changes by reintroducing old ctor --- .../Trees/MemberGroupTreeController.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs index 858a1c8184..e41f865981 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs @@ -1,14 +1,17 @@ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Cms.Web.Common.DependencyInjection; using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.Trees @@ -21,6 +24,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees { private readonly IMemberGroupService _memberGroupService; + [ActivatorUtilitiesConstructor] public MemberGroupTreeController( ILocalizedTextService localizedTextService, UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, @@ -31,6 +35,24 @@ namespace Umbraco.Cms.Web.BackOffice.Trees : base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator, memberTypeService) => _memberGroupService = memberGroupService; + [Obsolete("Use ctor with all params")] + public MemberGroupTreeController( + ILocalizedTextService localizedTextService, + UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, + IMenuItemCollectionFactory menuItemCollectionFactory, + IMemberGroupService memberGroupService, + IEventAggregator eventAggregator) + : this(localizedTextService, + umbracoApiControllerTypeCollection, + menuItemCollectionFactory, + memberGroupService, + eventAggregator, + StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + protected override IEnumerable GetTreeNodesFromService(string id, FormCollection queryStrings) => _memberGroupService.GetAll() .OrderBy(x => x.Name) From bef052ad3aff277a5892301d41652bed0016d24c Mon Sep 17 00:00:00 2001 From: Robert Foster Date: Tue, 19 Apr 2022 20:31:38 +0930 Subject: [PATCH 61/67] V9/tmp nucache lock (#12149) * wrapping SetAllFastSortedLocked in try catch * Potential issue mitigation by retrieving all data from the database at once instead of one at a time while populating the NuCache file. * Moved content retrieval to within the try-catch block. * using InGroupsOf() to retrieve content without loading absolutely everything into memory. * added "old" method signatures to prevent breaking change in ContentStore * Revert code style cleanups for clarity Co-authored-by: Sebastiaan Janssen --- .../Configuration/Models/NuCacheSettings.cs | 7 + .../ContentStore.cs | 213 ++++++++++++------ .../PublishedSnapshotService.cs | 33 ++- 3 files changed, 179 insertions(+), 74 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs index 7767d1dbdc..ee41fc32d3 100644 --- a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs @@ -13,6 +13,7 @@ namespace Umbraco.Cms.Core.Configuration.Models { internal const string StaticNuCacheSerializerType = "MessagePack"; internal const int StaticSqlPageSize = 1000; + internal const int StaticKitBatchSize = 1; /// /// Gets or sets a value defining the BTree block size. @@ -31,6 +32,12 @@ namespace Umbraco.Cms.Core.Configuration.Models [DefaultValue(StaticSqlPageSize)] public int SqlPageSize { get; set; } = StaticSqlPageSize; + /// + /// The size to use for nucache Kit batches. Higher value means more content loaded into memory at a time. + /// + [DefaultValue(StaticKitBatchSize)] + public int KitBatchSize { get; set; } = StaticKitBatchSize; + public bool UnPublishedContentCompression { get; set; } = false; } } diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index ddb8ea9057..1ccc75331b 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.PublishedCache.Snap; +using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.PublishedCache { @@ -342,7 +343,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache if (node == null) continue; var contentTypeId = node.ContentType.Id; if (index.TryGetValue(contentTypeId, out var contentType) == false) continue; - SetValueLocked(_contentNodes, node.Id, new ContentNode(node, _publishedModelFactory, contentType)); + SetValueLocked(_contentNodes, node.Id, new ContentNode(node, _publishedModelFactory, contentType)); } } @@ -515,7 +516,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache _contentNodes.TryGetValue(id, out var link); if (link?.Value == null) continue; - var node = new ContentNode(link.Value, _publishedModelFactory, contentType); + var node = new ContentNode(link.Value, _publishedModelFactory, contentType); SetValueLocked(_contentNodes, id, node); if (_localDb != null) RegisterChange(id, node.ToKit()); } @@ -631,12 +632,12 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // moving? var moving = existing != null && existing.ParentContentId != kit.Node.ParentContentId; - // manage children - if (existing != null) - { - kit.Node.FirstChildContentId = existing.FirstChildContentId; - kit.Node.LastChildContentId = existing.LastChildContentId; - } + // manage children + if (existing != null) + { + kit.Node.FirstChildContentId = existing.FirstChildContentId; + kit.Node.LastChildContentId = existing.LastChildContentId; + } // set SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); @@ -696,7 +697,36 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache /// /// Thrown if this method is not called within a write lock /// + [Obsolete("Use the overload that takes a 'kitGroupSize' parameter instead")] public bool SetAllFastSortedLocked(IEnumerable kits, bool fromDb) + { + return SetAllFastSortedLocked(kits, 1, fromDb); + } + + /// + /// Builds all kits on startup using a fast forward only cursor + /// + /// + /// All kits sorted by Level + Parent Id + Sort order + /// + /// + /// True if the data is coming from the database (not the local cache db) + /// + /// + /// + /// This requires that the collection is sorted by Level + ParentId + Sort Order. + /// This should be used only on a site startup as the first generations. + /// This CANNOT be used after startup since it bypasses all checks for Generations. + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetAllFastSortedLocked(IEnumerable kits, int kitGroupSize, bool fromDb) { EnsureLocked(); @@ -714,54 +744,67 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache ContentNode previousNode = null; ContentNode parent = null; - foreach (var kit in kits) + // By using InGroupsOf() here we are forcing the database query result extraction to retrieve items in batches, + // reducing the possibility of a database timeout (ThreadAbortException) on large datasets. + // This in turn reduces the possibility that the NuCache file will remain locked, because an exception + // here results in the calling method to not release the lock. + + // However the larger the batck size, the more content loaded into memory. So by default, this is set to 1 and can be increased by setting + // the configuration setting Umbraco:CMS:NuCache:KitPageSize to a higher value. + + // If we are not loading from the database, then we can ignore this restriction. + + foreach (var kitGroup in kits.InGroupsOf(!fromDb || kitGroupSize < 1 ? 1 : kitGroupSize)) { - if (!BuildKit(kit, out var parentLink)) + foreach (var kit in kitGroup) { - ok = false; - continue; // skip that one + if (!BuildKit(kit, out var parentLink)) + { + ok = false; + continue; // skip that one + } + + var thisNode = kit.Node; + + if (parent == null) + { + // first parent + parent = parentLink.Value; + parent.FirstChildContentId = thisNode.Id; // this node is the first node + } + else if (parent.Id != parentLink.Value.Id) + { + // new parent + parent = parentLink.Value; + parent.FirstChildContentId = thisNode.Id; // this node is the first node + previousNode = null; // there is no previous sibling + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Set {thisNodeId} with parent {thisNodeParentContentId}", thisNode.Id, thisNode.ParentContentId); + } + + SetValueLocked(_contentNodes, thisNode.Id, thisNode); + + // if we are initializing from the database source ensure the local db is updated + if (fromDb && _localDb != null) RegisterChange(thisNode.Id, kit); + + // this node is always the last child + parent.LastChildContentId = thisNode.Id; + + // wire previous node as previous sibling + if (previousNode != null) + { + previousNode.NextSiblingContentId = thisNode.Id; + thisNode.PreviousSiblingContentId = previousNode.Id; + } + + // this node becomes the previous node + previousNode = thisNode; + + _contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id; } - - var thisNode = kit.Node; - - if (parent == null) - { - // first parent - parent = parentLink.Value; - parent.FirstChildContentId = thisNode.Id; // this node is the first node - } - else if (parent.Id != parentLink.Value.Id) - { - // new parent - parent = parentLink.Value; - parent.FirstChildContentId = thisNode.Id; // this node is the first node - previousNode = null; // there is no previous sibling - } - - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Set {thisNodeId} with parent {thisNodeParentContentId}", thisNode.Id, thisNode.ParentContentId); - } - - SetValueLocked(_contentNodes, thisNode.Id, thisNode); - - // if we are initializing from the database source ensure the local db is updated - if (fromDb && _localDb != null) RegisterChange(thisNode.Id, kit); - - // this node is always the last child - parent.LastChildContentId = thisNode.Id; - - // wire previous node as previous sibling - if (previousNode != null) - { - previousNode.NextSiblingContentId = thisNode.Id; - thisNode.PreviousSiblingContentId = previousNode.Id; - } - - // this node becomes the previous node - previousNode = thisNode; - - _contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id; } return ok; @@ -779,7 +822,27 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache /// /// Thrown if this method is not called within a write lock /// + [Obsolete("Use the overload that takes the 'kitGroupSize' and 'fromDb' parameters instead")] public bool SetAllLocked(IEnumerable kits) + { + return SetAllLocked(kits, 1, false); + } + + /// + /// Set all data for a collection of + /// + /// + /// + /// True if the data is coming from the database (not the local cache db) + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetAllLocked(IEnumerable kits, int kitGroupSize, bool fromDb) { EnsureLocked(); @@ -792,25 +855,37 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache //ClearLocked(_contentTypesById); //ClearLocked(_contentTypesByAlias); - foreach (var kit in kits) + // By using InGroupsOf() here we are forcing the database query result extraction to retrieve items in batches, + // reducing the possibility of a database timeout (ThreadAbortException) on large datasets. + // This in turn reduces the possibility that the NuCache file will remain locked, because an exception + // here results in the calling method to not release the lock. + + // However the larger the batck size, the more content loaded into memory. So by default, this is set to 1 and can be increased by setting + // the configuration setting Umbraco:CMS:NuCache:KitPageSize to a higher value. + + // If we are not loading from the database, then we can ignore this restriction. + foreach (var kitGroup in kits.InGroupsOf(!fromDb || kitGroupSize < 1 ? 1 : kitGroupSize)) { - if (!BuildKit(kit, out var parent)) + foreach (var kit in kitGroup) { - ok = false; - continue; // skip that one + if (!BuildKit(kit, out var parent)) + { + ok = false; + continue; // skip that one + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Set {kitNodeId} with parent {kitNodeParentContentId}", kit.Node.Id, kit.Node.ParentContentId); + } + + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + AddTreeNodeLocked(kit.Node, parent); + + _contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id; } - - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Set {kitNodeId} with parent {kitNodeParentContentId}", kit.Node.Id, kit.Node.ParentContentId); - } - - SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); - - if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddTreeNodeLocked(kit.Node, parent); - - _contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id; } return ok; diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 20df726080..5dac6c1b9b 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -337,8 +337,19 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache _localContentDb?.Clear(); // IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder - var kits = _publishedContentService.GetAllContentSources(); - return onStartup ? _contentStore.SetAllFastSortedLocked(kits, true) : _contentStore.SetAllLocked(kits); + + try + { + var kits = _publishedContentService.GetAllContentSources(); + return onStartup ? _contentStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) : _contentStore.SetAllLocked(kits, _config.KitBatchSize, true); + } + catch (ThreadAbortException tae) + { + // Caught a ThreadAbortException, most likely from a database timeout. + // If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment). + _logger.LogWarning(tae, tae.Message); + } + return false; } } @@ -385,8 +396,20 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache _logger.LogDebug("Loading media from database..."); // IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder - var kits = _publishedContentService.GetAllMediaSources(); - return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, true) : _mediaStore.SetAllLocked(kits); + + try + { + + var kits = _publishedContentService.GetAllMediaSources(); + return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) : _mediaStore.SetAllLocked(kits, _config.KitBatchSize, true); + } + catch (ThreadAbortException tae) + { + // Caught a ThreadAbortException, most likely from a database timeout. + // If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment). + _logger.LogWarning(tae, tae.Message); + } + return false; } } @@ -434,7 +457,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache return false; } - return onStartup ? store.SetAllFastSortedLocked(kits, false) : store.SetAllLocked(kits); + return onStartup ? store.SetAllFastSortedLocked(kits, _config.KitBatchSize, false) : store.SetAllLocked(kits, _config.KitBatchSize, false); } private void LockAndLoadDomains() From c07ffb68fc987a758160a5aa44128dbd3c00d3cb Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 19 Apr 2022 15:06:10 +0200 Subject: [PATCH 62/67] v9: Implement telemetry levels (#12267) * Add initial classes * Add TelemetryProviders * Add new NodeCountService.cs and NodeTelemetryProvider * Add data contract attribute to UsageInformation Otherwise it wont serialize correctly * Implement more providers * Fix builders and propertyEditorTelemetry * Add MediaTelemetryProvider * Add MediaTelemetryProvider * Fix doubling of media telemetry * Move contentCount from NodeCountTelemetryProvider and move to ContentTelemetryProvider * Revert ContentTelemetryProvider changes * Add detailed information to TelemetryService * Add integration tests * Add more tests and todos for tests * Fix stylecop warnings * Use yield return instead of instantiating local list * Implement Macro test * Inject interface instead of implementation in TelemetryService * Fix TelemetryServiceTests.cs * Implement media tests * Implement propertyTypeTests * Implement constants instead of hardcoded strings * Add SystemInformationTelemetryProvider * Use SystemInformationTableDataProvider in UserDataService * Implement more properties * Add UsageInformation * Replace UserDataService with SystemInformationTelemetryProvider * Undo changes to UserDataService and obsolete it * Remove ISystemInformationTableDataProvider * Register SystemInformationTelemetryProvider as telemetry provider * Use constants for telemetry names * Make UserDataServiceTests test SystemInformationTelemetryProvider instead * Update UserDataServiceTests to cover new data * Add unit tests * Add integration test testing expected data is returned * Implement Analytics dashboard * Improve assertion message * Add text and styling to analyticspage * Rename consent to analytic * implement save button for consent level * Implement save button * Fix system information test * Add TelemetryResource * Move telemetry providers to infrastructure * Add database provider to system information * Set startvalue for slider * Fix unit tests * Implement MetricsConsentService using KeyValueService * Return void hen setting the telemetry level * fix startposition when not reloading * Add a couple tests * Update src/Umbraco.Core/Services/MetricsConsentService.cs * Rename ConsentLevel.cs * Use direct Enum instead of parsing * rename consent resource * add lazy database * refactor slider * Implement ng-if and propers pips * Make classes internal * Fix slider not loading when navigating to tab * Add telemetry level check to TelemetryService.cs * Add Consent for analytics text * Fix build errors for unit tests * Fix TelemetryServiceTests * revert package-lock.json * Fix integration test * Update slider * Update TelemetryService.cs * Apply suggestions from code review Co-authored-by: Mole Co-authored-by: Nikolaj Geisle Co-authored-by: nikolajlauridsen --- .../Configuration/Models/GlobalSettings.cs | 2 +- src/Umbraco.Core/Constants-System.cs | 4 +- src/Umbraco.Core/Constants-Telemetry.cs | 32 ++ .../Dashboards/AnalyticsDashboard.cs | 15 + .../DependencyInjection/UmbracoBuilder.cs | 5 +- src/Umbraco.Core/Models/TelemetryLevel.cs | 12 + src/Umbraco.Core/Models/TelemetryResource.cs | 11 + src/Umbraco.Core/Models/UsageInformation.cs | 20 + .../Services/IExamineIndexCountService.cs | 7 + .../Services/IMetricsConsentService.cs | 11 + .../Services/INodeCountService.cs | 10 + .../Services/IUsageInformationService.cs | 10 + .../Services/MetricsConsentService.cs | 34 ++ src/Umbraco.Core/Services/UserDataService.cs | 5 +- .../Telemetry/Models/TelemetryReportData.cs | 4 + .../Telemetry/TelemetryService.cs | 28 +- .../UmbracoBuilder.CoreServices.cs | 2 + .../UmbracoBuilder.Services.cs | 4 + .../UmbracoBuilder.TelemetryProviders.cs | 25 ++ .../Implement/ExamineIndexCountService.cs | 21 ++ .../Services/Implement/NodeCountService.cs | 51 +++ .../Interfaces/IDetailedTelemetryProvider.cs | 10 + .../Providers/ContentTelemetryProvider.cs | 23 ++ .../Providers/DomainTelemetryProvider.cs | 22 ++ .../Providers/ExamineTelemetryProvider.cs | 21 ++ .../Providers/LanguagesTelemetryProvider.cs | 25 ++ .../Providers/MacroTelemetryProvider.cs | 25 ++ .../Providers/MediaTelemetryProvider.cs | 20 + .../Providers/NodeCountTelemetryProvider.cs | 24 ++ .../PropertyEditorTelemetryProvider.cs | 28 ++ .../SystemInformationTelemetryProvider.cs | 106 ++++++ .../Providers/UserTelemetryProvider.cs | 28 ++ .../Services/UsageInformationService.cs | 36 ++ .../Controllers/AnalyticsController.cs | 36 ++ .../Controllers/BackOfficeServerVariables.cs | 6 +- .../UmbracoBuilderExtensions.cs | 1 + .../components/umbrangeslider.directive.js | 12 +- .../common/mocks/umbraco.servervariables.js | 3 +- .../src/common/resources/analytic.resource.js | 57 +++ .../common/services/localization.service.js | 4 + .../settings/analytics.controller.js | 98 +++++ .../views/dashboard/settings/analytics.html | 59 +++ .../umbraco/config/lang/en_us.xml | 5 + .../HelpPanel/systemInformation.ts | 2 +- .../Services/MetricsConsentServiceTest.cs | 40 ++ .../Services/TelemetryProviderTests.cs | 351 ++++++++++++++++++ .../Telemetry/TelemetryServiceTests.cs | 77 ++++ .../Services/UserDataServiceTests.cs | 51 ++- ...SystemInformationTelemetryProviderTests.cs | 121 ++++++ .../Telemetry/TelemetryServiceTests.cs | 19 +- 50 files changed, 1597 insertions(+), 26 deletions(-) create mode 100644 src/Umbraco.Core/Constants-Telemetry.cs create mode 100644 src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs create mode 100644 src/Umbraco.Core/Models/TelemetryLevel.cs create mode 100644 src/Umbraco.Core/Models/TelemetryResource.cs create mode 100644 src/Umbraco.Core/Models/UsageInformation.cs create mode 100644 src/Umbraco.Core/Services/IExamineIndexCountService.cs create mode 100644 src/Umbraco.Core/Services/IMetricsConsentService.cs create mode 100644 src/Umbraco.Core/Services/INodeCountService.cs create mode 100644 src/Umbraco.Core/Services/IUsageInformationService.cs create mode 100644 src/Umbraco.Core/Services/MetricsConsentService.cs create mode 100644 src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.TelemetryProviders.cs create mode 100644 src/Umbraco.Infrastructure/Services/Implement/ExamineIndexCountService.cs create mode 100644 src/Umbraco.Infrastructure/Services/Implement/NodeCountService.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Interfaces/IDetailedTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/ContentTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/DomainTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/ExamineTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/LanguagesTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/MacroTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/MediaTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/NodeCountTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/PropertyEditorTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/UserTelemetryProvider.cs create mode 100644 src/Umbraco.Infrastructure/Telemetry/Services/UsageInformationService.cs create mode 100644 src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs create mode 100644 src/Umbraco.Web.UI.Client/src/common/resources/analytic.resource.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.html create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MetricsConsentServiceTest.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index 5e42d3b8be..2d5508f106 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Core.Configuration.Models internal const bool StaticHideTopLevelNodeFromPath = true; internal const bool StaticUseHttps = false; internal const int StaticVersionCheckPeriod = 7; - internal const string StaticUmbracoPath = "~/umbraco"; + internal const string StaticUmbracoPath = Constants.System.DefaultUmbracoPath; internal const string StaticIconsPath = "~/umbraco/assets/icons"; internal const string StaticUmbracoCssPath = "~/css"; internal const string StaticUmbracoScriptsPath = "~/scripts"; diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index ddff380c08..657b4b6f2d 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -59,7 +59,9 @@ public const string RecycleBinMediaPathPrefix = "-1,-21,"; public const int DefaultLabelDataTypeId = -92; - public const string UmbracoConnectionName = "umbracoDbDSN"; + public const string UmbracoConnectionName = "umbracoDbDSN"; + + public const string DefaultUmbracoPath = "~/umbraco"; } } } diff --git a/src/Umbraco.Core/Constants-Telemetry.cs b/src/Umbraco.Core/Constants-Telemetry.cs new file mode 100644 index 0000000000..6fc474d9ae --- /dev/null +++ b/src/Umbraco.Core/Constants-Telemetry.cs @@ -0,0 +1,32 @@ +namespace Umbraco.Cms.Core +{ + public static partial class Constants + { + public static class Telemetry + { + + public static string RootCount = "RootCount"; + public static string DomainCount = "DomainCount"; + public static string ExamineIndexCount = "ExamineIndexCount"; + public static string LanguageCount = "LanguageCount"; + public static string MacroCount = "MacroCount"; + public static string MediaCount = "MediaCount"; + public static string MemberCount = "MemberCount"; + public static string TemplateCount = "TemplateCount"; + public static string ContentCount = "ContentCount"; + public static string DocumentTypeCount = "DocumentTypeCount"; + public static string Properties = "Properties"; + public static string UserCount = "UserCount"; + public static string UserGroupCount = "UserGroupCount"; + public static string ServerOs = "ServerOs"; + public static string ServerFramework = "ServerFramework"; + public static string OsLanguage = "OsLanguage"; + public static string WebServer = "WebServer"; + public static string ModelsBuilderMode = "ModelBuilderMode"; + public static string CustomUmbracoPath = "CustomUmbracoPath"; + public static string AspEnvironment = "AspEnvironment"; + public static string IsDebug = "IsDebug"; + public static string DatabaseProvider = "DatabaseProvider"; + } + } +} diff --git a/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs b/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs new file mode 100644 index 0000000000..1be6e045d0 --- /dev/null +++ b/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs @@ -0,0 +1,15 @@ +using System; + +namespace Umbraco.Cms.Core.Dashboards +{ + public class AnalyticsDashboard : IDashboard + { + public string Alias => "settingsAnalytics"; + + public string[] Sections => new [] { "settings" }; + + public string View => "views/dashboard/settings/analytics.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 235dc71252..abbadcc5e8 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -25,7 +25,7 @@ using Umbraco.Cms.Core.Install; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Mail; -using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PropertyEditors; @@ -39,7 +39,6 @@ using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Telemetry; using Umbraco.Cms.Core.Templates; using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.DependencyInjection @@ -182,7 +181,7 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddSingleton(); Services.AddUnique(); - Services.AddUnique(); + Services.AddSingleton(); // will be injected in controllers when needed to invoke rest endpoints on Our Services.AddUnique(); diff --git a/src/Umbraco.Core/Models/TelemetryLevel.cs b/src/Umbraco.Core/Models/TelemetryLevel.cs new file mode 100644 index 0000000000..26a714b385 --- /dev/null +++ b/src/Umbraco.Core/Models/TelemetryLevel.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Models +{ + [DataContract] + public enum TelemetryLevel + { + Minimal, + Basic, + Detailed, + } +} diff --git a/src/Umbraco.Core/Models/TelemetryResource.cs b/src/Umbraco.Core/Models/TelemetryResource.cs new file mode 100644 index 0000000000..401e07848f --- /dev/null +++ b/src/Umbraco.Core/Models/TelemetryResource.cs @@ -0,0 +1,11 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Models +{ + [DataContract] + public class TelemetryResource + { + [DataMember] + public TelemetryLevel TelemetryLevel { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/UsageInformation.cs b/src/Umbraco.Core/Models/UsageInformation.cs new file mode 100644 index 0000000000..e2bedd6f0f --- /dev/null +++ b/src/Umbraco.Core/Models/UsageInformation.cs @@ -0,0 +1,20 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Models +{ + [DataContract] + public class UsageInformation + { + [DataMember(Name = "name")] + public string Name { get; } + + [DataMember(Name = "data")] + public object Data { get; } + + public UsageInformation(string name, object data) + { + Name = name; + Data = data; + } + } +} diff --git a/src/Umbraco.Core/Services/IExamineIndexCountService.cs b/src/Umbraco.Core/Services/IExamineIndexCountService.cs new file mode 100644 index 0000000000..05c5f7d554 --- /dev/null +++ b/src/Umbraco.Core/Services/IExamineIndexCountService.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Cms.Core.Services +{ + public interface IExamineIndexCountService + { + public int GetCount(); + } +} diff --git a/src/Umbraco.Core/Services/IMetricsConsentService.cs b/src/Umbraco.Core/Services/IMetricsConsentService.cs new file mode 100644 index 0000000000..e55cfd71d0 --- /dev/null +++ b/src/Umbraco.Core/Services/IMetricsConsentService.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + public interface IMetricsConsentService + { + TelemetryLevel GetConsentLevel(); + + void SetConsentLevel(TelemetryLevel telemetryLevel); + } +} diff --git a/src/Umbraco.Core/Services/INodeCountService.cs b/src/Umbraco.Core/Services/INodeCountService.cs new file mode 100644 index 0000000000..50d91c1512 --- /dev/null +++ b/src/Umbraco.Core/Services/INodeCountService.cs @@ -0,0 +1,10 @@ +using System; + +namespace Umbraco.Cms.Core.Services +{ + public interface INodeCountService + { + int GetNodeCount(Guid nodeType); + int GetMediaCount(); + } +} diff --git a/src/Umbraco.Core/Services/IUsageInformationService.cs b/src/Umbraco.Core/Services/IUsageInformationService.cs new file mode 100644 index 0000000000..fbc988d6b4 --- /dev/null +++ b/src/Umbraco.Core/Services/IUsageInformationService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + public interface IUsageInformationService + { + IEnumerable GetDetailed(); + } +} diff --git a/src/Umbraco.Core/Services/MetricsConsentService.cs b/src/Umbraco.Core/Services/MetricsConsentService.cs new file mode 100644 index 0000000000..3e93a34d8a --- /dev/null +++ b/src/Umbraco.Core/Services/MetricsConsentService.cs @@ -0,0 +1,34 @@ +using System; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + public class MetricsConsentService : IMetricsConsentService + { + internal const string Key = "UmbracoAnalyticsLevel"; + + private readonly IKeyValueService _keyValueService; + + public MetricsConsentService(IKeyValueService keyValueService) + { + _keyValueService = keyValueService; + } + + public TelemetryLevel GetConsentLevel() + { + var analyticsLevelString = _keyValueService.GetValue(Key); + + if (analyticsLevelString is null || Enum.TryParse(analyticsLevelString, out TelemetryLevel analyticsLevel) is false) + { + return TelemetryLevel.Basic; + } + + return analyticsLevel; + } + + public void SetConsentLevel(TelemetryLevel telemetryLevel) + { + _keyValueService.SetValue(Key, telemetryLevel.ToString()); + } + } +} diff --git a/src/Umbraco.Core/Services/UserDataService.cs b/src/Umbraco.Core/Services/UserDataService.cs index 490b5af6a8..a3c6bd11b4 100644 --- a/src/Umbraco.Core/Services/UserDataService.cs +++ b/src/Umbraco.Core/Services/UserDataService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -9,11 +10,13 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services { + [Obsolete("Use the IUserDataService interface instead")] public class UserDataService : IUserDataService { private readonly IUmbracoVersion _version; private readonly ILocalizationService _localizationService; + public UserDataService(IUmbracoVersion version, ILocalizationService localizationService) { _version = version; diff --git a/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs b/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs index d19e24695b..3c88147e8b 100644 --- a/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs +++ b/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.Serialization; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Telemetry.Models { @@ -30,5 +31,8 @@ namespace Umbraco.Cms.Core.Telemetry.Models /// [DataMember(Name = "packages")] public IEnumerable Packages { get; set; } + + [DataMember(Name = "detailed")] + public IEnumerable Detailed { get; set; } } } diff --git a/src/Umbraco.Core/Telemetry/TelemetryService.cs b/src/Umbraco.Core/Telemetry/TelemetryService.cs index d5a3acac98..e854e62180 100644 --- a/src/Umbraco.Core/Telemetry/TelemetryService.cs +++ b/src/Umbraco.Core/Telemetry/TelemetryService.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Telemetry.Models; using Umbraco.Extensions; @@ -13,6 +15,8 @@ namespace Umbraco.Cms.Core.Telemetry private readonly IManifestParser _manifestParser; private readonly IUmbracoVersion _umbracoVersion; private readonly ISiteIdentifierService _siteIdentifierService; + private readonly IUsageInformationService _usageInformationService; + private readonly IMetricsConsentService _metricsConsentService; /// /// Initializes a new instance of the class. @@ -20,11 +24,15 @@ namespace Umbraco.Cms.Core.Telemetry public TelemetryService( IManifestParser manifestParser, IUmbracoVersion umbracoVersion, - ISiteIdentifierService siteIdentifierService) + ISiteIdentifierService siteIdentifierService, + IUsageInformationService usageInformationService, + IMetricsConsentService metricsConsentService) { _manifestParser = manifestParser; _umbracoVersion = umbracoVersion; _siteIdentifierService = siteIdentifierService; + _usageInformationService = usageInformationService; + _metricsConsentService = metricsConsentService; } /// @@ -39,14 +47,30 @@ namespace Umbraco.Cms.Core.Telemetry telemetryReportData = new TelemetryReportData { Id = telemetryId, - Version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(), + Version = GetVersion(), Packages = GetPackageTelemetry(), + Detailed = _usageInformationService.GetDetailed(), }; return true; } + private string GetVersion() + { + if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal) + { + return null; + } + + return _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(); + } + private IEnumerable GetPackageTelemetry() { + if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal) + { + return null; + } + List packages = new(); IEnumerable manifests = _manifestParser.GetManifests(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index c4b9a6367c..68ed50ec10 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -52,6 +52,7 @@ using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Runtime; using Umbraco.Cms.Infrastructure.Search; using Umbraco.Cms.Infrastructure.Serialization; +using Umbraco.Cms.Infrastructure.Services.Implement; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DependencyInjection @@ -196,6 +197,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddSingleton(); + builder.Services.AddTransient(); builder.AddInstaller(); // Services required to run background jobs (with out the handler) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 157d49fd39..eb19adb5b1 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -20,6 +20,7 @@ using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Services.Implement; +using Umbraco.Cms.Infrastructure.Telemetry.Providers; using Umbraco.Cms.Infrastructure.Templates; using Umbraco.Extensions; @@ -93,6 +94,9 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddSingleton(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddTransient(); + builder.Services.AddUnique(); + builder.Services.AddTransient(); return builder; } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.TelemetryProviders.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.TelemetryProviders.cs new file mode 100644 index 0000000000..f0ab1ec344 --- /dev/null +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.TelemetryProviders.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; +using Umbraco.Cms.Infrastructure.Telemetry.Providers; + +namespace Umbraco.Cms.Infrastructure.DependencyInjection +{ + public static class UmbracoBuilder_TelemetryProviders + { + public static IUmbracoBuilder AddTelemetryProviders(this IUmbracoBuilder builder) + { + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + return builder; + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Implement/ExamineIndexCountService.cs b/src/Umbraco.Infrastructure/Services/Implement/ExamineIndexCountService.cs new file mode 100644 index 0000000000..eed1a4f5c2 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Implement/ExamineIndexCountService.cs @@ -0,0 +1,21 @@ +using System.Linq; +using Examine; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Infrastructure.Services.Implement +{ + public class ExamineIndexCountService : IExamineIndexCountService + { + private readonly IExamineManager _examineManager; + + public ExamineIndexCountService(IExamineManager examineManager) + { + _examineManager = examineManager; + } + + public int GetCount() + { + return _examineManager.Indexes.Count(); + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Implement/NodeCountService.cs b/src/Umbraco.Infrastructure/Services/Implement/NodeCountService.cs new file mode 100644 index 0000000000..1de813900b --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Implement/NodeCountService.cs @@ -0,0 +1,51 @@ +using System; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Services.Implement +{ + public class NodeCountService : INodeCountService + { + private readonly IScopeProvider _scopeProvider; + + public NodeCountService(IScopeProvider scopeProvider) => _scopeProvider = scopeProvider; + + public int GetNodeCount(Guid nodeType) + { + int count = 0; + using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) + { + var query = scope.Database.SqlContext.Sql() + .SelectCount() + .From() + .Where(x => x.NodeObjectType == nodeType && x.Trashed == false); + + count = scope.Database.ExecuteScalar(query); + } + + return count; + } + + public int GetMediaCount() + { + using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) + { + var query = scope.Database.SqlContext.Sql() + .SelectCount() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.ContentTypeId, right => right.NodeId) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media) + .Where(x => !x.Trashed) + .Where(x => x.Alias != Constants.Conventions.MediaTypes.Folder); + + return scope.Database.ExecuteScalar(query); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Interfaces/IDetailedTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Interfaces/IDetailedTelemetryProvider.cs new file mode 100644 index 0000000000..0936dc14a2 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Interfaces/IDetailedTelemetryProvider.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Interfaces +{ + internal interface IDetailedTelemetryProvider + { + IEnumerable GetInformation(); + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/ContentTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/ContentTelemetryProvider.cs new file mode 100644 index 0000000000..23971aec99 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/ContentTelemetryProvider.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + public class ContentTelemetryProvider : IDetailedTelemetryProvider + { + private readonly IContentService _contentService; + + public ContentTelemetryProvider(IContentService contentService) => _contentService = contentService; + + public IEnumerable GetInformation() + { + var rootNodes = _contentService.GetRootContent(); + int nodes = rootNodes.Count(); + yield return new UsageInformation(Constants.Telemetry.RootCount, nodes); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/DomainTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/DomainTelemetryProvider.cs new file mode 100644 index 0000000000..0fc845b490 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/DomainTelemetryProvider.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + public class DomainTelemetryProvider : IDetailedTelemetryProvider + { + private readonly IDomainService _domainService; + + public DomainTelemetryProvider(IDomainService domainService) => _domainService = domainService; + + public IEnumerable GetInformation() + { + var domains = _domainService.GetAll(true).Count(); + yield return new UsageInformation(Constants.Telemetry.DomainCount, domains); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/ExamineTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/ExamineTelemetryProvider.cs new file mode 100644 index 0000000000..fd64b7dce1 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/ExamineTelemetryProvider.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + public class ExamineTelemetryProvider : IDetailedTelemetryProvider + { + private readonly IExamineIndexCountService _examineIndexCountService; + + public ExamineTelemetryProvider(IExamineIndexCountService examineIndexCountService) => _examineIndexCountService = examineIndexCountService; + + public IEnumerable GetInformation() + { + var indexes = _examineIndexCountService.GetCount(); + yield return new UsageInformation(Constants.Telemetry.ExamineIndexCount, indexes); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/LanguagesTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/LanguagesTelemetryProvider.cs new file mode 100644 index 0000000000..b3b18e3488 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/LanguagesTelemetryProvider.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + public class LanguagesTelemetryProvider : IDetailedTelemetryProvider + { + private readonly ILocalizationService _localizationService; + + public LanguagesTelemetryProvider(ILocalizationService localizationService) + { + _localizationService = localizationService; + } + + public IEnumerable GetInformation() + { + int languages = _localizationService.GetAllLanguages().Count(); + yield return new UsageInformation(Constants.Telemetry.LanguageCount, languages); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/MacroTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/MacroTelemetryProvider.cs new file mode 100644 index 0000000000..ee96acd1e7 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/MacroTelemetryProvider.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + public class MacroTelemetryProvider : IDetailedTelemetryProvider + { + private readonly IMacroService _macroService; + + public MacroTelemetryProvider(IMacroService macroService) + { + _macroService = macroService; + } + + public IEnumerable GetInformation() + { + var macros = _macroService.GetAll().Count(); + yield return new UsageInformation(Constants.Telemetry.MacroCount, macros); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/MediaTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/MediaTelemetryProvider.cs new file mode 100644 index 0000000000..9e690ce461 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/MediaTelemetryProvider.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + public class MediaTelemetryProvider : IDetailedTelemetryProvider + { + private readonly INodeCountService _nodeCountService; + + public MediaTelemetryProvider(INodeCountService nodeCountService) => _nodeCountService = nodeCountService; + + public IEnumerable GetInformation() + { + yield return new UsageInformation(Constants.Telemetry.MediaCount, _nodeCountService.GetMediaCount()); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/NodeCountTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/NodeCountTelemetryProvider.cs new file mode 100644 index 0000000000..8e27c39eed --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/NodeCountTelemetryProvider.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + /// + public class NodeCountTelemetryProvider : IDetailedTelemetryProvider + { + private readonly INodeCountService _nodeCountService; + + public NodeCountTelemetryProvider(INodeCountService nodeCountService) => _nodeCountService = nodeCountService; + + public IEnumerable GetInformation() + { + yield return new UsageInformation(Constants.Telemetry.MemberCount, _nodeCountService.GetNodeCount(Constants.ObjectTypes.Member)); + yield return new UsageInformation(Constants.Telemetry.TemplateCount, _nodeCountService.GetNodeCount(Constants.ObjectTypes.Template)); + yield return new UsageInformation(Constants.Telemetry.ContentCount, _nodeCountService.GetNodeCount(Constants.ObjectTypes.Document)); + yield return new UsageInformation(Constants.Telemetry.DocumentTypeCount, _nodeCountService.GetNodeCount(Constants.ObjectTypes.DocumentType)); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/PropertyEditorTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/PropertyEditorTelemetryProvider.cs new file mode 100644 index 0000000000..b78ede7851 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/PropertyEditorTelemetryProvider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + public class PropertyEditorTelemetryProvider : IDetailedTelemetryProvider + { + private readonly IContentTypeService _contentTypeService; + + public PropertyEditorTelemetryProvider(IContentTypeService contentTypeService) => _contentTypeService = contentTypeService; + + public IEnumerable GetInformation() + { + var contentTypes = _contentTypeService.GetAll(); + var propertyTypes = new HashSet(); + foreach (IContentType contentType in contentTypes) + { + propertyTypes.UnionWith(contentType.PropertyTypes.Select(x => x.PropertyEditorAlias)); + } + + yield return new UsageInformation(Constants.Telemetry.Properties, propertyTypes); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs new file mode 100644 index 0000000000..55b69df851 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + internal class SystemInformationTelemetryProvider : IDetailedTelemetryProvider, IUserDataService + { + private readonly IUmbracoVersion _version; + private readonly ILocalizationService _localizationService; + private readonly IHostEnvironment _hostEnvironment; + private readonly Lazy _database; + private readonly GlobalSettings _globalSettings; + private readonly HostingSettings _hostingSettings; + private readonly ModelsBuilderSettings _modelsBuilderSettings; + + public SystemInformationTelemetryProvider( + IUmbracoVersion version, + ILocalizationService localizationService, + IOptions modelsBuilderSettings, + IOptions hostingSettings, + IOptions globalSettings, + IHostEnvironment hostEnvironment, + Lazy database) + { + _version = version; + _localizationService = localizationService; + _hostEnvironment = hostEnvironment; + _database = database; + _globalSettings = globalSettings.Value; + _hostingSettings = hostingSettings.Value; + _modelsBuilderSettings = modelsBuilderSettings.Value; + } + + private string CurrentWebServer => IsRunningInProcessIIS() ? "IIS" : "Kestrel"; + + private string ServerFramework => RuntimeInformation.FrameworkDescription; + + private string ModelsBuilderMode => _modelsBuilderSettings.ModelsMode.ToString(); + + private string CurrentCulture => Thread.CurrentThread.CurrentCulture.ToString(); + + private bool IsDebug => _hostingSettings.Debug; + + private bool UmbracoPathCustomized => _globalSettings.UmbracoPath != Constants.System.DefaultUmbracoPath; + + private string AspEnvironment => _hostEnvironment.EnvironmentName; + + private string ServerOs => RuntimeInformation.OSDescription; + + private string DatabaseProvider => _database.Value.DatabaseType.GetProviderName(); + + public IEnumerable GetInformation() => + new UsageInformation[] + { + new(Constants.Telemetry.ServerOs, ServerOs), + new(Constants.Telemetry.ServerFramework, ServerFramework), + new(Constants.Telemetry.OsLanguage, CurrentCulture), + new(Constants.Telemetry.WebServer, CurrentWebServer), + new(Constants.Telemetry.ModelsBuilderMode, ModelsBuilderMode), + new(Constants.Telemetry.CustomUmbracoPath, UmbracoPathCustomized), + new(Constants.Telemetry.AspEnvironment, AspEnvironment), + new(Constants.Telemetry.IsDebug, IsDebug), + new(Constants.Telemetry.DatabaseProvider, DatabaseProvider), + }; + + public IEnumerable GetUserData() => + new UserData[] + { + new("Server OS", ServerOs), + new("Server Framework", ServerFramework), + new("Default Language", _localizationService.GetDefaultLanguageIsoCode()), + new("Umbraco Version", _version.SemanticVersion.ToSemanticStringWithoutBuild()), + new("Current Culture", CurrentCulture), + new("Current UI Culture", Thread.CurrentThread.CurrentUICulture.ToString()), + new("Current Webserver", CurrentWebServer), + new("Models Builder Mode", ModelsBuilderMode), + new("Debug Mode", IsDebug.ToString()), + new("Database Provider", DatabaseProvider), + }; + + private bool IsRunningInProcessIIS() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return false; + } + + string processName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName); + return (processName.Contains("w3wp") || processName.Contains("iisexpress")); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/UserTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/UserTelemetryProvider.cs new file mode 100644 index 0000000000..66f697daef --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/UserTelemetryProvider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +{ + public class UserTelemetryProvider : IDetailedTelemetryProvider + { + private readonly IUserService _userService; + + public UserTelemetryProvider(IUserService userService) + { + _userService = userService; + } + + public IEnumerable GetInformation() + { + _userService.GetAll(1, 1, out long total); + int userGroups = _userService.GetAllUserGroups().Count(); + + yield return new UsageInformation(Constants.Telemetry.UserCount, total); + yield return new UsageInformation(Constants.Telemetry.UserGroupCount, userGroups); + } + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Services/UsageInformationService.cs b/src/Umbraco.Infrastructure/Telemetry/Services/UsageInformationService.cs new file mode 100644 index 0000000000..486a071af7 --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Services/UsageInformationService.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Core.Services +{ + internal class UsageInformationService : IUsageInformationService + { + private readonly IMetricsConsentService _metricsConsentService; + private readonly IEnumerable _providers; + + public UsageInformationService( + IMetricsConsentService metricsConsentService, + IEnumerable providers) + { + _metricsConsentService = metricsConsentService; + _providers = providers; + } + + public IEnumerable GetDetailed() + { + if (_metricsConsentService.GetConsentLevel() != TelemetryLevel.Detailed) + { + return null; + } + + var detailedUsageInformation = new List(); + foreach (IDetailedTelemetryProvider provider in _providers) + { + detailedUsageInformation.AddRange(provider.GetInformation()); + } + + return detailedUsageInformation; + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs b/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs new file mode 100644 index 0000000000..e1aac7319b --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Web.BackOffice.Controllers +{ + public class AnalyticsController : UmbracoAuthorizedJsonController + { + private readonly IMetricsConsentService _metricsConsentService; + public AnalyticsController(IMetricsConsentService metricsConsentService) + { + _metricsConsentService = metricsConsentService; + } + + public TelemetryLevel GetConsentLevel() + { + return _metricsConsentService.GetConsentLevel(); + } + + [HttpPost] + public IActionResult SetConsentLevel([FromBody]TelemetryResource telemetryResource) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + _metricsConsentService.SetConsentLevel(telemetryResource.TelemetryLevel); + return Ok(); + } + + public IEnumerable GetAllLevels() => new[] { TelemetryLevel.Minimal, TelemetryLevel.Basic, TelemetryLevel.Detailed }; + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index cbb17dce99..e2229d4222 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -385,7 +385,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { "trackedReferencesApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.GetPagedReferences(0, 1, 1, false)) - } + }, + { + "analyticsApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( + controller => controller.GetConsentLevel()) + }, } }, { diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index d2cbf5bd14..6c5126f97b 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -160,6 +160,7 @@ namespace Umbraco.Extensions )); builder.AddCoreInitialServices(); + builder.AddTelemetryProviders(); // aspnet app lifetime mgmt builder.Services.AddUnique(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js index d5c791281c..262f70f62b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js @@ -13,7 +13,7 @@ For extra details about options and events take a look here: https://refreshless
 	
- @@ -229,11 +229,13 @@ For extra details about options and events take a look here: https://refreshless var origins = slider.noUiSlider.getOrigins(); // Move tooltips into the origin element. The default stylesheet handles this. + if(tooltips && tooltips.length !== 0){ tooltips.forEach(function (tooltip, index) { - if (tooltip) { - origins[index].appendChild(tooltip); - } + if (tooltip) { + origins[index].appendChild(tooltip); + } }); + } slider.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) { @@ -283,7 +285,7 @@ For extra details about options and events take a look here: https://refreshless offset = (textIsRtl && !isVertical ? 100 : 0) + (offset / handlesInPool) - lastOffset; // Filter to unique values - var tooltipValues = poolValues[poolIndex].filter((v, i, a) => a.indexOf(v) === i); + var tooltipValues = poolValues[poolIndex].filter((v, i, a) => a.indexOf(v) === i); // Center this tooltip over the affected handles tooltips[handleNumber].innerHTML = tooltipValues.join(separator); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js index 868bc4c6d5..7abefedfab 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js @@ -20,7 +20,8 @@ Umbraco.Sys.ServerVariables = { "updateCheckApiBaseUrl": "/umbraco/Api/UpdateCheck/", "relationApiBaseUrl": "/umbraco/UmbracoApi/Relation/", "rteApiBaseUrl": "/umbraco/UmbracoApi/RichTextPreValue/", - "iconApiBaseUrl": "/umbraco/UmbracoApi/Icon/" + "iconApiBaseUrl": "/umbraco/UmbracoApi/Icon/", + "analyticsApiBaseUrl": "/umbraco/UmbracoApi/Consent/" }, umbracoSettings: { "umbracoPath": "/umbraco", diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/analytic.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/analytic.resource.js new file mode 100644 index 0000000000..fa3b203df2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/analytic.resource.js @@ -0,0 +1,57 @@ +/** + * @ngdoc service + * @name umbraco.resources.consentResource + * @function + * + * @description + * Used by the health check dashboard to get checks and send requests to fix checks. + */ +(function () { + 'use strict'; + + function analyticResource($http, umbRequestHelper) { + + function getConsentLevel () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "analyticsApiBaseUrl", + "GetConsentLevel")), + 'Server call failed for getting current consent level'); + } + + function getAllConsentLevels () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "analyticsApiBaseUrl", + "GetAllLevels")), + 'Server call failed for getting current consent level'); + } + + function saveConsentLevel (value) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "analyticsApiBaseUrl", + "SetConsentLevel"), + { telemetryLevel : value } + ), + 'Server call failed for getting current consent level'); + } + + var resource = { + getConsentLevel: getConsentLevel, + getAllConsentLevels : getAllConsentLevels, + saveConsentLevel : saveConsentLevel + }; + + return resource; + + } + + + angular.module('umbraco.resources').factory('analyticResource', analyticResource); + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index 805afae5b8..6911651af9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -200,6 +200,10 @@ angular.module('umbraco.services') * localizationService.localizeMany(["speechBubbles_templateErrorHeader", "speechBubbles_templateErrorText"]).then(function(data){ * var header = data[0]; * var message = data[1]; + * + * + * + * * notificationService.error(header, message); * }); *
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.controller.js new file mode 100644 index 0000000000..094941b63a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.controller.js @@ -0,0 +1,98 @@ +(function () { + "use strict"; + + function AnalyticsController($q, analyticResource, localizationService, notificationsService) { + + let sliderRef = null; + + var vm = this; + vm.getConsentLevel = getConsentLevel; + vm.getAllConsentLevels = getAllConsentLevels; + vm.saveConsentLevel = saveConsentLevel; + vm.sliderChange = sliderChange; + vm.setup = setup; + vm.loading = true; + vm.consentLevel = ''; + vm.consentLevels = []; + vm.val = 1; + vm.sliderOptions = + { + "start": 1, + "step": 1, + "tooltips": [false], + "range": { + "min": 1, + "max": 3 + }, + pips: { + mode: 'values', + density: 50, + values: [1, 2, 3], + format: { + to: function (value) { + return vm.consentLevels[value - 1]; + }, + from: function (value) { + return Number(value); + } + } + } + }; + $q.all( + [getConsentLevel(), + getAllConsentLevels() + ]).then( () => { + vm.startPos = calculateStartPositionForSlider(); + vm.sliderVal = vm.consentLevels[vm.startPos - 1]; + vm.sliderOptions.start = vm.startPos; + vm.val = vm.startPos; + vm.sliderOptions.pips.format = { + to: function (value) { + return vm.consentLevels[value - 1]; + }, + from: function (value) { + return Number(value); + } + } + vm.loading = false; + if (sliderRef) { + sliderRef.noUiSlider.set(vm.startPos); + } + + }); + function setup(slider) { + sliderRef = slider; + } + + function getConsentLevel() { + return analyticResource.getConsentLevel().then(function (response) { + vm.consentLevel = response; + }) + } + function getAllConsentLevels(){ + return analyticResource.getAllConsentLevels().then(function (response) { + vm.consentLevels = response; + }) + } + function saveConsentLevel(){ + analyticResource.saveConsentLevel(vm.sliderVal); + localizationService.localize("analytics_analyticsLevelSavedSuccess").then(function(value) { + notificationsService.success(value); + }); + } + + function sliderChange(values) { + const result = Number(values[0]); + vm.sliderVal = vm.consentLevels[result - 1]; + } + + function calculateStartPositionForSlider(){ + let startPosition = vm.consentLevels.indexOf(vm.consentLevel) + 1; + if(startPosition === 0){ + return 2;// Default start value + } + return startPosition; + } + } + angular.module("umbraco").controller("Umbraco.Dashboard.AnalyticsController", AnalyticsController); + })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.html new file mode 100644 index 0000000000..361b0c8bdc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.html @@ -0,0 +1,59 @@ +
+ + +

+ Consent for analytics +

+
+

In order to improve Umbraco and add new functionality based on as relevant information as possible, +
we would like to collect system- and usage information from your installation. +
We will NOT collect any personal data like content, code or users, and all data will be fully anonymous. +
+
We will on a regular basis share some of the overall learnings from these metrics. + Hopefully, you'll help us collect some valuable data.

+
+ + +
+ +

+

+ {{vm.sliderVal}} +
We'll only send an anonymous site ID to let us know that the site exists. +
+
+ {{vm.sliderVal}} +
We'll send site ID, umbraco version and packages installed +
+
+ {{vm.sliderVal}} + +
We'll send: +
- Site ID, umbraco version and packages installed +
- System information like Server OS and Webserver +
- Statistics, like number of content nodes and number of media items +
- Configuration settings, like modelsbuilder mode and used languages +
+
We might change/extend what we send on the detailed level in the future, but if so, it will be listed in + this view. + By choosing "detailed" I accept these future changes +
+

+
+
+ + +
+
+
+
+ + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index c901b1040a..bc8f38d408 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -2508,6 +2508,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Published Status Models Builder Health Check + Analytics Profiling Getting Started Install Umbraco Forms @@ -2863,4 +2864,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont item returned items returned + + Consent for analytics + Analytics level saved! + diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/HelpPanel/systemInformation.ts b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/HelpPanel/systemInformation.ts index 363244e12f..dbc9d19427 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/HelpPanel/systemInformation.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/HelpPanel/systemInformation.ts @@ -20,7 +20,7 @@ context('System Information', () => { it('Check System Info Displays', () => { openSystemInformation(); - cy.get('.table').find('tr').should('have.length', 10); + cy.get('.table').find('tr').should('have.length', 13); cy.contains('Current Culture').parent().should('contain', 'en-US'); cy.contains('Current UI Culture').parent().should('contain', 'en-US'); }); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MetricsConsentServiceTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MetricsConsentServiceTest.cs new file mode 100644 index 0000000000..70d0e80a33 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MetricsConsentServiceTest.cs @@ -0,0 +1,40 @@ +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class MetricsConsentServiceTest : UmbracoIntegrationTest + { + private IMetricsConsentService MetricsConsentService => GetRequiredService(); + + private IKeyValueService KeyValueService => GetRequiredService(); + + [Test] + [TestCase(TelemetryLevel.Minimal)] + [TestCase(TelemetryLevel.Basic)] + [TestCase(TelemetryLevel.Detailed)] + public void Can_Store_Consent(TelemetryLevel level) + { + MetricsConsentService.SetConsentLevel(level); + + var actual = MetricsConsentService.GetConsentLevel(); + Assert.IsNotNull(actual); + Assert.AreEqual(level, actual); + } + + [Test] + public void Enum_Stored_as_string() + { + MetricsConsentService.SetConsentLevel(TelemetryLevel.Detailed); + + var stringValue = KeyValueService.GetValue(Cms.Core.Services.MetricsConsentService.Key); + + Assert.AreEqual("Detailed", stringValue); + } + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs new file mode 100644 index 0000000000..e6287c80fa --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs @@ -0,0 +1,351 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Telemetry.Providers; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services +{ + /// + /// Tests covering the SectionService + /// + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class TelemetryProviderTests : UmbracoIntegrationTest + { + private IContentTypeService ContentTypeService => GetRequiredService(); + + private IFileService FileService => GetRequiredService(); + + private IDomainService DomainService => GetRequiredService(); + + private IContentService ContentService => GetRequiredService(); + + private DomainTelemetryProvider DetailedTelemetryProviders => GetRequiredService(); + + private ContentTelemetryProvider ContentTelemetryProvider => GetRequiredService(); + + private LanguagesTelemetryProvider LanguagesTelemetryProvider => GetRequiredService(); + + private UserTelemetryProvider UserTelemetryProvider => GetRequiredService(); + + private MacroTelemetryProvider MacroTelemetryProvider => GetRequiredService(); + + private MediaTelemetryProvider MediaTelemetryProvider => GetRequiredService(); + + private PropertyEditorTelemetryProvider PropertyEditorTelemetryProvider => GetRequiredService(); + + private ILocalizationService LocalizationService => GetRequiredService(); + + private IUserService UserService => GetRequiredService(); + + private IMediaService MediaService => GetRequiredService(); + + private IMediaTypeService MediaTypeService => GetRequiredService(); + + private LanguageBuilder _languageBuilder = new(); + + private UserBuilder _userBuilder = new(); + + private UserGroupBuilder _userGroupBuilder = new(); + + private ContentTypeBuilder _contentTypeBuilder = new (); + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + base.CustomTestSetup(builder); + } + + [Test] + public void Domain_Telemetry_Provider_Can_Get_Domains() + { + // Arrange + DomainService.Save(new UmbracoDomain("danish", "da-DK")); + + IEnumerable result = null; + // Act + result = DetailedTelemetryProviders.GetInformation(); + + + // Assert + Assert.AreEqual(1, result.First().Data); + } + + [Test] + public void SectionService_Can_Get_Allowed_Sections_For_User() + { + // Arrange + Template template = TemplateBuilder.CreateTextPageTemplate(); + FileService.SaveTemplate(template); + + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(defaultTemplateId: template.Id); + ContentTypeService.Save(contentType); + + Content blueprint = ContentBuilder.CreateTextpageContent(contentType, "hello", Constants.System.Root); + blueprint.SetValue("title", "blueprint 1"); + blueprint.SetValue("bodyText", "blueprint 2"); + blueprint.SetValue("keywords", "blueprint 3"); + blueprint.SetValue("description", "blueprint 4"); + + ContentService.SaveBlueprint(blueprint); + + IContent fromBlueprint = ContentService.CreateContentFromBlueprint(blueprint, "My test content"); + ContentService.Save(fromBlueprint); + + IEnumerable result = null; + // Act + result = ContentTelemetryProvider.GetInformation(); + + + // Assert + // TODO : Test multiple roots, with children + grandchildren + Assert.AreEqual(1, result.First().Data); + } + + [Test] + public void Language_Telemetry_Can_Get_Languages() + { + // Arrange + var langTwo = _languageBuilder.WithCultureInfo("da-DK").Build(); + var langThree = _languageBuilder.WithCultureInfo("se-SV").Build(); + + LocalizationService.Save(langTwo); + LocalizationService.Save(langThree); + + IEnumerable result = null; + + // Act + result = LanguagesTelemetryProvider.GetInformation(); + + // Assert + Assert.AreEqual(3, result.First().Data); + } + + [Test] + public void MacroTelemetry_Can_Get_Macros() + { + BuildMacros(); + var result = MacroTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.MacroCount); + Assert.AreEqual(3, result.Data); + } + + [Test] + public void MediaTelemetry_Can_Get_Media_In_Folders() + { + IMediaType folderType = MediaTypeService.Get(1031); + IMediaType imageMediaType = MediaTypeService.Get(1032); + + Media root = MediaBuilder.CreateMediaFolder(folderType, -1); + MediaService.Save(root); + int createdMediaCount = 10; + for (int i = 0; i < createdMediaCount; i++) + { + Media c1 = MediaBuilder.CreateMediaImage(imageMediaType, root.Id); + MediaService.Save(c1); + } + + var result = MediaTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.MediaCount); + Assert.AreEqual(createdMediaCount, result.Data); + } + + [Test] + public void MediaTelemetry_Can_Get_Media_In_Root() + { + IMediaType imageMediaType = MediaTypeService.Get(1032); + int createdMediaCount = 10; + for (int i = 0; i < createdMediaCount; i++) + { + Media c1 = MediaBuilder.CreateMediaImage(imageMediaType, -1); + MediaService.Save(c1); + } + + var result = MediaTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.MediaCount); + Assert.AreEqual(createdMediaCount, result.Data); + } + + [Test] + public void PropertyEditorTelemetry_Counts_Same_Editor_As_One() + { + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); + AddPropType("title", -88, ct2); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3", null); + AddPropType("title",-88, ct3); + ContentType ct5 = ContentTypeBuilder.CreateBasicContentType("ct5", "CT5", null); + AddPropType("blah", -88, ct5); + + ContentTypeService.Save(ct2); + ContentTypeService.Save(ct3); + ContentTypeService.Save(ct5); + + var properties = PropertyEditorTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.Properties); + var result = properties.Data as IEnumerable; + Assert.AreEqual(1, result?.Count()); + } + + [Test] + public void PropertyEditorTelemetry_Can_Get_All_PropertyTypes() + { + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); + AddPropType("title", -88, ct2); + AddPropType("title",-99, ct2); + ContentType ct5 = ContentTypeBuilder.CreateBasicContentType("ct5", "CT5", null); + AddPropType("blah", -88, ct5); + + ContentTypeService.Save(ct2); + ContentTypeService.Save(ct5); + + var properties = PropertyEditorTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.Properties); + var result = properties.Data as IEnumerable; + Assert.AreEqual(2, result?.Count()); + } + + [Test] + public void UserTelemetry_Can_Get_Default_User() + { + var result = UserTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.UserCount); + + Assert.AreEqual(1, result.Data); + } + + [Test] + public void UserTelemetry_Can_Get_With_Saved_User() + { + var user = BuildUser(0); + + UserService.Save(user); + + var result = UserTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.UserCount); + + Assert.AreEqual(2, result.Data); + } + + [Test] + public void UserTelemetry_Can_Get_More_Users() + { + int totalUsers = 99; + var users = BuildUsers(totalUsers); + UserService.Save(users); + + var result = UserTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.UserCount); + + Assert.AreEqual(totalUsers + 1, result.Data); + } + + [Test] + public void UserTelemetry_Can_Get_Default_UserGroups() + { + var result = UserTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.UserGroupCount); + Assert.AreEqual(5, result.Data); + } + + [Test] + public void UserTelemetry_Can_Get_With_Saved_UserGroups() + { + var userGroup = BuildUserGroup("testGroup"); + + UserService.Save(userGroup); + var result = UserTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.UserGroupCount); + + Assert.AreEqual(6, result.Data); + } + + [Test] + public void UserTelemetry_Can_Get_More_UserGroups() + { + var userGroups = BuildUserGroups(100); + + + foreach (var userGroup in userGroups) + { + UserService.Save(userGroup); + } + + var result = UserTelemetryProvider.GetInformation().FirstOrDefault(x => x.Name == Constants.Telemetry.UserGroupCount); + + Assert.AreEqual(105, result.Data); + } + + private User BuildUser(int count) => + _userBuilder + .WithLogin($"username{count}", "test pass") + .WithName("Test" + count) + .WithEmail($"test{count}@test.com") + .Build(); + + private IEnumerable BuildUsers(int count) + { + for (int i = 0; i < count; i++) + { + yield return BuildUser(count); + } + } + + private IUserGroup BuildUserGroup(string alias) => + _userGroupBuilder + .WithAlias(alias) + .WithName(alias) + .WithAllowedSections(new List(){"A", "B"}) + .Build(); + + private IEnumerable BuildUserGroups(int count) + { + for (int i = 0; i < count; i++) + { + yield return BuildUserGroup(i.ToString()); + } + } + + private void BuildMacros() + { + IScopeProvider scopeProvider = ScopeProvider; + using (IScope scope = scopeProvider.CreateScope()) + { + var repository = new MacroRepository((IScopeAccessor)scopeProvider, AppCaches.Disabled, Mock.Of>(), ShortStringHelper); + + repository.Save(new Macro(ShortStringHelper, "test1", "Test1", "~/views/macropartials/test1.cshtml")); + repository.Save(new Macro(ShortStringHelper, "test2", "Test2", "~/views/macropartials/test2.cshtml")); + repository.Save(new Macro(ShortStringHelper, "test3", "Tet3", "~/views/macropartials/test3.cshtml")); + scope.Complete(); + } + } + + private void AddPropType(string alias, int dataTypeId, IContentType ct) + { + var contentCollection = new PropertyTypeCollection(true) + { + new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = alias, Name = "Title", Description = string.Empty, Mandatory = false, SortOrder = 1, DataTypeId = dataTypeId }, + }; + var pg = new PropertyGroup(contentCollection) + { + Alias = "test", + Name = "test", + SortOrder = 1 + }; + ct.PropertyGroups.Add(pg); + } + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs new file mode 100644 index 0000000000..e898ba49ce --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Telemetry; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Telemetry +{ + + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class TelemetryServiceTests : UmbracoIntegrationTest + { + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.Configure(options => options.Id = Guid.NewGuid().ToString()); + } + + private ITelemetryService TelemetryService => GetRequiredService(); + private IMetricsConsentService MetricsConsentService => GetRequiredService(); + + [Test] + public void Expected_Detailed_Telemetry_Exists() + { + var expectedData = new string[] + { + Constants.Telemetry.RootCount, + Constants.Telemetry.DomainCount, + Constants.Telemetry.ExamineIndexCount, + Constants.Telemetry.LanguageCount, + Constants.Telemetry.MacroCount, + Constants.Telemetry.MediaCount, + Constants.Telemetry.MediaCount, + Constants.Telemetry.TemplateCount, + Constants.Telemetry.ContentCount, + Constants.Telemetry.DocumentTypeCount, + Constants.Telemetry.Properties, + Constants.Telemetry.UserCount, + Constants.Telemetry.UserGroupCount, + Constants.Telemetry.ServerOs, + Constants.Telemetry.ServerFramework, + Constants.Telemetry.OsLanguage, + Constants.Telemetry.WebServer, + Constants.Telemetry.ModelsBuilderMode, + Constants.Telemetry.CustomUmbracoPath, + Constants.Telemetry.AspEnvironment, + Constants.Telemetry.IsDebug, + Constants.Telemetry.DatabaseProvider, + }; + + MetricsConsentService.SetConsentLevel(TelemetryLevel.Detailed); + var success = TelemetryService.TryGetTelemetryReportData(out var telemetryReportData); + var detailed = telemetryReportData.Detailed.ToArray(); + + Assert.IsTrue(success); + Assert.Multiple(() => + { + Assert.IsNotNull(detailed); + Assert.AreEqual(expectedData.Length, detailed.Length); + + foreach (var expectedInfo in expectedData) + { + var expected = detailed.FirstOrDefault(x => x.Name == expectedInfo); + Assert.IsNotNull(expected, $"Expected {expectedInfo} to exists in the detailed list"); + } + }); + } + + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs index 7417976369..4a8c77edb8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs @@ -1,13 +1,19 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Telemetry.Providers; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services { @@ -86,10 +92,49 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services }); } - private UserDataService CreateUserDataService(string culture) + [Test] + [TestCase(ModelsMode.Nothing)] + [TestCase(ModelsMode.InMemoryAuto)] + [TestCase(ModelsMode.SourceCodeAuto)] + [TestCase(ModelsMode.SourceCodeManual)] + public void ReportsModelsModeCorrectly(ModelsMode modelsMode) + { + var userDataService = CreateUserDataService(modelsMode: modelsMode); + UserData[] userData = userDataService.GetUserData().ToArray(); + + var actual = userData.FirstOrDefault(x => x.Name == "Models Builder Mode"); + Assert.IsNotNull(actual?.Data); + Assert.AreEqual(modelsMode.ToString(), actual.Data); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ReportsDebugModeCorrectly(bool isDebug) + { + var userDataService = CreateUserDataService(isDebug: isDebug); + UserData[] userData = userDataService.GetUserData().ToArray(); + + var actual = userData.FirstOrDefault(x => x.Name == "Debug Mode"); + Assert.IsNotNull(actual?.Data); + Assert.AreEqual(isDebug.ToString(), actual.Data); + } + + private SystemInformationTelemetryProvider CreateUserDataService(string culture = "", ModelsMode modelsMode = ModelsMode.InMemoryAuto, bool isDebug = true) { var localizationService = CreateILocalizationService(culture); - return new UserDataService(_umbracoVersion, localizationService); + + var databaseMock = new Mock(); + databaseMock.Setup(x => x.DatabaseType.GetProviderName()).Returns("SQL"); + + return new SystemInformationTelemetryProvider( + _umbracoVersion, + localizationService, + Mock.Of>(x => x.Value == new ModelsBuilderSettings { ModelsMode = modelsMode }), + Mock.Of>(x => x.Value == new HostingSettings { Debug = isDebug }), + Mock.Of>(x => x.Value == new GlobalSettings()), + Mock.Of(), + new Lazy(databaseMock.Object)); } private ILocalizationService CreateILocalizationService(string culture) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs new file mode 100644 index 0000000000..8d33236ce9 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs @@ -0,0 +1,121 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Telemetry.Providers; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry +{ + [TestFixture] + public class SystemInformationTelemetryProviderTests + { + [Test] + [TestCase(ModelsMode.Nothing)] + [TestCase(ModelsMode.InMemoryAuto)] + [TestCase(ModelsMode.SourceCodeAuto)] + [TestCase(ModelsMode.SourceCodeManual)] + public void ReportsModelsModeCorrectly(ModelsMode modelsMode) + { + var telemetryProvider = CreateProvider(modelsMode: modelsMode); + UsageInformation[] usageInformation = telemetryProvider.GetInformation().ToArray(); + + var actual = usageInformation.FirstOrDefault(x => x.Name == Constants.Telemetry.ModelsBuilderMode); + Assert.IsNotNull(actual?.Data); + Assert.AreEqual(modelsMode.ToString(), actual.Data); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ReportsDebugModeCorrectly(bool isDebug) + { + var telemetryProvider = CreateProvider(isDebug: isDebug); + UsageInformation[] usageInformation = telemetryProvider.GetInformation().ToArray(); + + var actual = usageInformation.FirstOrDefault(x => x.Name == Constants.Telemetry.IsDebug); + Assert.IsNotNull(actual?.Data); + Assert.AreEqual(isDebug, actual.Data); + } + + [Test] + [TestCase("en-US")] + [TestCase("de-DE")] + [TestCase("en-NZ")] + [TestCase("sv-SE")] + public void ReportsOsLanguageCorrectly(string culture) + { + Thread.CurrentThread.CurrentCulture = new CultureInfo(culture); + var telemetryProvider = CreateProvider(); + + UsageInformation[] usageInformation = telemetryProvider.GetInformation().ToArray(); + var actual = usageInformation.FirstOrDefault(x => x.Name == Constants.Telemetry.OsLanguage); + + Assert.NotNull(actual?.Data); + Assert.AreEqual(culture, actual.Data.ToString()); + } + + [Test] + [TestCase(GlobalSettings.StaticUmbracoPath, false)] + [TestCase("mycustompath", true)] + [TestCase("~/notUmbraco", true)] + [TestCase("/umbraco", true)] + [TestCase("umbraco", true)] + public void ReportsCustomUmbracoPathCorrectly(string path, bool isCustom) + { + var telemetryProvider = CreateProvider(umbracoPath: path); + + UsageInformation[] usageInformation = telemetryProvider.GetInformation().ToArray(); + var actual = usageInformation.FirstOrDefault(x => x.Name == Constants.Telemetry.CustomUmbracoPath); + + Assert.NotNull(actual?.Data); + Assert.AreEqual(isCustom, actual.Data); + } + + [Test] + [TestCase("Development")] + [TestCase("Staging")] + [TestCase("Production")] + public void ReportsCorrectAspEnvironment(string environment) + { + var telemetryProvider = CreateProvider(environment: environment); + + UsageInformation[] usageInformation = telemetryProvider.GetInformation().ToArray(); + var actual = usageInformation.FirstOrDefault(x => x.Name == Constants.Telemetry.AspEnvironment); + + Assert.NotNull(actual?.Data); + Assert.AreEqual(environment, actual.Data); + } + + private SystemInformationTelemetryProvider CreateProvider( + ModelsMode modelsMode = ModelsMode.InMemoryAuto, + bool isDebug = true, + string umbracoPath = "", + string environment = "") + { + var hostEnvironment = new Mock(); + hostEnvironment.Setup(x => x.EnvironmentName).Returns(environment); + + var databaseMock = new Mock(); + databaseMock.Setup(x => x.DatabaseType.GetProviderName()).Returns("SQL"); + + return new SystemInformationTelemetryProvider( + Mock.Of(), + Mock.Of(), + Mock.Of>(x => x.Value == new ModelsBuilderSettings{ ModelsMode = modelsMode }), + Mock.Of>(x => x.Value == new HostingSettings { Debug = isDebug }), + Mock.Of>(x => x.Value == new GlobalSettings{ UmbracoPath = umbracoPath }), + hostEnvironment.Object, + new Lazy(databaseMock.Object)); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs index 910ca7c792..430f383646 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Semver; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Telemetry; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry @@ -20,7 +20,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry { var version = CreateUmbracoVersion(9, 3, 1); var siteIdentifierServiceMock = new Mock(); - var sut = new TelemetryService(Mock.Of(), version, siteIdentifierServiceMock.Object); + var usageInformationServiceMock = new Mock(); + var sut = new TelemetryService(Mock.Of(), version, siteIdentifierServiceMock.Object, usageInformationServiceMock.Object, Mock.Of()); Guid guid; var result = sut.TryGetTelemetryReportData(out var telemetryReportData); @@ -31,7 +32,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry public void SkipsIfCantGetOrCreateId() { var version = CreateUmbracoVersion(9, 3, 1); - var sut = new TelemetryService(Mock.Of(), version, createSiteIdentifierService(false)); + var sut = new TelemetryService(Mock.Of(), version, createSiteIdentifierService(false), Mock.Of(), Mock.Of()); var result = sut.TryGetTelemetryReportData(out var telemetry); @@ -44,7 +45,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry { var version = CreateUmbracoVersion(9, 1, 1, "-rc", "-ad2f4k2d"); - var sut = new TelemetryService(Mock.Of(), version, createSiteIdentifierService()); + var sut = new TelemetryService(Mock.Of(), version, createSiteIdentifierService(), Mock.Of(), Mock.Of()); var result = sut.TryGetTelemetryReportData(out var telemetry); @@ -65,7 +66,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry new () { PackageName = noVersionPackageName } }; var manifestParser = CreateManifestParser(manifests); - var sut = new TelemetryService(manifestParser, version, createSiteIdentifierService()); + var metricsConsentService = new Mock(); + metricsConsentService.Setup(x => x.GetConsentLevel()).Returns(TelemetryLevel.Basic); + var sut = new TelemetryService(manifestParser, version, createSiteIdentifierService(), Mock.Of(), metricsConsentService.Object); var success = sut.TryGetTelemetryReportData(out var telemetry); @@ -93,7 +96,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry new () { PackageName = "TrackingAllowed", AllowPackageTelemetry = true }, }; var manifestParser = CreateManifestParser(manifests); - var sut = new TelemetryService(manifestParser, version, createSiteIdentifierService()); + var metricsConsentService = new Mock(); + metricsConsentService.Setup(x => x.GetConsentLevel()).Returns(TelemetryLevel.Basic); + var sut = new TelemetryService(manifestParser, version, createSiteIdentifierService(), Mock.Of(), metricsConsentService.Object); var success = sut.TryGetTelemetryReportData(out var telemetry); From ce6a3d6751d8b0da1d9b6b19212803791f3f6253 Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Tue, 19 Apr 2022 15:19:25 +0200 Subject: [PATCH 63/67] Fix TelemetryServiceTests --- .../Umbraco.Core/Telemetry/TelemetryServiceTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs index 430f383646..67e046f1fa 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs @@ -45,7 +45,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry { var version = CreateUmbracoVersion(9, 1, 1, "-rc", "-ad2f4k2d"); - var sut = new TelemetryService(Mock.Of(), version, createSiteIdentifierService(), Mock.Of(), Mock.Of()); + var metricsConsentService = new Mock(); + metricsConsentService.Setup(x => x.GetConsentLevel()).Returns(TelemetryLevel.Detailed); + var sut = new TelemetryService(Mock.Of(), version, createSiteIdentifierService(), Mock.Of(), metricsConsentService.Object); var result = sut.TryGetTelemetryReportData(out var telemetry); From 7e6e9c7431359f6f002e72bea28676c1704c50eb Mon Sep 17 00:00:00 2001 From: VWA Software internet Date: Tue, 19 Apr 2022 14:04:11 +0200 Subject: [PATCH 64/67] =?UTF-8?q?Include=20the=20PluginController=20Area?= =?UTF-8?q?=20when=20searching=20for=20matching=20surface=E2=80=A6=20(#122?= =?UTF-8?q?18)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Routing/ControllerActionSearcher.cs | 22 ++++++++++++++++--- .../Routing/IControllerActionSearcher.cs | 6 +++++ .../Routing/UmbracoRouteValueTransformer.cs | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/ControllerActionSearcher.cs b/src/Umbraco.Web.Website/Routing/ControllerActionSearcher.cs index 5c758a948c..bf7c0aff59 100644 --- a/src/Umbraco.Web.Website/Routing/ControllerActionSearcher.cs +++ b/src/Umbraco.Web.Website/Routing/ControllerActionSearcher.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; @@ -31,13 +32,20 @@ namespace Umbraco.Cms.Web.Website.Routing _actionSelector = actionSelector; } + /// /// Determines if a custom controller can hijack the current route /// /// The controller type to find - public ControllerActionDescriptor Find(HttpContext httpContext, string controller, string action) + public ControllerActionDescriptor Find(HttpContext httpContext, string controller, string action) => Find(httpContext, controller, action, null); + + /// + /// Determines if a custom controller can hijack the current route + /// + /// The controller type to find + public ControllerActionDescriptor Find(HttpContext httpContext, string controller, string action, string area) { - IReadOnlyList candidates = FindControllerCandidates(httpContext, controller, action, DefaultActionName); + IReadOnlyList candidates = FindControllerCandidates(httpContext, controller, action, DefaultActionName, area); if (candidates.Count > 0) { @@ -47,6 +55,7 @@ namespace Umbraco.Cms.Web.Website.Routing return null; } + /// /// Return a list of controller candidates that match the custom controller and action names /// @@ -54,7 +63,8 @@ namespace Umbraco.Cms.Web.Website.Routing HttpContext httpContext, string customControllerName, string customActionName, - string defaultActionName) + string defaultActionName, + string area = null) { // Use aspnetcore's IActionSelector to do the finding since it uses an optimized cache lookup var routeValues = new RouteValueDictionary @@ -62,6 +72,12 @@ namespace Umbraco.Cms.Web.Website.Routing [ControllerToken] = customControllerName, [ActionToken] = customActionName, // first try to find the custom action }; + + if (area != null) + { + routeValues[AreaToken] = area; + } + var routeData = new RouteData(routeValues); var routeContext = new RouteContext(httpContext) { diff --git a/src/Umbraco.Web.Website/Routing/IControllerActionSearcher.cs b/src/Umbraco.Web.Website/Routing/IControllerActionSearcher.cs index b272b4afd3..1b50638fff 100644 --- a/src/Umbraco.Web.Website/Routing/IControllerActionSearcher.cs +++ b/src/Umbraco.Web.Website/Routing/IControllerActionSearcher.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Controllers; @@ -5,6 +6,11 @@ namespace Umbraco.Cms.Web.Website.Routing { public interface IControllerActionSearcher { + ControllerActionDescriptor Find(HttpContext httpContext, string controller, string action); + + ControllerActionDescriptor Find(HttpContext httpContext, string controller, string action, string area) + => Find(httpContext, controller, action); + } } diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 60384de752..bb00f958cf 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -240,7 +240,7 @@ namespace Umbraco.Cms.Web.Website.Routing [ActionToken] = postedInfo.ActionName }; - ControllerActionDescriptor surfaceControllerDescriptor = _controllerActionSearcher.Find(httpContext, postedInfo.ControllerName, postedInfo.ActionName); + ControllerActionDescriptor surfaceControllerDescriptor = _controllerActionSearcher.Find(httpContext, postedInfo.ControllerName, postedInfo.ActionName, postedInfo.Area); if (surfaceControllerDescriptor == null) { From 7becf76a0242178f8748b3b55f235d14266940d3 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 Apr 2022 10:56:15 +0200 Subject: [PATCH 65/67] Added notification when requires user 2fa, so implementors can use this to send emails etc. --- .../UserTwoFactorRequestedNotification.cs | 14 ++++++ .../Services/UserServiceExtensions.cs | 6 +++ .../Security/BackOfficeSignInManager.cs | 50 ++++++++++++++++++- 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs diff --git a/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs b/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs new file mode 100644 index 0000000000..ccb07c593c --- /dev/null +++ b/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs @@ -0,0 +1,14 @@ +using System; + +namespace Umbraco.Cms.Core.Notifications +{ + public class UserTwoFactorRequestedNotification : INotification + { + public UserTwoFactorRequestedNotification(Guid userKey) + { + UserKey = userKey; + } + + public Guid UserKey { get; } + } +} diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index 57d09077fc..19f1a7ac5c 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -84,5 +84,11 @@ namespace Umbraco.Extensions }); } + + public static IUser GetByKey(this IUserService userService, Guid key) + { + int id = BitConverter.ToInt32(key.ToByteArray(), 0); + return userService.GetUserById(id); + } } } diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index ec0d273b56..dc71c5f6bb 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -6,10 +6,14 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.Security; using Umbraco.Extensions; @@ -24,6 +28,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security { private readonly BackOfficeUserManager _userManager; private readonly IBackOfficeExternalLoginProviders _externalLogins; + private readonly IEventAggregator _eventAggregator; private readonly GlobalSettings _globalSettings; protected override string AuthenticationType => Constants.Security.BackOfficeAuthenticationType; @@ -43,14 +48,32 @@ namespace Umbraco.Cms.Web.BackOffice.Security IOptions globalSettings, ILogger> logger, IAuthenticationSchemeProvider schemes, - IUserConfirmation confirmation) + IUserConfirmation confirmation, + IEventAggregator eventAggregator) : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) { _userManager = userManager; _externalLogins = externalLogins; + _eventAggregator = eventAggregator; _globalSettings = globalSettings.Value; } + [Obsolete("Use ctor with all params")] + public BackOfficeSignInManager( + BackOfficeUserManager userManager, + IHttpContextAccessor contextAccessor, + IBackOfficeExternalLoginProviders externalLogins, + IUserClaimsPrincipalFactory claimsFactory, + IOptions optionsAccessor, + IOptions globalSettings, + ILogger> logger, + IAuthenticationSchemeProvider schemes, + IUserConfirmation confirmation) + : this(userManager, contextAccessor, externalLogins, claimsFactory, optionsAccessor, globalSettings, logger, schemes, confirmation, StaticServiceProvider.Instance.GetRequiredService()) + { + + } + /// /// Custom ExternalLoginSignInAsync overload for handling external sign in with auto-linking /// @@ -284,6 +307,31 @@ namespace Umbraco.Cms.Web.BackOffice.Security } } + protected override async Task SignInOrTwoFactorAsync(BackOfficeIdentityUser user, bool isPersistent, + string loginProvider = null, bool bypassTwoFactor = false) + { + var result = await base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); + + if (result.RequiresTwoFactor) + { + NotifyRequiresTwoFactor(user); + } + + return result; + } + + protected void NotifyRequiresTwoFactor(BackOfficeIdentityUser user) => Notify(user, + (currentUser) => new UserTwoFactorRequestedNotification(currentUser.Key) + ); + + private T Notify(BackOfficeIdentityUser currentUser, Func createNotification) where T : INotification + { + + var notification = createNotification(currentUser); + _eventAggregator.Publish(notification); + return notification; + } + private void LogFailedExternalLogin(ExternalLoginInfo loginInfo, BackOfficeIdentityUser user) => Logger.LogWarning("The AutoLinkOptions of the external authentication provider '{LoginProvider}' have refused the login based on the OnExternalLogin method. Affected user id: '{UserId}'", loginInfo.LoginProvider, user.Id); } From b19dab5f3a49f51cb6ed3e954b00f6ff5f897e52 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 Apr 2022 15:42:27 +0200 Subject: [PATCH 66/67] Fix merge issues --- src/JsonSchema/JsonSchema.csproj | 8 +- .../Repositories/INodeCountRepository.cs | 9 + src/Umbraco.Core/Services/NodeCountService.cs | 31 ++++ .../UmbracoBuilder.Repositories.cs | 1 + .../UmbracoBuilder.Services.cs | 2 + .../Implement/NodeCountRepository.cs | 44 +++++ .../Services/Implement/NodeCountService.cs | 51 ------ .../SystemInformationTelemetryProvider.cs | 160 +++++++++--------- .../Controllers/AuthenticationController.cs | 88 ---------- .../Trees/MemberGroupTreeController.cs | 3 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 9 +- .../.template.config/template.json | 11 +- .../.template.config/template.json | 11 +- .../Umbraco.TestData/Umbraco.TestData.csproj | 2 - .../Umbraco.Tests.Benchmarks.csproj | 2 - .../Services/TelemetryProviderTests.cs | 1 + .../Umbraco.Tests.UnitTests.csproj | 2 - 17 files changed, 175 insertions(+), 260 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs create mode 100644 src/Umbraco.Core/Services/NodeCountService.cs create mode 100644 src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NodeCountRepository.cs delete mode 100644 src/Umbraco.Infrastructure/Services/Implement/NodeCountService.cs diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj index 18c0dd0292..13edd52c81 100644 --- a/src/JsonSchema/JsonSchema.csproj +++ b/src/JsonSchema/JsonSchema.csproj @@ -1,13 +1,7 @@ - - - Exe - net6.0 - true - Exe - net5.0 + net6.0 true false diff --git a/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs b/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs new file mode 100644 index 0000000000..4ae191fa72 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface INodeCountRepository +{ + int GetNodeCount(Guid nodeType); + int GetMediaCount(); +} diff --git a/src/Umbraco.Core/Services/NodeCountService.cs b/src/Umbraco.Core/Services/NodeCountService.cs new file mode 100644 index 0000000000..7fe77a22a5 --- /dev/null +++ b/src/Umbraco.Core/Services/NodeCountService.cs @@ -0,0 +1,31 @@ +using System; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Infrastructure.Services.Implement +{ + public class NodeCountService : INodeCountService + { + private readonly INodeCountRepository _nodeCountRepository; + private readonly IScopeProvider _scopeProvider; + + public NodeCountService(INodeCountRepository nodeCountRepository, IScopeProvider scopeProvider) + { + _nodeCountRepository = nodeCountRepository; + _scopeProvider = scopeProvider; + } + + public int GetNodeCount(Guid nodeType) + { + using var scope = _scopeProvider.CreateScope(autoComplete: true); + return _nodeCountRepository.GetNodeCount(nodeType); + } + + public int GetMediaCount() + { + using var scope = _scopeProvider.CreateScope(autoComplete: true); + return _nodeCountRepository.GetMediaCount(); + } + } +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index 734fcb5661..7ae70440cc 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -65,6 +65,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddUnique(); return builder; } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 0f8490bbd1..c0bb0ca42e 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -16,6 +16,8 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Services.Implement; +using Umbraco.Cms.Infrastructure.Telemetry.Providers; using Umbraco.Cms.Infrastructure.Templates; using Umbraco.Extensions; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NodeCountRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NodeCountRepository.cs new file mode 100644 index 0000000000..fe9bbaa9d7 --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NodeCountRepository.cs @@ -0,0 +1,44 @@ +using System; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; + +public class NodeCountRepository : INodeCountRepository +{ + private readonly IScopeAccessor _scopeAccessor; + + public NodeCountRepository(IScopeAccessor scopeAccessor) => _scopeAccessor = scopeAccessor; + + /// + + public int GetNodeCount(Guid nodeType) + { + var query = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() + .SelectCount() + .From() + .Where(x => x.NodeObjectType == nodeType && x.Trashed == false); + + return _scopeAccessor.AmbientScope.Database.ExecuteScalar(query); + + } + + public int GetMediaCount() + { + var query = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() + .SelectCount() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.ContentTypeId, right => right.NodeId) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media) + .Where(x => !x.Trashed) + .Where(x => x.Alias != Constants.Conventions.MediaTypes.Folder); + + return _scopeAccessor.AmbientScope.Database.ExecuteScalar(query); + } +} diff --git a/src/Umbraco.Infrastructure/Services/Implement/NodeCountService.cs b/src/Umbraco.Infrastructure/Services/Implement/NodeCountService.cs deleted file mode 100644 index 1de813900b..0000000000 --- a/src/Umbraco.Infrastructure/Services/Implement/NodeCountService.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.Persistence.Dtos; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Infrastructure.Services.Implement -{ - public class NodeCountService : INodeCountService - { - private readonly IScopeProvider _scopeProvider; - - public NodeCountService(IScopeProvider scopeProvider) => _scopeProvider = scopeProvider; - - public int GetNodeCount(Guid nodeType) - { - int count = 0; - using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) - { - var query = scope.Database.SqlContext.Sql() - .SelectCount() - .From() - .Where(x => x.NodeObjectType == nodeType && x.Trashed == false); - - count = scope.Database.ExecuteScalar(query); - } - - return count; - } - - public int GetMediaCount() - { - using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) - { - var query = scope.Database.SqlContext.Sql() - .SelectCount() - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.ContentTypeId, right => right.NodeId) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media) - .Where(x => !x.Trashed) - .Where(x => x.Alias != Constants.Conventions.MediaTypes.Folder); - - return scope.Database.ExecuteScalar(query); - } - } - } -} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs index 55b69df851..4d01a41cd9 100644 --- a/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -15,92 +14,87 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; using Umbraco.Extensions; -namespace Umbraco.Cms.Infrastructure.Telemetry.Providers +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers; + +internal class SystemInformationTelemetryProvider : IDetailedTelemetryProvider, IUserDataService { - internal class SystemInformationTelemetryProvider : IDetailedTelemetryProvider, IUserDataService + private readonly GlobalSettings _globalSettings; + private readonly IHostEnvironment _hostEnvironment; + private readonly HostingSettings _hostingSettings; + private readonly ILocalizationService _localizationService; + private readonly ModelsBuilderSettings _modelsBuilderSettings; + private readonly IUmbracoDatabaseFactory _umbracoDatabaseFactory; + private readonly IUmbracoVersion _version; + + public SystemInformationTelemetryProvider( + IUmbracoVersion version, + ILocalizationService localizationService, + IOptionsMonitor modelsBuilderSettings, + IOptionsMonitor hostingSettings, + IOptionsMonitor globalSettings, + IHostEnvironment hostEnvironment, + IUmbracoDatabaseFactory umbracoDatabaseFactory) { - private readonly IUmbracoVersion _version; - private readonly ILocalizationService _localizationService; - private readonly IHostEnvironment _hostEnvironment; - private readonly Lazy _database; - private readonly GlobalSettings _globalSettings; - private readonly HostingSettings _hostingSettings; - private readonly ModelsBuilderSettings _modelsBuilderSettings; + _version = version; + _localizationService = localizationService; + _hostEnvironment = hostEnvironment; + _umbracoDatabaseFactory = umbracoDatabaseFactory; - public SystemInformationTelemetryProvider( - IUmbracoVersion version, - ILocalizationService localizationService, - IOptions modelsBuilderSettings, - IOptions hostingSettings, - IOptions globalSettings, - IHostEnvironment hostEnvironment, - Lazy database) + _globalSettings = globalSettings.CurrentValue; + _hostingSettings = hostingSettings.CurrentValue; + _modelsBuilderSettings = modelsBuilderSettings.CurrentValue; + } + + private string CurrentWebServer => IsRunningInProcessIIS() ? "IIS" : "Kestrel"; + + private string ServerFramework => RuntimeInformation.FrameworkDescription; + + private string ModelsBuilderMode => _modelsBuilderSettings.ModelsMode.ToString(); + + private string CurrentCulture => Thread.CurrentThread.CurrentCulture.ToString(); + + private bool IsDebug => _hostingSettings.Debug; + + private bool UmbracoPathCustomized => _globalSettings.UmbracoPath != Constants.System.DefaultUmbracoPath; + + private string AspEnvironment => _hostEnvironment.EnvironmentName; + + private string ServerOs => RuntimeInformation.OSDescription; + + private string DatabaseProvider => _umbracoDatabaseFactory.CreateDatabase().DatabaseType.GetProviderName(); + + public IEnumerable GetInformation() => + new UsageInformation[] { - _version = version; - _localizationService = localizationService; - _hostEnvironment = hostEnvironment; - _database = database; - _globalSettings = globalSettings.Value; - _hostingSettings = hostingSettings.Value; - _modelsBuilderSettings = modelsBuilderSettings.Value; + new(Constants.Telemetry.ServerOs, ServerOs), new(Constants.Telemetry.ServerFramework, ServerFramework), + new(Constants.Telemetry.OsLanguage, CurrentCulture), + new(Constants.Telemetry.WebServer, CurrentWebServer), + new(Constants.Telemetry.ModelsBuilderMode, ModelsBuilderMode), + new(Constants.Telemetry.CustomUmbracoPath, UmbracoPathCustomized), + new(Constants.Telemetry.AspEnvironment, AspEnvironment), new(Constants.Telemetry.IsDebug, IsDebug), + new(Constants.Telemetry.DatabaseProvider, DatabaseProvider) + }; + + public IEnumerable GetUserData() => + new UserData[] + { + new("Server OS", ServerOs), new("Server Framework", ServerFramework), + new("Default Language", _localizationService.GetDefaultLanguageIsoCode()), + new("Umbraco Version", _version.SemanticVersion.ToSemanticStringWithoutBuild()), + new("Current Culture", CurrentCulture), + new("Current UI Culture", Thread.CurrentThread.CurrentUICulture.ToString()), + new("Current Webserver", CurrentWebServer), new("Models Builder Mode", ModelsBuilderMode), + new("Debug Mode", IsDebug.ToString()), new("Database Provider", DatabaseProvider) + }; + + private bool IsRunningInProcessIIS() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return false; } - private string CurrentWebServer => IsRunningInProcessIIS() ? "IIS" : "Kestrel"; - - private string ServerFramework => RuntimeInformation.FrameworkDescription; - - private string ModelsBuilderMode => _modelsBuilderSettings.ModelsMode.ToString(); - - private string CurrentCulture => Thread.CurrentThread.CurrentCulture.ToString(); - - private bool IsDebug => _hostingSettings.Debug; - - private bool UmbracoPathCustomized => _globalSettings.UmbracoPath != Constants.System.DefaultUmbracoPath; - - private string AspEnvironment => _hostEnvironment.EnvironmentName; - - private string ServerOs => RuntimeInformation.OSDescription; - - private string DatabaseProvider => _database.Value.DatabaseType.GetProviderName(); - - public IEnumerable GetInformation() => - new UsageInformation[] - { - new(Constants.Telemetry.ServerOs, ServerOs), - new(Constants.Telemetry.ServerFramework, ServerFramework), - new(Constants.Telemetry.OsLanguage, CurrentCulture), - new(Constants.Telemetry.WebServer, CurrentWebServer), - new(Constants.Telemetry.ModelsBuilderMode, ModelsBuilderMode), - new(Constants.Telemetry.CustomUmbracoPath, UmbracoPathCustomized), - new(Constants.Telemetry.AspEnvironment, AspEnvironment), - new(Constants.Telemetry.IsDebug, IsDebug), - new(Constants.Telemetry.DatabaseProvider, DatabaseProvider), - }; - - public IEnumerable GetUserData() => - new UserData[] - { - new("Server OS", ServerOs), - new("Server Framework", ServerFramework), - new("Default Language", _localizationService.GetDefaultLanguageIsoCode()), - new("Umbraco Version", _version.SemanticVersion.ToSemanticStringWithoutBuild()), - new("Current Culture", CurrentCulture), - new("Current UI Culture", Thread.CurrentThread.CurrentUICulture.ToString()), - new("Current Webserver", CurrentWebServer), - new("Models Builder Mode", ModelsBuilderMode), - new("Debug Mode", IsDebug.ToString()), - new("Database Provider", DatabaseProvider), - }; - - private bool IsRunningInProcessIIS() - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return false; - } - - string processName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName); - return (processName.Contains("w3wp") || processName.Contains("iisexpress")); - } + var processName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName); + return processName.Contains("w3wp") || processName.Contains("iisexpress"); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 78b0aebb15..3a1c9fe4c7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -123,94 +123,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _twoFactorLoginService = twoFactorLoginService; } - [Obsolete("Use constructor that takes all params, scheduled for removal in V11")] - public AuthenticationController( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - IBackOfficeUserManager backOfficeUserManager, - IBackOfficeSignInManager signInManager, - IUserService userService, - ILocalizedTextService textService, - IUmbracoMapper umbracoMapper, - IOptions globalSettings, - IOptions securitySettings, - ILogger logger, - IIpResolver ipResolver, - IOptions passwordConfiguration, - IEmailSender emailSender, - ISmsSender smsSender, - IHostingEnvironment hostingEnvironment, - LinkGenerator linkGenerator, - IBackOfficeExternalLoginProviders externalAuthenticationOptions, - IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, - IHttpContextAccessor httpContextAccessor, - IOptions webRoutingSettings) - : this( - backofficeSecurityAccessor, - backOfficeUserManager, - signInManager, - userService, - textService, - umbracoMapper, - globalSettings, - securitySettings, - logger, - ipResolver, - passwordConfiguration, - emailSender, - smsSender, - hostingEnvironment, - linkGenerator, - externalAuthenticationOptions, - backOfficeTwoFactorOptions, - StaticServiceProvider.Instance.GetRequiredService(), - StaticServiceProvider.Instance.GetRequiredService>(), - StaticServiceProvider.Instance.GetRequiredService()) - { - - } - - [Obsolete("Use constructor that takes all params, scheduled for removal in V11")] - public AuthenticationController( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - IBackOfficeUserManager backOfficeUserManager, - IBackOfficeSignInManager signInManager, - IUserService userService, - ILocalizedTextService textService, - IUmbracoMapper umbracoMapper, - IOptions globalSettings, - IOptions securitySettings, - ILogger logger, - IIpResolver ipResolver, - IOptions passwordConfiguration, - IEmailSender emailSender, - ISmsSender smsSender, - IHostingEnvironment hostingEnvironment, - LinkGenerator linkGenerator, - IBackOfficeExternalLoginProviders externalAuthenticationOptions, - IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions) - : this( - backofficeSecurityAccessor, - backOfficeUserManager, - signInManager, - userService, - textService, - umbracoMapper, - globalSettings, - securitySettings, - logger, - ipResolver, - passwordConfiguration, - emailSender, - smsSender, - hostingEnvironment, - linkGenerator, - externalAuthenticationOptions, - backOfficeTwoFactorOptions, - StaticServiceProvider.Instance.GetRequiredService(), - StaticServiceProvider.Instance.GetRequiredService>()) - { - } - /// /// Returns the configuration for the backoffice user membership provider - used to configure the change password dialog /// diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs index e41f865981..c4267d4773 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs @@ -24,7 +24,8 @@ namespace Umbraco.Cms.Web.BackOffice.Trees { private readonly IMemberGroupService _memberGroupService; - [ActivatorUtilitiesConstructor] + [ + ActivatorUtilitiesConstructor] public MemberGroupTreeController( ILocalizedTextService localizedTextService, UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 6943c9e892..373de1bb89 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -25,7 +25,6 @@ - @@ -36,13 +35,7 @@ - - - - - - - + diff --git a/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json index d0bbc2b925..0d2c7055fb 100644 --- a/templates/UmbracoPackage/.template.config/template.json +++ b/templates/UmbracoPackage/.template.config/template.json @@ -27,26 +27,21 @@ "type": "parameter", "datatype": "choice", "choices": [ - { - "displayName": ".NET 5.0", - "description": "Target net5.0", - "choice": "net5.0" - }, { "displayName": ".NET 6.0", "description": "Target net6.0", "choice": "net6.0" } ], - "defaultValue": "net5.0", - "replaces": "net5.0" + "defaultValue": "net6.0", + "replaces": "net6.0" }, "UmbracoVersion": { "displayName": "Umbraco version", "description": "The version of Umbraco.Cms to add as PackageReference.", "type": "parameter", "datatype": "string", - "defaultValue": "9.5.0-rc", + "defaultValue": "10.0.0-rc", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, "namespaceReplacer": { diff --git a/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json index be82ef9b5f..29d464a50d 100644 --- a/templates/UmbracoProject/.template.config/template.json +++ b/templates/UmbracoProject/.template.config/template.json @@ -25,26 +25,21 @@ "type": "parameter", "datatype": "choice", "choices": [ - { - "displayName": ".NET 5.0", - "description": "Target net5.0", - "choice": "net5.0" - }, { "displayName": ".NET 6.0", "description": "Target net6.0", "choice": "net6.0" } ], - "defaultValue": "net5.0", - "replaces": "net5.0" + "defaultValue": "net6.0", + "replaces": "net6.0" }, "UmbracoVersion": { "displayName": "Umbraco version", "description": "The version of Umbraco.Cms to add as PackageReference.", "type": "parameter", "datatype": "string", - "defaultValue": "9.5.0-rc", + "defaultValue": "10.0.0-rc", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, "UseHttpsRedirect": { diff --git a/tests/Umbraco.TestData/Umbraco.TestData.csproj b/tests/Umbraco.TestData/Umbraco.TestData.csproj index a077c6e74b..6343a92def 100644 --- a/tests/Umbraco.TestData/Umbraco.TestData.csproj +++ b/tests/Umbraco.TestData/Umbraco.TestData.csproj @@ -4,8 +4,6 @@ false Umbraco.TestData net6.0 - net5.0 - false diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index a5e91ad1dc..4440943322 100644 --- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -1,8 +1,6 @@ net6.0 - Exe - net5.0 Exe false false diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs index e6287c80fa..54392df0e4 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs @@ -15,6 +15,7 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Infrastructure.Telemetry.Providers; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index ecf6a98f99..64ae376541 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -3,8 +3,6 @@ Exe net6.0 - false - net5.0 Umbraco.Cms.Tests.UnitTests false From 2f1cb4b20a6255cc555bee500f944d2d5e7925a2 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 Apr 2022 15:54:09 +0200 Subject: [PATCH 67/67] Fix build/tests + cleanup duplicate dependencies --- src/JsonSchema/JsonSchema.csproj | 10 ---------- .../Umbraco.Core/Services/UserDataServiceTests.cs | 9 +++++---- .../SystemInformationTelemetryProviderTests.cs | 9 +++++---- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj index 13edd52c81..3cd8def105 100644 --- a/src/JsonSchema/JsonSchema.csproj +++ b/src/JsonSchema/JsonSchema.csproj @@ -6,20 +6,10 @@ false - - - - - - - - - - diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs index 4a8c77edb8..3c638fb2ef 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs @@ -6,6 +6,7 @@ using System.Threading; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Moq; +using NPoco; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; @@ -130,11 +131,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services return new SystemInformationTelemetryProvider( _umbracoVersion, localizationService, - Mock.Of>(x => x.Value == new ModelsBuilderSettings { ModelsMode = modelsMode }), - Mock.Of>(x => x.Value == new HostingSettings { Debug = isDebug }), - Mock.Of>(x => x.Value == new GlobalSettings()), + Mock.Of>(x => x.CurrentValue == new ModelsBuilderSettings { ModelsMode = modelsMode }), + Mock.Of>(x => x.CurrentValue == new HostingSettings { Debug = isDebug }), + Mock.Of>(x => x.CurrentValue == new GlobalSettings()), Mock.Of(), - new Lazy(databaseMock.Object)); + Mock.Of(x=>x.CreateDatabase() == Mock.Of(y=>y.DatabaseType == DatabaseType.SQLite))); } private ILocalizationService CreateILocalizationService(string culture) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs index 8d33236ce9..42aae01281 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs @@ -5,6 +5,7 @@ using System.Threading; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Moq; +using NPoco; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; @@ -111,11 +112,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry return new SystemInformationTelemetryProvider( Mock.Of(), Mock.Of(), - Mock.Of>(x => x.Value == new ModelsBuilderSettings{ ModelsMode = modelsMode }), - Mock.Of>(x => x.Value == new HostingSettings { Debug = isDebug }), - Mock.Of>(x => x.Value == new GlobalSettings{ UmbracoPath = umbracoPath }), + Mock.Of>(x => x.CurrentValue == new ModelsBuilderSettings{ ModelsMode = modelsMode }), + Mock.Of>(x => x.CurrentValue == new HostingSettings { Debug = isDebug }), + Mock.Of>(x => x.CurrentValue == new GlobalSettings{ UmbracoPath = umbracoPath }), hostEnvironment.Object, - new Lazy(databaseMock.Object)); + Mock.Of(x=>x.CreateDatabase() == Mock.Of(y=>y.DatabaseType == DatabaseType.SQLite))); } } }