diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d67ad0fe28..4cb593a39b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -72,10 +72,10 @@ Great question! The short version goes like this: ![Clone the fork](img/clonefork.png) - * **Switch to the correct branch** - switch to the `v8/contrib` branch + * **Switch to the correct branch** - switch to the `v9/contrib` branch * **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md) * **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions) - * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/contrib`, create a new branch first. + * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v9/contrib`, create a new branch first. * **Push** - great, now you can push the changes up to your fork on GitHub * **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here](https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go. @@ -173,7 +173,7 @@ To find the general areas for something you're looking to fix or improve, have a ### Which branch should I target for my contributions? -We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/contrib`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'. +We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v9/contrib`. If you are working on v9, this is the branch you should be targetting. For v8 contributions, please target 'v8/contrib' Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise. @@ -199,10 +199,10 @@ Then when you want to get the changes from the main repository: ``` git fetch upstream -git rebase upstream/v8/contrib +git rebase upstream/v9/contrib ``` -In this command we're syncing with the `v8/contrib` branch, but you can of course choose another one if needed. +In this command we're syncing with the `v9/contrib` branch, but you can of course choose another one if needed. (More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated)) diff --git a/.github/workflows/codeql-config.yml b/.github/config/codeql-config.yml similarity index 100% rename from .github/workflows/codeql-config.yml rename to .github/config/codeql-config.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3d08bf3a4b..ee912262d7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -56,7 +56,7 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 with: - config-file: ./.github/workflows/codeql-config.yml + config-file: ./.github/config/codeql-config.yml # This job is to prevent the workflow status from showing as failed when all other jobs are skipped - See https://github.community/t/workflow-is-failing-if-no-job-can-be-ran-due-to-condition/16873 always_job: diff --git a/.vscode/launch.json b/.vscode/launch.json index 1c7f8b11d7..ab97269d1f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,6 +14,7 @@ "args": [], "cwd": "${workspaceFolder}/src/Umbraco.Web.UI", "stopAtEntry": false, + "requireExactSource": false, // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser "serverReadyAction": { "action": "openExternally", diff --git a/NuGet.Config b/NuGet.Config deleted file mode 100644 index a0d5bc0481..0000000000 --- a/NuGet.Config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets index d271653e44..70081e0677 100644 --- a/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets +++ b/build/NuSpecs/buildTransitive/Umbraco.Cms.StaticAssets.targets @@ -6,6 +6,16 @@ umbraco + + $(DefaultItemExcludes);App_Plugins\**; + + $(DefaultItemExcludes);umbraco\Data\**; + $(DefaultItemExcludes);umbraco\Logs\**; + $(DefaultItemExcludes);umbraco\mediacache\**; + + $(DefaultItemExcludes);wwwroot\media\**; + + @@ -21,9 +31,47 @@ SourceFiles="@(ContentWwwrootFiles)" DestinationFiles="@(ContentWwwrootFiles->'$(MSBuildProjectDirectory)\wwwroot\$(UmbracoWwwrootName)\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" /> - + + + <_AppPluginsFiles Include="App_Plugins\**" /> + + + + + + + + + <_UmbracoFolderFiles Include="umbraco\config\**" /> + <_UmbracoFolderFiles Include="umbraco\PartialViewMacros\**" /> + <_UmbracoFolderFiles Include="umbraco\UmbracoBackOffice\**" /> + <_UmbracoFolderFiles Include="umbraco\UmbracoInstall\**" /> + <_UmbracoFolderFiles Include="umbraco\UmbracoWebsite\**" /> + <_UmbracoFolderFiles Include="umbraco\UmbracoWebsite\**" /> + <_UmbracoFolderFiles Include="umbraco\Licenses\**" /> + + + <_UmbracoFolderFiles Include="umbraco\Deploy\**" /> + + + + + + diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index f7d94f1b8b..a699d05148 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -226,14 +226,14 @@ stages: inputs: command: ci workingDir: 'tests\Umbraco.Tests.AcceptanceTest' - - task: Npm@1 + - task: PowerShell@2 displayName: Run Cypress (Desktop) condition: always() continueOnError: true inputs: - workingDir: tests\Umbraco.Tests.AcceptanceTest - command: 'custom' - customCommand: 'run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"' + targetType: inline + workingDirectory: tests\Umbraco.Tests.AcceptanceTest + script: 'npm run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"' - task: PublishTestResults@2 condition: always() @@ -259,7 +259,6 @@ stages: # customCommand: 'run test -- --config="viewportHeight=812,viewportWidth=375,screenshotsFolder=cypress/artifacts/mobile/screenshots,videosFolder=cypress/artifacts/mobile/videos,videoUploadOnPasses=false"' - task: PublishPipelineArtifact@1 displayName: "Publish test artifacts" - condition: failed() inputs: targetPath: '$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/cypress/artifacts' artifact: 'Test artifacts - Windows' @@ -329,14 +328,14 @@ stages: inputs: command: ci workingDir: 'tests/Umbraco.Tests.AcceptanceTest' - - task: Npm@1 + - task: Bash@3 displayName: Run Cypress (Desktop) condition: always() continueOnError: true inputs: - workingDir: tests/Umbraco.Tests.AcceptanceTest - command: 'custom' - customCommand: 'run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"' + targetType: inline + workingDirectory: tests/Umbraco.Tests.AcceptanceTest + script: 'npm run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"' - task: PublishTestResults@2 condition: always() inputs: @@ -361,13 +360,11 @@ stages: # customCommand: 'run test -- --config="viewportHeight=812,viewportWidth=375,screenshotsFolder=cypress/artifacts/mobile/screenshots,videosFolder=cypress/artifacts/mobile/videos,videoUploadOnPasses=false"' - task: PublishPipelineArtifact@1 displayName: "Publish test artifacts" - condition: failed() inputs: targetPath: '$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/cypress/artifacts' artifact: 'Test artifacts - Linux' - task: PublishPipelineArtifact@1 displayName: "Publish run log" - condition: failed() inputs: targetPath: '$(Build.ArtifactStagingDirectory)/dotnet_run_log_linux.txt' artifact: Test Run logs - Linux @@ -541,7 +538,7 @@ stages: inputs: targetType: inline script: | - choco install docfx -y + choco install docfx --version=2.58.5 -y if ($lastexitcode -ne 0){ throw ("Error installing DocFX") } diff --git a/build/build.ps1 b/build/build.ps1 index 8a0b9dfaaf..da4733d432 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -449,12 +449,6 @@ if ($this.OnError()) { return } }) - $ubuild.DefineMethod("PrepareAzureGallery", - { - Write-Host "Prepare Azure Gallery" - $this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput) - }) - $ubuild.DefineMethod("PrepareCSharpDocs", { Write-Host "Prepare C# Documentation" @@ -528,8 +522,6 @@ if ($this.OnError()) { return } $this.PackageNuGet() if ($this.OnError()) { return } - $this.PrepareAzureGallery() - if ($this.OnError()) { return } $this.PostPackageHook() if ($this.OnError()) { return } diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index 247dda7d72..4fa6e23e21 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.1.0", + "defaultValue": "9.2.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index 397f751676..2236e809a1 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.1.0", + "defaultValue": "9.2.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/build/templates/UmbracoProject/UmbracoProject.csproj index 088cfab554..ebbaa1bc11 100644 --- a/build/templates/UmbracoProject/UmbracoProject.csproj +++ b/build/templates/UmbracoProject/UmbracoProject.csproj @@ -1,41 +1,28 @@ - + net6.0 Umbraco.Cms.Web.UI - $(DefaultItemExcludes);App_Plugins/**; - $(DefaultItemExcludes);umbraco/**; - $(DefaultItemExcludes);wwwroot/media/**; - - - - - - + + + + + + - - - - - - - - - - true diff --git a/build/templates/UmbracoProject/appsettings.json b/build/templates/UmbracoProject/appsettings.json index feb6b07d95..99e877812c 100644 --- a/build/templates/UmbracoProject/appsettings.json +++ b/build/templates/UmbracoProject/appsettings.json @@ -17,6 +17,7 @@ "CMS": { //#if (HasNoNodesViewPath || UseHttpsRedirect) "Global": { + "SanitizeTinyMce": true, //#if (!HasNoNodesViewPath && UseHttpsRedirect) "UseHttps": true //#elseif (UseHttpsRedirect) @@ -25,10 +26,16 @@ //#if (HasNoNodesViewPath) "NoNodesViewPath": "NO_NODES_VIEW_PATH_FROM_TEMPLATE" //#endif + }, //#endif "Hosting": { "Debug": false + }, + "Content": { + "ContentVersionCleanupPolicy": { + "EnableCleanup": true + } } } } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5fb6242c02..fed70283e7 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,10 +3,10 @@ - 9.1.0 - 9.1.0 - 9.1.0 - 9.1.0 + 9.2.0 + 9.2.0 + 9.2.0-rc + 9.2.0 9.0 en-US Umbraco CMS diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index 1b7c6d46fc..4af6685d8a 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -1,82 +1,120 @@ -using Umbraco.Cms.Core.Configuration.Models; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Forms.Core.Configuration; using SecuritySettings = Umbraco.Cms.Core.Configuration.Models.SecuritySettings; namespace JsonSchema { - public class AppSettings + internal class AppSettings { + /// + /// Gets or sets the Umbraco + /// public UmbracoDefinition Umbraco { get; set; } /// - /// Configuration of Umbraco CMS and packages + /// Configuration of Umbraco CMS and packages /// - public class UmbracoDefinition + internal class UmbracoDefinition { + // ReSharper disable once InconsistentNaming public CmsDefinition CMS { get; set; } + public FormsDefinition Forms { get; set; } + public DeployDefinition Deploy { get; set; } /// - /// Configurations for the Umbraco CMS + /// Configurations for the Umbraco CMS /// public class CmsDefinition { public ActiveDirectorySettings ActiveDirectory { get; set; } + public ContentSettings Content { get; set; } + public ExceptionFilterSettings ExceptionFilter { get; set; } + public ModelsBuilderSettings ModelsBuilder { get; set; } + public GlobalSettings Global { get; set; } + public HealthChecksSettings HealthChecks { get; set; } + public HostingSettings Hosting { get; set; } + public ImagingSettings Imaging { get; set; } + public IndexCreatorSettings Examine { get; set; } + public KeepAliveSettings KeepAlive { get; set; } + public LoggingSettings Logging { get; set; } + public MemberPasswordConfigurationSettings MemberPassword { get; set; } + public NuCacheSettings NuCache { get; set; } + public RequestHandlerSettings RequestHandler { get; set; } + public RuntimeSettings Runtime { get; set; } + public SecuritySettings Security { get; set; } + public TourSettings Tours { get; set; } + public TypeFinderSettings TypeFinder { get; set; } + public UserPasswordConfigurationSettings UserPassword { get; set; } + public WebRoutingSettings WebRouting { get; set; } + public UmbracoPluginSettings Plugins { get; set; } + public UnattendedSettings Unattended { get; set; } + public RichTextEditorSettings RichTextEditor { get; set; } + public RuntimeMinificationSettings RuntimeMinification { get; set; } + public BasicAuthSettings BasicAuth { get; set; } + public PackageMigrationSettings PackageMigration { get; set; } } /// - /// Configurations for the Umbraco Forms package to Umbraco CMS + /// Configurations for the Umbraco Forms package to Umbraco CMS /// public class FormsDefinition { public FormDesignSettings FormDesign { get; set; } + public PackageOptionSettings Options { get; set; } + public Umbraco.Forms.Core.Configuration.SecuritySettings Security { get; set; } + public FieldTypesDefinition FieldTypes { get; set; } /// - /// Configurations for the Umbraco Forms Field Types + /// Configurations for the Umbraco Forms Field Types /// public class FieldTypesDefinition { public DatePickerSettings DatePicker { get; set; } + public Recaptcha2Settings Recaptcha2 { get; set; } + public Recaptcha3Settings Recaptcha3 { get; set; } } } /// - /// Configurations for the Umbraco Deploy package to Umbraco CMS + /// Configurations for the Umbraco Deploy package to Umbraco CMS /// public class DeployDefinition { - } } } diff --git a/src/JsonSchema/NamespacePrefixedSchemaNameGenerator.cs b/src/JsonSchema/NamespacePrefixedSchemaNameGenerator.cs index 77b7b8c474..54ce0fdedf 100644 --- a/src/JsonSchema/NamespacePrefixedSchemaNameGenerator.cs +++ b/src/JsonSchema/NamespacePrefixedSchemaNameGenerator.cs @@ -1,13 +1,13 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NJsonSchema.Generation; namespace JsonSchema { - public class NamespacePrefixedSchemaNameGenerator : DefaultSchemaNameGenerator + internal class NamespacePrefixedSchemaNameGenerator : DefaultSchemaNameGenerator { - public override string Generate(Type type) - { - return type.Namespace.Replace(".", String.Empty) + base.Generate(type); - } + public override string Generate(Type type) => type.Namespace.Replace(".", string.Empty) + base.Generate(type); } } diff --git a/src/JsonSchema/Options.cs b/src/JsonSchema/Options.cs new file mode 100644 index 0000000000..9930210cd8 --- /dev/null +++ b/src/JsonSchema/Options.cs @@ -0,0 +1,13 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using CommandLine; + +namespace JsonSchema +{ + internal class Options + { + [Option('o', "outputFile", Required = false, HelpText = "Set path of the output file.", Default = "../../../../Umbraco.Web.UI/umbraco/config/appsettings-schema.json")] + public string OutputFile { get; set; } + } +} diff --git a/src/JsonSchema/Program.cs b/src/JsonSchema/Program.cs index cd09093020..8b02068c46 100644 --- a/src/JsonSchema/Program.cs +++ b/src/JsonSchema/Program.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.IO; using System.Threading.Tasks; @@ -5,14 +8,8 @@ using CommandLine; namespace JsonSchema { - class Program + internal class Program { - private class Options - { - [Option('o', "outputFile", Required = false, HelpText = "Set path of the output file.", Default = "../../../../Umbraco.Web.UI/umbraco/config/appsettings-schema.json")] - public string OutputFile { get; set; } - } - public static async Task Main(string[] args) { try @@ -25,7 +22,6 @@ namespace JsonSchema Console.WriteLine(e); throw; } - } private static async Task Execute(Options options) @@ -34,7 +30,7 @@ namespace JsonSchema var schema = await generator.Generate(); var path = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, options.OutputFile)); - Console.WriteLine("Path to use {0}",path); + Console.WriteLine("Path to use {0}", path); Directory.CreateDirectory(Path.GetDirectoryName(path)); Console.WriteLine("Ensured directory exists"); await File.WriteAllTextAsync(path, schema); diff --git a/src/JsonSchema/UmbracoJsonSchemaGenerator.cs b/src/JsonSchema/UmbracoJsonSchemaGenerator.cs index b6e516d0c5..e06189d3b4 100644 --- a/src/JsonSchema/UmbracoJsonSchemaGenerator.cs +++ b/src/JsonSchema/UmbracoJsonSchemaGenerator.cs @@ -1,4 +1,7 @@ -using System.Net.Http; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -7,28 +10,26 @@ using NJsonSchema.Generation; namespace JsonSchema { /// - /// Generator of the JsonSchema for AppSettings.json including A specific Umbraco version. + /// Generator of the JsonSchema for AppSettings.json including A specific Umbraco version. /// public class UmbracoJsonSchemaGenerator { + private static readonly HttpClient s_client = new (); private readonly JsonSchemaGenerator _innerGenerator; - private static readonly HttpClient s_client = new HttpClient(); /// - /// Creates a new instance of . + /// Initializes a new instance of the class. /// - /// The prefix to use for definitions generated. public UmbracoJsonSchemaGenerator() => _innerGenerator = new JsonSchemaGenerator(new UmbracoJsonSchemaGeneratorSettings()); - /// - /// Generates a json representing the JsonSchema for AppSettings.json including A specific Umbraco version.. + /// Generates a json representing the JsonSchema for AppSettings.json including A specific Umbraco version.. /// public async Task Generate() { - var umbracoSchema = GenerateUmbracoSchema(); - var officialSchema = await GetOfficialAppSettingsSchema(); + JObject umbracoSchema = GenerateUmbracoSchema(); + JObject officialSchema = await GetOfficialAppSettingsSchema(); officialSchema.Merge(umbracoSchema); @@ -37,19 +38,17 @@ namespace JsonSchema private async Task GetOfficialAppSettingsSchema() { + HttpResponseMessage response = await s_client.GetAsync("https://json.schemastore.org/appsettings.json") + .ConfigureAwait(false); - var response = await s_client.GetAsync("https://json.schemastore.org/appsettings.json"); - - - var result = await response.Content.ReadAsStringAsync(); + var result = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(result); - } private JObject GenerateUmbracoSchema() { - var schema = _innerGenerator.Generate(typeof(AppSettings)); + NJsonSchema.JsonSchema schema = _innerGenerator.Generate(typeof(AppSettings)); return JsonConvert.DeserializeObject(schema.ToJson()); } diff --git a/src/JsonSchema/UmbracoJsonSchemaGeneratorSettings.cs b/src/JsonSchema/UmbracoJsonSchemaGeneratorSettings.cs index 26af0faae2..46625aeb2c 100644 --- a/src/JsonSchema/UmbracoJsonSchemaGeneratorSettings.cs +++ b/src/JsonSchema/UmbracoJsonSchemaGeneratorSettings.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -12,15 +15,14 @@ namespace JsonSchema public class UmbracoJsonSchemaGeneratorSettings : JsonSchemaGeneratorSettings { /// - /// Creates a new instance of . + /// Initializes a new instance of the class. /// - /// The prefix to use for definitions generated. public UmbracoJsonSchemaGeneratorSettings() { AlwaysAllowAdditionalObjectProperties = true; SerializerSettings = new JsonSerializerSettings() { - ContractResolver = new WritablePropertiesOnlyResolver() + ContractResolver = new WritablePropertiesOnlyResolver() }; DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull; SchemaNameGenerator = new NamespacePrefixedSchemaNameGenerator(); diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs index ddd9f96c73..670574896e 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -18,6 +18,8 @@ namespace Umbraco.Cms.Core.Cache /// private readonly ConcurrentDictionary> _items = new ConcurrentDictionary>(); + public IEnumerable Keys => _items.Keys; + public int Count => _items.Count; /// diff --git a/src/Umbraco.Core/Collections/StackQueue.cs b/src/Umbraco.Core/Collections/StackQueue.cs index 9bf9c365f0..b760ffe646 100644 --- a/src/Umbraco.Core/Collections/StackQueue.cs +++ b/src/Umbraco.Core/Collections/StackQueue.cs @@ -3,58 +3,37 @@ namespace Umbraco.Core.Collections { /// - /// Collection that can be both a queue and a stack. + /// Collection that can be both a queue and a stack. /// /// public class StackQueue { - private readonly LinkedList _linkedList = new LinkedList(); + private readonly LinkedList _linkedList = new(); - public void Clear() - { - _linkedList.Clear(); - } + public int Count => _linkedList.Count; - public void Push(T obj) - { - _linkedList.AddFirst(obj); - } + public void Clear() => _linkedList.Clear(); - public void Enqueue(T obj) - { - _linkedList.AddFirst(obj); - } + public void Push(T obj) => _linkedList.AddFirst(obj); + + public void Enqueue(T obj) => _linkedList.AddFirst(obj); public T Pop() { - var obj = _linkedList.First.Value; + T obj = _linkedList.First.Value; _linkedList.RemoveFirst(); return obj; } public T Dequeue() { - var obj = _linkedList.Last.Value; + T obj = _linkedList.Last.Value; _linkedList.RemoveLast(); return obj; } - public T PeekStack() - { - return _linkedList.First.Value; - } + public T PeekStack() => _linkedList.First.Value; - public T PeekQueue() - { - return _linkedList.Last.Value; - } - - public int Count - { - get - { - return _linkedList.Count; - } - } + public T PeekQueue() => _linkedList.Last.Value; } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index dcca8c2adb..1caa81d80a 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -219,6 +219,9 @@ namespace Umbraco.Cms.Core.Configuration.Models [DefaultValue(StaticLoginLogoImage)] public string LoginLogoImage { get; set; } = StaticLoginLogoImage; - + /// + /// Get or sets the model representing the global content version cleanup policy + /// + public ContentVersionCleanupPolicySettings ContentVersionCleanupPolicy { get; set; } = new ContentVersionCleanupPolicySettings(); } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs b/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs new file mode 100644 index 0000000000..bd460058eb --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs @@ -0,0 +1,33 @@ +using System.ComponentModel; + +namespace Umbraco.Cms.Core.Configuration.Models +{ + /// + /// Model representing the global content version cleanup policy + /// + public class ContentVersionCleanupPolicySettings + { + private const bool StaticEnableCleanup = false; + private const int StaticKeepAllVersionsNewerThanDays = 7; + private const int StaticKeepLatestVersionPerDayForDays = 90; + + /// + /// Gets or sets a value indicating whether or not the cleanup job should be executed. + /// + [DefaultValue(StaticEnableCleanup)] + public bool EnableCleanup { get; set; } = StaticEnableCleanup; + + /// + /// Gets or sets the number of days where all historical content versions are kept. + /// + [DefaultValue(StaticKeepAllVersionsNewerThanDays)] + public int KeepAllVersionsNewerThanDays { get; set; } = StaticKeepAllVersionsNewerThanDays; + + /// + /// Gets or sets the number of days where the latest historical content version for that day are kept. + /// + [DefaultValue(StaticKeepLatestVersionPerDayForDays)] + public int KeepLatestVersionPerDayForDays { get; set; } = StaticKeepLatestVersionPerDayForDays; + + } +} diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index 7799fec5ea..c880830274 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -28,6 +28,7 @@ namespace Umbraco.Cms.Core.Configuration.Models internal const bool StaticDisableElectionForSingleServer = false; internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml"; internal const string StaticSqlWriteLockTimeOut = "00:00:05"; + internal const bool StaticSanitizeTinyMce = false; /// /// Gets or sets a value for the reserved URLs. @@ -157,6 +158,12 @@ namespace Umbraco.Cms.Core.Configuration.Models /// public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host); + /// + /// Gets a value indicating whether TinyMCE scripting sanitization should be applied + /// + [DefaultValue(StaticSanitizeTinyMce)] + public bool SanitizeTinyMce => StaticSanitizeTinyMce; + /// /// An int value representing the time in milliseconds to lock the database for a write operation /// diff --git a/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs b/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs index d5b74b38dc..d016e3547b 100644 --- a/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs @@ -11,8 +11,6 @@ namespace Umbraco.Cms.Core.Configuration.Models [UmbracoOptions(Constants.Configuration.ConfigPlugins)] public class UmbracoPluginSettings { - - /// /// Gets or sets the allowed file extensions (including the period ".") that should be accessible from the browser. /// @@ -25,7 +23,8 @@ namespace Umbraco.Cms.Core.Configuration.Models ".jpg", ".jpeg", ".gif", ".png", ".svg", // images ".eot", ".ttf", ".woff", // fonts ".xml", ".json", ".config", // configurations - ".lic" // license + ".lic", // license + ".map" // js map files }); } } diff --git a/src/Umbraco.Core/Constants-Sql.cs b/src/Umbraco.Core/Constants-Sql.cs new file mode 100644 index 0000000000..b57861c92a --- /dev/null +++ b/src/Umbraco.Core/Constants-Sql.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Cms.Core +{ + public static partial class Constants + { + public static class Sql + { + /// + /// The maximum amount of parameters that can be used in a query. + /// + /// + /// The actual limit is 2100 + /// (https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server), + /// but we want to ensure there's room for additional parameters if this value is used to create groups/batches. + /// + public const int MaxParameterCount = 2000; + } + } +} diff --git a/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs b/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs new file mode 100644 index 0000000000..3b5bf7aebb --- /dev/null +++ b/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs @@ -0,0 +1,19 @@ +using System; + +namespace Umbraco.Cms.Core.DependencyInjection +{ + /// + /// Provides access to a request scoped service provider when available for cases where + /// IHttpContextAccessor is not available. e.g. No reference to AspNetCore.Http in core. + /// + public interface IScopedServiceProvider + { + /// + /// Gets a request scoped service provider when available. + /// + /// + /// Can be null. + /// + IServiceProvider ServiceProvider { get; } + } +} diff --git a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs index 4fb335be84..21e5cc03ed 100644 --- a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs +++ b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.Serialization; using Umbraco.Extensions; +using System.Text; namespace Umbraco.Cms.Core.Exceptions { @@ -86,18 +87,28 @@ namespace Umbraco.Cms.Core.Exceptions private static string FormatMessage(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases) { - // TODO Add property group aliases to message - return addedCompositionAlias.IsNullOrWhiteSpace() - ? string.Format( - "ContentType with alias '{0}' has an invalid composition " + - "and there was a conflict on the following PropertyTypes: '{1}'. " + - "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.", - contentTypeAlias, string.Join(", ", propertyTypeAliases)) - : string.Format( - "ContentType with alias '{0}' was added as a Composition to ContentType with alias '{1}', " + - "but there was a conflict on the following PropertyTypes: '{2}'. " + - "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.", - addedCompositionAlias, contentTypeAlias, string.Join(", ", propertyTypeAliases)); + var sb = new StringBuilder(); + + if (addedCompositionAlias.IsNullOrWhiteSpace()) + { + sb.AppendFormat("Content type with alias '{0}' has an invalid composition.", contentTypeAlias); + } + else + { + sb.AppendFormat("Content type with alias '{0}' was added as a composition to content type with alias '{1}', but there was a conflict.", addedCompositionAlias, contentTypeAlias); + } + + if (propertyTypeAliases.Length > 0) + { + sb.AppendFormat(" Property types must have a unique alias across all compositions, these aliases are duplicate: {0}.", string.Join(", ", propertyTypeAliases)); + } + + if (propertyGroupAliases.Length > 0) + { + sb.AppendFormat(" Property groups with the same alias must also have the same type across all compositions, these aliases have different types: {0}.", string.Join(", ", propertyGroupAliases)); + } + + return sb.ToString(); } /// diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs index 8c4e572442..197f6f6d63 100644 --- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs @@ -1328,8 +1328,8 @@ namespace Umbraco.Extensions {"NodeTypeAlias", "NodeTypeAlias"}, {"CreateDate", "CreateDate"}, {"UpdateDate", "UpdateDate"}, - {"CreatorName", "CreatorName"}, - {"WriterName", "WriterName"}, + {"CreatorId", "CreatorId"}, + {"WriterId", "WriterId"}, {"Url", "Url"} }; diff --git a/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs index b714caf744..b4ffc40130 100644 --- a/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs @@ -12,7 +12,7 @@ namespace Umbraco.Extensions public static class PublishedModelFactoryExtensions { /// - /// Returns true if the current is an implementation of and is enabled + /// Returns true if the current is an implementation of and is enabled /// public static bool IsLiveFactoryEnabled(this IPublishedModelFactory factory) { @@ -21,8 +21,8 @@ namespace Umbraco.Extensions return liveFactory.Enabled; } - // if it's not ILivePublishedModelFactory we can't determine if it's enabled or not so return true - return true; + // if it's not ILivePublishedModelFactory we know we're not using a live factory + return false; } /// diff --git a/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs b/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs index e22bf86a59..502268ead0 100644 --- a/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs +++ b/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs @@ -226,7 +226,7 @@ namespace Umbraco.Cms.Core.Handlers foreach (var perm in perms) { var group = _userService.GetUserGroupById(perm.UserGroupId); - var assigned = string.Join(", ", perm.AssignedPermissions); + var assigned = string.Join(", ", perm?.AssignedPermissions ?? Array.Empty()); var entity = _entityService.Get(perm.EntityId); _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs index eeb291c41f..b7e5e867c4 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -20,12 +20,26 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security public abstract class BaseHttpHeaderCheck : HealthCheck { private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILocalizedTextService _textService; private readonly string _header; - private readonly string _value; private readonly string _localizedTextPrefix; private readonly bool _metaTagOptionAvailable; private static HttpClient s_httpClient; + [Obsolete("Use ctor without value.")] + protected BaseHttpHeaderCheck( + IHostingEnvironment hostingEnvironment, + ILocalizedTextService textService, + string header, + string value, + string localizedTextPrefix, + bool metaTagOptionAvailable) :this(hostingEnvironment, textService, header, localizedTextPrefix, metaTagOptionAvailable) + { + + } + + [Obsolete("Save ILocalizedTextService in a field on the super class instead of using this")] + protected ILocalizedTextService LocalizedTextService => _textService; /// /// Initializes a new instance of the class. /// @@ -33,26 +47,18 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security IHostingEnvironment hostingEnvironment, ILocalizedTextService textService, string header, - string value, string localizedTextPrefix, bool metaTagOptionAvailable) { - LocalizedTextService = textService ?? throw new ArgumentNullException(nameof(textService)); + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); _hostingEnvironment = hostingEnvironment; _header = header; - _value = value; _localizedTextPrefix = localizedTextPrefix; _metaTagOptionAvailable = metaTagOptionAvailable; } private static HttpClient HttpClient => s_httpClient ??= new HttpClient(); - - /// - /// Gets the localized text service. - /// - protected ILocalizedTextService LocalizedTextService { get; } - /// /// Gets a link to an external read more page. /// @@ -79,7 +85,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security var success = false; // Access the site home page and check for the click-jack protection header or meta tag - Uri url = _hostingEnvironment.ApplicationMainUrl; + var url = _hostingEnvironment.ApplicationMainUrl.GetLeftPart(UriPartial.Authority); try { @@ -95,12 +101,12 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security } message = success - ? LocalizedTextService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderFound") - : LocalizedTextService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderNotFound"); + ? _textService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderFound") + : _textService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderNotFound"); } catch (Exception ex) { - message = LocalizedTextService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url.ToString(), ex.Message }); + message = _textService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url.ToString(), ex.Message }); } return diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs index 957ee0b715..8586989f32 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Hosting; @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security /// Initializes a new instance of the class. /// public ClickJackingCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) - : base(hostingEnvironment, textService, "X-Frame-Options", "sameorigin", "clickJacking", true) + : base(hostingEnvironment, textService, "X-Frame-Options", "clickJacking", true) { } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs index cdd8a0493f..d5eac03038 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -53,7 +53,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security { string message; var success = false; - var url = _hostingEnvironment.ApplicationMainUrl.GetLeftPart(UriPartial.Authority);; + var url = _hostingEnvironment.ApplicationMainUrl.GetLeftPart(UriPartial.Authority); // Access the site home page and check for the headers var request = new HttpRequestMessage(HttpMethod.Head, url); @@ -65,7 +65,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security var headersToCheckFor = new List {"Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version" }; // Ignore if server header is present and it's set to cloudflare - if (allHeaders.InvariantContains("Server") && response.Headers.TryGetValues("Server", out var serverHeaders) && serverHeaders.ToString().InvariantEquals("cloudflare")) + if (allHeaders.InvariantContains("Server") && response.Headers.TryGetValues("Server", out var serverHeaders) && serverHeaders.FirstOrDefault().InvariantEquals("cloudflare")) { headersToCheckFor.Remove("Server"); } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs index b2166b88bd..7902f4e3f8 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Hosting; @@ -27,7 +27,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites. /// public HstsCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) - : base(hostingEnvironment, textService, "Strict-Transport-Security", "max-age=10886400", "hSTS", true) + : base(hostingEnvironment, textService, "Strict-Transport-Security", "hSTS", true) { } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs index 035733e4ee..78ee2c0e12 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Hosting; @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security /// Initializes a new instance of the class. /// public NoSniffCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) - : base(hostingEnvironment, textService, "X-Content-Type-Options", "nosniff", "noSniff", false) + : base(hostingEnvironment, textService, "X-Content-Type-Options", "noSniff", false) { } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs index 6c05c39f46..570ca8002d 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Hosting; @@ -27,7 +27,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites. /// public XssProtectionCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) - : base(hostingEnvironment, textService, "X-XSS-Protection", "1; mode=block", "xssProtection", true) + : base(hostingEnvironment, textService, "X-XSS-Protection", "xssProtection", true) { } diff --git a/src/Umbraco.Core/IO/DefaultViewContentProvider.cs b/src/Umbraco.Core/IO/DefaultViewContentProvider.cs new file mode 100644 index 0000000000..7c42255971 --- /dev/null +++ b/src/Umbraco.Core/IO/DefaultViewContentProvider.cs @@ -0,0 +1,62 @@ +using System.Text; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.IO +{ + public class DefaultViewContentProvider : IDefaultViewContentProvider + { + public string GetDefaultFileContent(string layoutPageAlias = null, string modelClassName = null, string modelNamespace = null, string modelNamespaceAlias = null) + { + var content = new StringBuilder(); + + if (string.IsNullOrWhiteSpace(modelNamespaceAlias)) + modelNamespaceAlias = "ContentModels"; + + // either + // @inherits Umbraco.Web.Mvc.UmbracoViewPage + // @inherits Umbraco.Web.Mvc.UmbracoViewPage + content.AppendLine("@using Umbraco.Cms.Web.Common.PublishedModels;"); + content.Append("@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage"); + if (modelClassName.IsNullOrWhiteSpace() == false) + { + content.Append("<"); + if (modelNamespace.IsNullOrWhiteSpace() == false) + { + content.Append(modelNamespaceAlias); + content.Append("."); + } + content.Append(modelClassName); + content.Append(">"); + } + content.Append("\r\n"); + + // if required, add + // @using ContentModels = ModelNamespace; + if (modelClassName.IsNullOrWhiteSpace() == false && modelNamespace.IsNullOrWhiteSpace() == false) + { + content.Append("@using "); + content.Append(modelNamespaceAlias); + content.Append(" = "); + content.Append(modelNamespace); + content.Append(";\r\n"); + } + + // either + // Layout = null; + // Layout = "layoutPage.cshtml"; + content.Append("@{\r\n\tLayout = "); + if (layoutPageAlias.IsNullOrWhiteSpace()) + { + content.Append("null"); + } + else + { + content.Append("\""); + content.Append(layoutPageAlias); + content.Append(".cshtml\""); + } + content.Append(";\r\n}"); + return content.ToString(); + } + } +} diff --git a/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs b/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs new file mode 100644 index 0000000000..8c1a775d7c --- /dev/null +++ b/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Core.IO +{ + public interface IDefaultViewContentProvider + { + string GetDefaultFileContent(string layoutPageAlias = null, string modelClassName = null, + string modelNamespace = null, string modelNamespaceAlias = null); + } +} diff --git a/src/Umbraco.Core/IO/IViewHelper.cs b/src/Umbraco.Core/IO/IViewHelper.cs new file mode 100644 index 0000000000..d53dcbf2b9 --- /dev/null +++ b/src/Umbraco.Core/IO/IViewHelper.cs @@ -0,0 +1,13 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.IO +{ + public interface IViewHelper + { + bool ViewExists(ITemplate t); + string GetFileContents(ITemplate t); + string CreateView(ITemplate t, bool overWrite = false); + string UpdateViewFile(ITemplate t, string currentAlias = null); + string ViewPath(string alias); + } +} diff --git a/src/Umbraco.Core/IO/ViewHelper.cs b/src/Umbraco.Core/IO/ViewHelper.cs index 258c4a7f64..56a2760802 100644 --- a/src/Umbraco.Core/IO/ViewHelper.cs +++ b/src/Umbraco.Core/IO/ViewHelper.cs @@ -2,26 +2,34 @@ using System; using System.IO; using System.Linq; using System.Text; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.IO { - public class ViewHelper + public class ViewHelper : IViewHelper { private readonly IFileSystem _viewFileSystem; + private readonly IDefaultViewContentProvider _defaultViewContentProvider; + [Obsolete("Use ctor with all params")] public ViewHelper(IFileSystem viewFileSystem) { - if (viewFileSystem == null) throw new ArgumentNullException(nameof(viewFileSystem)); - _viewFileSystem = viewFileSystem; + _viewFileSystem = viewFileSystem ?? throw new ArgumentNullException(nameof(viewFileSystem)); + _defaultViewContentProvider = StaticServiceProvider.Instance.GetRequiredService(); } - internal bool ViewExists(ITemplate t) + public ViewHelper(FileSystems fileSystems, IDefaultViewContentProvider defaultViewContentProvider) { - return _viewFileSystem.FileExists(ViewPath(t.Alias)); + _viewFileSystem = fileSystems.MvcViewsFileSystem ?? throw new ArgumentNullException(nameof(fileSystems)); + _defaultViewContentProvider = defaultViewContentProvider ?? throw new ArgumentNullException(nameof(defaultViewContentProvider)); } + public bool ViewExists(ITemplate t) => _viewFileSystem.FileExists(ViewPath(t.Alias)); + + public string GetFileContents(ITemplate t) { var viewContent = ""; @@ -60,58 +68,13 @@ namespace Umbraco.Cms.Core.IO return viewContent; } - public static string GetDefaultFileContent(string layoutPageAlias = null, string modelClassName = null, string modelNamespace = null, string modelNamespaceAlias = null) + [Obsolete("Inject IDefaultViewContentProvider instead")] + public static string GetDefaultFileContent(string layoutPageAlias = null, string modelClassName = null, + string modelNamespace = null, string modelNamespaceAlias = null) { - var content = new StringBuilder(); - - if (string.IsNullOrWhiteSpace(modelNamespaceAlias)) - modelNamespaceAlias = "ContentModels"; - - // either - // @inherits Umbraco.Web.Mvc.UmbracoViewPage - // @inherits Umbraco.Web.Mvc.UmbracoViewPage - content.AppendLine("@using Umbraco.Cms.Web.Common.PublishedModels;"); - content.Append("@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage"); - if (modelClassName.IsNullOrWhiteSpace() == false) - { - content.Append("<"); - if (modelNamespace.IsNullOrWhiteSpace() == false) - { - content.Append(modelNamespaceAlias); - content.Append("."); - } - content.Append(modelClassName); - content.Append(">"); - } - content.Append("\r\n"); - - // if required, add - // @using ContentModels = ModelNamespace; - if (modelClassName.IsNullOrWhiteSpace() == false && modelNamespace.IsNullOrWhiteSpace() == false) - { - content.Append("@using "); - content.Append(modelNamespaceAlias); - content.Append(" = "); - content.Append(modelNamespace); - content.Append(";\r\n"); - } - - // either - // Layout = null; - // Layout = "layoutPage.cshtml"; - content.Append("@{\r\n\tLayout = "); - if (layoutPageAlias.IsNullOrWhiteSpace()) - { - content.Append("null"); - } - else - { - content.Append("\""); - content.Append(layoutPageAlias); - content.Append(".cshtml\""); - } - content.Append(";\r\n}"); - return content.ToString(); + var viewContentProvider = StaticServiceProvider.Instance.GetRequiredService(); + return viewContentProvider.GetDefaultFileContent(layoutPageAlias, modelClassName, modelNamespace, + modelNamespaceAlias); } private string SaveTemplateToFile(ITemplate template) @@ -157,12 +120,12 @@ namespace Umbraco.Cms.Core.IO return _viewFileSystem.GetRelativePath(alias.Replace(" ", "") + ".cshtml"); } - private static string EnsureInheritedLayout(ITemplate template) + private string EnsureInheritedLayout(ITemplate template) { var design = template.Content; if (string.IsNullOrEmpty(design)) - design = GetDefaultFileContent(template.MasterTemplateAlias); + design = _defaultViewContentProvider.GetDefaultFileContent(template.MasterTemplateAlias); return design; } diff --git a/src/Umbraco.Core/Models/AuditType.cs b/src/Umbraco.Core/Models/AuditType.cs index cea6c7ccf2..b6a36be5ff 100644 --- a/src/Umbraco.Core/Models/AuditType.cs +++ b/src/Umbraco.Core/Models/AuditType.cs @@ -113,6 +113,16 @@ /// /// Custom audit message. /// - Custom + Custom, + + /// + /// Content version preventCleanup set to true + /// + ContentVersionPreventCleanup, + + /// + /// Content version preventCleanup set to false + /// + ContentVersionEnableCleanup } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs index 79f3fba1fd..6198f0c664 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs @@ -29,6 +29,9 @@ namespace Umbraco.Cms.Core.Models.ContentEditing [DataMember(Name = "allowedContentTypes")] public IEnumerable AllowedContentTypes { get; set; } + [DataMember(Name = "historyCleanup")] + public HistoryCleanupViewModel HistoryCleanup { get; set; } + /// /// Custom validation /// diff --git a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs index 7d45c46600..3bb52c39b7 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing @@ -6,11 +6,9 @@ namespace Umbraco.Cms.Core.Models.ContentEditing [DataContract(Name = "contentType", Namespace = "")] public class DocumentTypeDisplay : ContentTypeCompositionDisplay { - public DocumentTypeDisplay() - { + public DocumentTypeDisplay() => //initialize collections so at least their never null AllowedTemplates = new List(); - } //name, alias, icon, thumb, desc, inherited from the content type @@ -29,5 +27,8 @@ namespace Umbraco.Cms.Core.Models.ContentEditing [DataMember(Name = "apps")] public IEnumerable ContentApps { get; set; } + + [DataMember(Name = "historyCleanup")] + public HistoryCleanupViewModel HistoryCleanup { get; set; } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs new file mode 100644 index 0000000000..b7bfb32808 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs @@ -0,0 +1,17 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Models.ContentEditing +{ + [DataContract(Name = "historyCleanup", Namespace = "")] + public class HistoryCleanup + { + [DataMember(Name = "preventCleanup")] + public bool PreventCleanup { get; set; } + + [DataMember(Name = "keepAllVersionsNewerThanDays")] + public int? KeepAllVersionsNewerThanDays { get; set; } + + [DataMember(Name = "keepLatestVersionPerDayForDays")] + public int? KeepLatestVersionPerDayForDays { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs new file mode 100644 index 0000000000..303ff4eda3 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs @@ -0,0 +1,18 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Models.ContentEditing +{ + [DataContract(Name = "historyCleanup", Namespace = "")] + public class HistoryCleanupViewModel : HistoryCleanup + { + + [DataMember(Name = "globalEnableCleanup")] + public bool GlobalEnableCleanup { get; set; } + + [DataMember(Name = "globalKeepAllVersionsNewerThanDays")] + public int? GlobalKeepAllVersionsNewerThanDays { get; set; } + + [DataMember(Name = "globalKeepLatestVersionPerDayForDays")] + public int? GlobalKeepLatestVersionPerDayForDays { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs b/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs index ae5b4805a7..551cbbc0ee 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs @@ -10,7 +10,7 @@ namespace Umbraco.Cms.Core.Models.ContentEditing /// Used to assign user group permissions to a content node /// [DataContract(Name = "contentPermission", Namespace = "")] - public class UserGroupPermissionsSave : IValidatableObject + public class UserGroupPermissionsSave { public UserGroupPermissionsSave() { @@ -28,13 +28,5 @@ namespace Umbraco.Cms.Core.Models.ContentEditing /// [DataMember(Name = "permissions")] public IDictionary> AssignedPermissions { get; set; } - - public IEnumerable Validate(ValidationContext validationContext) - { - if (AssignedPermissions.SelectMany(x => x.Value).Any(x => x.IsNullOrWhiteSpace())) - { - yield return new ValidationResult("A permission value cannot be null or empty", new[] { "Permissions" }); - } - } } } diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 9a0e1a6854..6ff94f57f3 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -1,37 +1,45 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models { /// - /// Represents the content type that a object is based on + /// Represents the content type that a object is based on /// [Serializable] [DataContract(IsReference = true)] - public class ContentType : ContentTypeCompositionBase, IContentType + public class ContentType : ContentTypeCompositionBase, IContentTypeWithHistoryCleanup { public const bool SupportsPublishingConst = true; - private int _defaultTemplate; + // Custom comparer for enumerable + private static readonly DelegateEqualityComparer> TemplateComparer = new ( + (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), + templates => templates.GetHashCode()); + private IEnumerable _allowedTemplates; + private int _defaultTemplate; + /// - /// Constuctor for creating a ContentType with the parent's id. + /// Constuctor for creating a ContentType with the parent's id. /// /// Only use this for creating ContentTypes at the root (with ParentId -1). /// public ContentType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) { _allowedTemplates = new List(); + HistoryCleanup = new HistoryCleanup(); } /// - /// Constuctor for creating a ContentType with the parent as an inherited type. + /// Constuctor for creating a ContentType with the parent as an inherited type. /// /// Use this to ensure inheritance from parent. /// @@ -40,30 +48,24 @@ namespace Umbraco.Cms.Core.Models : base(shortStringHelper, parent, alias) { _allowedTemplates = new List(); + HistoryCleanup = new HistoryCleanup(); } - /// - public override ISimpleContentType ToSimple() => new SimpleContentType(this); - /// public override bool SupportsPublishing => SupportsPublishingConst; - //Custom comparer for enumerable - private static readonly DelegateEqualityComparer> TemplateComparer = new DelegateEqualityComparer>( - (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), - templates => templates.GetHashCode()); + /// + public override ISimpleContentType ToSimple() => new SimpleContentType(this); /// - /// Gets or sets the alias of the default Template. - /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! - /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// Gets or sets the alias of the default Template. + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, /// we should not store direct entity /// [IgnoreDataMember] - public ITemplate DefaultTemplate - { - get { return AllowedTemplates.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); } - } + public ITemplate DefaultTemplate => + AllowedTemplates.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); [DataMember] @@ -74,9 +76,9 @@ namespace Umbraco.Cms.Core.Models } /// - /// Gets or Sets a list of Templates which are allowed for the ContentType - /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! - /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// Gets or Sets a list of Templates which are allowed for the ContentType + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, /// we should not store direct entity /// [DataMember] @@ -88,38 +90,38 @@ namespace Umbraco.Cms.Core.Models SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, nameof(AllowedTemplates), TemplateComparer); if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false) + { DefaultTemplateId = 0; + } } } + public HistoryCleanup HistoryCleanup { get; set; } + /// - /// Determines if AllowedTemplates contains templateId + /// Determines if AllowedTemplates contains templateId /// /// The template id to check /// True if AllowedTemplates contains the templateId else False - public bool IsAllowedTemplate(int templateId) - { - return AllowedTemplates == null + public bool IsAllowedTemplate(int templateId) => + AllowedTemplates == null ? false : AllowedTemplates.Any(t => t.Id == templateId); - } /// - /// Determines if AllowedTemplates contains templateId + /// Determines if AllowedTemplates contains templateId /// /// The template alias to check /// True if AllowedTemplates contains the templateAlias else False - public bool IsAllowedTemplate(string templateAlias) - { - return AllowedTemplates == null + public bool IsAllowedTemplate(string templateAlias) => + AllowedTemplates == null ? false : AllowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase)); - } /// - /// Sets the default template for the ContentType + /// Sets the default template for the ContentType /// - /// Default + /// Default public void SetDefaultTemplate(ITemplate template) { if (template == null) @@ -138,17 +140,19 @@ namespace Umbraco.Cms.Core.Models } /// - /// Removes a template from the list of allowed templates + /// Removes a template from the list of allowed templates /// - /// to remove + /// to remove /// True if template was removed, otherwise False public bool RemoveTemplate(ITemplate template) { if (DefaultTemplateId == template.Id) - DefaultTemplateId = default(int); + { + DefaultTemplateId = default; + } var templates = AllowedTemplates.ToList(); - var remove = templates.FirstOrDefault(x => x.Id == template.Id); + ITemplate remove = templates.FirstOrDefault(x => x.Id == template.Id); var result = templates.Remove(remove); AllowedTemplates = templates; @@ -156,6 +160,7 @@ namespace Umbraco.Cms.Core.Models } /// - IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) => (IContentType)DeepCloneWithResetIdentities(newAlias); + IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) => + (IContentType)DeepCloneWithResetIdentities(newAlias); } } diff --git a/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs b/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs new file mode 100644 index 0000000000..5fa0e98958 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs @@ -0,0 +1,17 @@ +using System; + +namespace Umbraco.Cms.Core.Models +{ + public class ContentVersionCleanupPolicySettings + { + public int ContentTypeId { get; set; } + + public bool PreventCleanup { get; set; } + + public int? KeepAllVersionsNewerThanDays { get; set; } + + public int? KeepLatestVersionPerDayForDays { get; set; } + + public DateTime Updated { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/ContentVersionMeta.cs b/src/Umbraco.Core/Models/ContentVersionMeta.cs new file mode 100644 index 0000000000..d2e53e0985 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentVersionMeta.cs @@ -0,0 +1,45 @@ +using System; + +namespace Umbraco.Cms.Core.Models +{ + public class ContentVersionMeta + { + public int ContentId { get; } + public int ContentTypeId { get; } + public int VersionId { get; } + public int UserId { get; } + + public DateTime VersionDate { get; } + public bool CurrentPublishedVersion { get; } + public bool CurrentDraftVersion { get; } + public bool PreventCleanup { get; } + public string Username { get; } + + public ContentVersionMeta() { } + + public ContentVersionMeta( + int versionId, + int contentId, + int contentTypeId, + int userId, + DateTime versionDate, + bool currentPublishedVersion, + bool currentDraftVersion, + bool preventCleanup, + string username) + { + VersionId = versionId; + ContentId = contentId; + ContentTypeId = contentTypeId; + + UserId = userId; + VersionDate = versionDate; + CurrentPublishedVersion = currentPublishedVersion; + CurrentDraftVersion = currentDraftVersion; + PreventCleanup = preventCleanup; + Username = username; + } + + public override string ToString() => $"ContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}"; + } +} diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs index f04a73d5e0..a01e612887 100644 --- a/src/Umbraco.Core/Models/IContentType.cs +++ b/src/Umbraco.Core/Models/IContentType.cs @@ -1,56 +1,67 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models.ContentEditing; namespace Umbraco.Cms.Core.Models { + [Obsolete("This will be merged into IContentType in Umbraco 10")] + public interface IContentTypeWithHistoryCleanup : IContentType + { + /// + /// Gets or Sets the history cleanup configuration + /// + HistoryCleanup HistoryCleanup { get; set; } + } + /// - /// Defines a ContentType, which Content is based on + /// Defines a ContentType, which Content is based on /// public interface IContentType : IContentTypeComposition { /// - /// Internal property to store the Id of the default template + /// Internal property to store the Id of the default template /// int DefaultTemplateId { get; set; } /// - /// Gets the default Template of the ContentType + /// Gets the default Template of the ContentType /// ITemplate DefaultTemplate { get; } /// - /// Gets or Sets a list of Templates which are allowed for the ContentType + /// Gets or Sets a list of Templates which are allowed for the ContentType /// IEnumerable AllowedTemplates { get; set; } /// - /// Determines if AllowedTemplates contains templateId + /// Determines if AllowedTemplates contains templateId /// /// The template id to check /// True if AllowedTemplates contains the templateId else False bool IsAllowedTemplate(int templateId); /// - /// Determines if AllowedTemplates contains templateId + /// Determines if AllowedTemplates contains templateId /// /// The template alias to check /// True if AllowedTemplates contains the templateAlias else False bool IsAllowedTemplate(string templateAlias); /// - /// Sets the default template for the ContentType + /// Sets the default template for the ContentType /// - /// Default + /// Default void SetDefaultTemplate(ITemplate template); /// - /// Removes a template from the list of allowed templates + /// Removes a template from the list of allowed templates /// - /// to remove + /// to remove /// True if template was removed, otherwise False bool RemoveTemplate(ITemplate template); /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset /// /// /// diff --git a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs index d3269c2c0b..debaa976c5 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs @@ -2,9 +2,9 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Hosting; @@ -13,31 +13,50 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models.Mapping { /// - /// Defines mappings for content/media/members type mappings + /// Defines mappings for content/media/members type mappings /// public class ContentTypeMapDefinition : IMapDefinition { private readonly CommonMapper _commonMapper; - private readonly PropertyEditorCollection _propertyEditors; + private readonly IContentTypeService _contentTypeService; private readonly IDataTypeService _dataTypeService; private readonly IFileService _fileService; - private readonly IContentTypeService _contentTypeService; - private readonly IMediaTypeService _mediaTypeService; - private readonly IMemberTypeService _memberTypeService; - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; - private readonly IShortStringHelper _shortStringHelper; private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; + private readonly IMediaTypeService _mediaTypeService; + private readonly IMemberTypeService _memberTypeService; + private readonly PropertyEditorCollection _propertyEditors; + private readonly IShortStringHelper _shortStringHelper; + private ContentSettings _contentSettings; - public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService, - IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, - ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, IHostingEnvironment hostingEnvironment) + + [Obsolete("Use ctor with all params injected")] + public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IFileService fileService, + IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, + IMemberTypeService memberTypeService, + ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, + IHostingEnvironment hostingEnvironment) + : this(commonMapper, propertyEditors, dataTypeService, fileService, contentTypeService, mediaTypeService, + memberTypeService, loggerFactory, shortStringHelper, globalSettings, hostingEnvironment, + StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IFileService fileService, + IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, + IMemberTypeService memberTypeService, + ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, + IHostingEnvironment hostingEnvironment, IOptionsMonitor contentSettings) { _commonMapper = commonMapper; _propertyEditors = propertyEditors; @@ -51,13 +70,19 @@ namespace Umbraco.Cms.Core.Models.Mapping _shortStringHelper = shortStringHelper; _globalSettings = globalSettings.Value; _hostingEnvironment = hostingEnvironment; + + _contentSettings = contentSettings.CurrentValue; + contentSettings.OnChange(x => _contentSettings = x); } public void DefineMaps(IUmbracoMapper mapper) { - mapper.Define((source, context) => new ContentType(_shortStringHelper, source.ParentId), Map); - mapper.Define((source, context) => new MediaType(_shortStringHelper, source.ParentId), Map); - mapper.Define((source, context) => new MemberType(_shortStringHelper, source.ParentId), Map); + mapper.Define( + (source, context) => new ContentType(_shortStringHelper, source.ParentId), Map); + mapper.Define( + (source, context) => new MediaType(_shortStringHelper, source.ParentId), Map); + mapper.Define( + (source, context) => new MemberType(_shortStringHelper, source.ParentId), Map); mapper.Define((source, context) => new DocumentTypeDisplay(), Map); mapper.Define((source, context) => new MediaTypeDisplay(), Map); @@ -66,14 +91,20 @@ namespace Umbraco.Cms.Core.Models.Mapping mapper.Define( (source, context) => { - var dataType = _dataTypeService.GetDataType(source.DataTypeId); - if (dataType == null) throw new NullReferenceException("No data type found with id " + source.DataTypeId); + IDataType dataType = _dataTypeService.GetDataType(source.DataTypeId); + if (dataType == null) + { + throw new NullReferenceException("No data type found with id " + source.DataTypeId); + } + return new PropertyType(_shortStringHelper, dataType, source.Alias); }, Map); // TODO: isPublishing in ctor? - mapper.Define, PropertyGroup>((source, context) => new PropertyGroup(false), Map); - mapper.Define, PropertyGroup>((source, context) => new PropertyGroup(false), Map); + mapper.Define, PropertyGroup>( + (source, context) => new PropertyGroup(false), Map); + mapper.Define, PropertyGroup>( + (source, context) => new PropertyGroup(false), Map); mapper.Define((source, context) => new ContentTypeBasic(), Map); mapper.Define((source, context) => new ContentTypeBasic(), Map); @@ -84,11 +115,14 @@ namespace Umbraco.Cms.Core.Models.Mapping mapper.Define((source, context) => new MediaTypeDisplay(), Map); mapper.Define((source, context) => new MemberTypeDisplay(), Map); - mapper.Define, PropertyGroupDisplay>((source, context) => new PropertyGroupDisplay(), Map); - mapper.Define, PropertyGroupDisplay>((source, context) => new PropertyGroupDisplay(), Map); + mapper.Define, PropertyGroupDisplay>( + (source, context) => new PropertyGroupDisplay(), Map); + mapper.Define, PropertyGroupDisplay>( + (source, context) => new PropertyGroupDisplay(), Map); mapper.Define((source, context) => new PropertyTypeDisplay(), Map); - mapper.Define((source, context) => new MemberPropertyTypeDisplay(), Map); + mapper.Define( + (source, context) => new MemberPropertyTypeDisplay(), Map); } // no MapAll - take care @@ -97,13 +131,20 @@ namespace Umbraco.Cms.Core.Models.Mapping MapSaveToTypeBase(source, target, context); MapComposition(source, target, alias => _contentTypeService.Get(alias)); + if (target is IContentTypeWithHistoryCleanup targetWithHistoryCleanup) + { + targetWithHistoryCleanup.HistoryCleanup = source.HistoryCleanup; + } + target.AllowedTemplates = source.AllowedTemplates .Where(x => x != null) .Select(_fileService.GetTemplate) .Where(x => x != null) .ToArray(); - target.SetDefaultTemplate(source.DefaultTemplate == null ? null : _fileService.GetTemplate(source.DefaultTemplate)); + target.SetDefaultTemplate(source.DefaultTemplate == null + ? null + : _fileService.GetTemplate(source.DefaultTemplate)); } // no MapAll - take care @@ -119,11 +160,16 @@ namespace Umbraco.Cms.Core.Models.Mapping MapSaveToTypeBase(source, target, context); MapComposition(source, target, alias => _memberTypeService.Get(alias)); - foreach (var propertyType in source.Groups.SelectMany(x => x.Properties)) + foreach (MemberPropertyTypeBasic propertyType in source.Groups.SelectMany(x => x.Properties)) { - var localCopy = propertyType; - var destProp = target.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); - if (destProp == null) continue; + MemberPropertyTypeBasic localCopy = propertyType; + IPropertyType destProp = + target.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); + if (destProp == null) + { + continue; + } + target.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); target.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty); target.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData); @@ -135,6 +181,24 @@ namespace Umbraco.Cms.Core.Models.Mapping { MapTypeToDisplayBase(source, target); + if (source is IContentTypeWithHistoryCleanup sourceWithHistoryCleanup) + { + target.HistoryCleanup = new HistoryCleanupViewModel + { + PreventCleanup = sourceWithHistoryCleanup.HistoryCleanup.PreventCleanup, + KeepAllVersionsNewerThanDays = + sourceWithHistoryCleanup.HistoryCleanup.KeepAllVersionsNewerThanDays, + KeepLatestVersionPerDayForDays = + sourceWithHistoryCleanup.HistoryCleanup.KeepLatestVersionPerDayForDays, + GlobalKeepAllVersionsNewerThanDays = + _contentSettings.ContentVersionCleanupPolicy.KeepAllVersionsNewerThanDays, + GlobalKeepLatestVersionPerDayForDays = + _contentSettings.ContentVersionCleanupPolicy.KeepLatestVersionPerDayForDays, + GlobalEnableCleanup = _contentSettings.ContentVersionCleanupPolicy.EnableCleanup + }; + } + + target.AllowCultureVariant = source.VariesByCulture(); target.AllowSegmentVariant = source.VariesBySegment(); target.ContentApps = _commonMapper.GetContentApps(source); @@ -143,16 +207,23 @@ namespace Umbraco.Cms.Core.Models.Mapping target.AllowedTemplates = context.MapEnumerable(source.AllowedTemplates); if (source.DefaultTemplate != null) + { target.DefaultTemplate = context.Map(source.DefaultTemplate); + } //default listview target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; - if (string.IsNullOrEmpty(source.Alias)) return; + if (string.IsNullOrEmpty(source.Alias)) + { + return; + } var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias; if (_dataTypeService.GetDataType(name) != null) + { target.ListViewEditorName = name; + } } // no MapAll - take care @@ -164,11 +235,16 @@ namespace Umbraco.Cms.Core.Models.Mapping target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; target.IsSystemMediaType = source.IsSystemMediaType(); - if (string.IsNullOrEmpty(source.Name)) return; + if (string.IsNullOrEmpty(source.Name)) + { + return; + } var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; if (_dataTypeService.GetDataType(name) != null) + { target.ListViewEditorName = name; + } } // no MapAll - take care @@ -177,11 +253,16 @@ namespace Umbraco.Cms.Core.Models.Mapping MapTypeToDisplayBase(source, target); //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData - foreach (var propertyType in source.PropertyTypes) + foreach (IPropertyType propertyType in source.PropertyTypes) { - var localCopy = propertyType; - var displayProp = target.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias)); - if (displayProp == null) continue; + IPropertyType localCopy = propertyType; + MemberPropertyTypeDisplay displayProp = target.Groups.SelectMany(dest => dest.Properties) + .SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias)); + if (displayProp == null) + { + continue; + } + displayProp.MemberCanEditProperty = source.MemberCanEditProperty(localCopy.Alias); displayProp.MemberCanViewProperty = source.MemberCanViewProperty(localCopy.Alias); displayProp.IsSensitiveData = source.IsSensitiveProperty(localCopy.Alias); @@ -216,28 +297,20 @@ namespace Umbraco.Cms.Core.Models.Mapping } // no MapAll - uses the IContentTypeBase map method, which has MapAll - private void Map(IContentTypeComposition source, ContentTypeBasic target, MapperContext context) - { + private void Map(IContentTypeComposition source, ContentTypeBasic target, MapperContext context) => Map(source, target, Constants.UdiEntityType.MemberType); - } // no MapAll - uses the IContentTypeBase map method, which has MapAll - private void Map(IContentType source, ContentTypeBasic target, MapperContext context) - { + private void Map(IContentType source, ContentTypeBasic target, MapperContext context) => Map(source, target, Constants.UdiEntityType.DocumentType); - } // no MapAll - uses the IContentTypeBase map method, which has MapAll - private void Map(IMediaType source, ContentTypeBasic target, MapperContext context) - { + private void Map(IMediaType source, ContentTypeBasic target, MapperContext context) => Map(source, target, Constants.UdiEntityType.MediaType); - } // no MapAll - uses the IContentTypeBase map method, which has MapAll - private void Map(IMemberType source, ContentTypeBasic target, MapperContext context) - { + private void Map(IMemberType source, ContentTypeBasic target, MapperContext context) => Map(source, target, Constants.UdiEntityType.MemberType); - } // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations @@ -254,10 +327,14 @@ namespace Umbraco.Cms.Core.Models.Mapping target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); if (source.Id > 0) + { target.Id = source.Id; + } if (source.GroupId > 0) + { target.PropertyGroupId = new Lazy(() => source.GroupId, false); + } target.Alias = source.Alias; target.Description = source.Description; @@ -268,18 +345,19 @@ namespace Umbraco.Cms.Core.Models.Mapping // no MapAll - take care private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperContext context) { - MapTypeToDisplayBase(source, target, context); + MapTypeToDisplayBase(source, + target, context); //sync templates - var destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias); + IEnumerable destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias); //if the dest is set and it's the same as the source, then don't change if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) { - var templates = _fileService.GetTemplates(source.AllowedTemplates.ToArray()); + IEnumerable templates = _fileService.GetTemplates(source.AllowedTemplates.ToArray()); target.AllowedTemplates = source.AllowedTemplates .Select(x => { - var template = templates.SingleOrDefault(t => t.Alias == x); + ITemplate template = templates.SingleOrDefault(t => t.Alias == x); return template != null ? context.Map(template) : null; @@ -293,7 +371,7 @@ namespace Umbraco.Cms.Core.Models.Mapping //if the dest is set and it's the same as the source, then don't change if (target.DefaultTemplate == null || source.DefaultTemplate != target.DefaultTemplate.Alias) { - var template = _fileService.GetTemplate(source.DefaultTemplate); + ITemplate template = _fileService.GetTemplate(source.DefaultTemplate); target.DefaultTemplate = template == null ? null : context.Map(template); } } @@ -304,22 +382,24 @@ namespace Umbraco.Cms.Core.Models.Mapping } // no MapAll - take care - private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context) - { - MapTypeToDisplayBase(source, target, context); - } + private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context) => + MapTypeToDisplayBase(source, + target, context); // no MapAll - take care - private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context) - { - MapTypeToDisplayBase(source, target, context); - } + private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context) => + MapTypeToDisplayBase( + source, target, context); // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes - private static void Map(PropertyGroupBasic source, PropertyGroup target, MapperContext context) + private static void Map(PropertyGroupBasic source, PropertyGroup target, + MapperContext context) { if (source.Id > 0) + { target.Id = source.Id; + } + target.Key = source.Key; target.Type = source.Type; target.Name = source.Name; @@ -328,10 +408,14 @@ namespace Umbraco.Cms.Core.Models.Mapping } // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes - private static void Map(PropertyGroupBasic source, PropertyGroup target, MapperContext context) + private static void Map(PropertyGroupBasic source, PropertyGroup target, + MapperContext context) { if (source.Id > 0) + { target.Id = source.Id; + } + target.Key = source.Key; target.Type = source.Type; target.Name = source.Name; @@ -340,11 +424,15 @@ namespace Umbraco.Cms.Core.Models.Mapping } // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames - private static void Map(PropertyGroupBasic source, PropertyGroupDisplay target, MapperContext context) + private static void Map(PropertyGroupBasic source, + PropertyGroupDisplay target, MapperContext context) { target.Inherited = source.Inherited; if (source.Id > 0) + { target.Id = source.Id; + } + target.Key = source.Key; target.Type = source.Type; target.Name = source.Name; @@ -354,17 +442,22 @@ namespace Umbraco.Cms.Core.Models.Mapping } // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames - private static void Map(PropertyGroupBasic source, PropertyGroupDisplay target, MapperContext context) + private static void Map(PropertyGroupBasic source, + PropertyGroupDisplay target, MapperContext context) { target.Inherited = source.Inherited; if (source.Id > 0) + { target.Id = source.Id; + } + target.Key = source.Key; target.Type = source.Type; target.Name = source.Name; target.Alias = source.Alias; target.SortOrder = source.SortOrder; - target.Properties = context.MapEnumerable(source.Properties); + target.Properties = + context.MapEnumerable(source.Properties); } // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName @@ -409,7 +502,8 @@ namespace Umbraco.Cms.Core.Models.Mapping // Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate // Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType) - private static void MapSaveToTypeBase(TSource source, IContentTypeComposition target, MapperContext context) + private static void MapSaveToTypeBase(TSource source, + IContentTypeComposition target, MapperContext context) where TSource : ContentTypeSave where TSourcePropertyType : PropertyTypeBasic { @@ -418,7 +512,9 @@ namespace Umbraco.Cms.Core.Models.Mapping var id = Convert.ToInt32(source.Id); if (id > 0) + { target.Id = id; + } target.Alias = source.Alias; target.Description = source.Description; @@ -457,18 +553,19 @@ namespace Umbraco.Cms.Core.Models.Mapping // - managing the content type's PropertyTypes collection (for generic properties) // handle actual groups (non-generic-properties) - var destOrigGroups = target.PropertyGroups.ToArray(); // local groups - var destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not + PropertyGroup[] destOrigGroups = target.PropertyGroups.ToArray(); // local groups + IPropertyType[] destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not var destGroups = new List(); - var sourceGroups = source.Groups.Where(x => x.IsGenericProperties == false).ToArray(); + PropertyGroupBasic[] sourceGroups = + source.Groups.Where(x => x.IsGenericProperties == false).ToArray(); var sourceGroupParentAliases = sourceGroups.Select(x => x.GetParentAlias()).Distinct().ToArray(); - foreach (var sourceGroup in sourceGroups) + foreach (PropertyGroupBasic sourceGroup in sourceGroups) { // get the dest group - var destGroup = MapSaveGroup(sourceGroup, destOrigGroups, context); + PropertyGroup destGroup = MapSaveGroup(sourceGroup, destOrigGroups, context); // handle local properties - var destProperties = sourceGroup.Properties + IPropertyType[] destProperties = sourceGroup.Properties .Where(x => x.Inherited == false) .Select(x => MapSaveProperty(x, destOrigProperties, context)) .ToArray(); @@ -476,7 +573,9 @@ namespace Umbraco.Cms.Core.Models.Mapping // if the group has no local properties and is not used as parent, skip it, ie sort-of garbage-collect // local groups which would not have local properties anymore if (destProperties.Length == 0 && !sourceGroupParentAliases.Contains(sourceGroup.Alias)) + { continue; + } // ensure no duplicate alias, then assign the group properties collection EnsureUniqueAliases(destProperties); @@ -492,11 +591,12 @@ namespace Umbraco.Cms.Core.Models.Mapping // the old groups - they are just gone and will be cleared by the repository // handle non-grouped (ie generic) properties - var genericPropertiesGroup = source.Groups.FirstOrDefault(x => x.IsGenericProperties); + PropertyGroupBasic genericPropertiesGroup = + source.Groups.FirstOrDefault(x => x.IsGenericProperties); if (genericPropertiesGroup != null) { // handle local properties - var destProperties = genericPropertiesGroup.Properties + IPropertyType[] destProperties = genericPropertiesGroup.Properties .Where(x => x.Inherited == false) .Select(x => MapSaveProperty(x, destOrigProperties, context)) .ToArray(); @@ -547,7 +647,8 @@ namespace Umbraco.Cms.Core.Models.Mapping { MapTypeToDisplayBase(source, target); - var groupsMapper = new PropertyTypeGroupMapper(_propertyEditors, _dataTypeService, _shortStringHelper, _loggerFactory.CreateLogger>()); + var groupsMapper = new PropertyTypeGroupMapper(_propertyEditors, _dataTypeService, + _shortStringHelper, _loggerFactory.CreateLogger>()); target.Groups = groupsMapper.Map(source); } @@ -580,7 +681,8 @@ namespace Umbraco.Cms.Core.Models.Mapping } // no MapAll - relies on the non-generic method - private void MapTypeToDisplayBase(TSource source, TTarget target, MapperContext context) + private void MapTypeToDisplayBase(TSource source, + TTarget target, MapperContext context) where TSource : ContentTypeSave where TSourcePropertyType : PropertyTypeBasic where TTarget : ContentTypeCompositionDisplay @@ -588,35 +690,49 @@ namespace Umbraco.Cms.Core.Models.Mapping { MapTypeToDisplayBase(source, target); - target.Groups = context.MapEnumerable, PropertyGroupDisplay>(source.Groups); + target.Groups = + context + .MapEnumerable, PropertyGroupDisplay>( + source.Groups); } private IEnumerable MapLockedCompositions(IContentTypeComposition source) { // get ancestor ids from path of parent if not root if (source.ParentId == Constants.System.Root) + { return Enumerable.Empty(); + } - var parent = _contentTypeService.Get(source.ParentId); + IContentType parent = _contentTypeService.Get(source.ParentId); if (parent == null) + { return Enumerable.Empty(); + } var aliases = new List(); - var ancestorIds = parent.Path.Split(Constants.CharArrays.Comma).Select(s => int.Parse(s, CultureInfo.InvariantCulture)); + IEnumerable ancestorIds = parent.Path.Split(Constants.CharArrays.Comma) + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)); // loop through all content types and return ordered aliases of ancestors - var allContentTypes = _contentTypeService.GetAll().ToArray(); + IContentType[] allContentTypes = _contentTypeService.GetAll().ToArray(); foreach (var ancestorId in ancestorIds) { - var ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId); + IContentType ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId); if (ancestor != null) + { aliases.Add(ancestor.Alias); + } } + return aliases.OrderBy(x => x); } public static Udi MapContentTypeUdi(IContentTypeComposition source) { - if (source == null) return null; + if (source == null) + { + return null; + } string udiType; switch (source) @@ -637,7 +753,8 @@ namespace Umbraco.Cms.Core.Models.Mapping return Udi.Create(udiType, source.Key); } - private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups, MapperContext context) + private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, + IEnumerable destOrigGroups, MapperContext context) where TPropertyType : PropertyTypeBasic { PropertyGroup destGroup; @@ -663,7 +780,8 @@ namespace Umbraco.Cms.Core.Models.Mapping return destGroup; } - private static IPropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, IEnumerable destOrigProperties, MapperContext context) + private static IPropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, + IEnumerable destOrigProperties, MapperContext context) { IPropertyType destProperty; if (sourceProperty.Id > 0) @@ -690,43 +808,52 @@ namespace Umbraco.Cms.Core.Models.Mapping private static void EnsureUniqueAliases(IEnumerable properties) { - var propertiesA = properties.ToArray(); + IPropertyType[] propertiesA = properties.ToArray(); var distinctProperties = propertiesA .Select(x => x.Alias?.ToUpperInvariant()) .Distinct() .Count(); if (distinctProperties != propertiesA.Length) + { throw new InvalidOperationException("Cannot map properties due to alias conflict."); + } } private static void EnsureUniqueAliases(IEnumerable groups) { - var groupsA = groups.ToArray(); + PropertyGroup[] groupsA = groups.ToArray(); var distinctProperties = groupsA .Select(x => x.Alias) .Distinct() .Count(); if (distinctProperties != groupsA.Length) + { throw new InvalidOperationException("Cannot map groups due to alias conflict."); + } } - private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, Func getContentType) + private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, + Func getContentType) { var current = target.CompositionAliases().ToArray(); - var proposed = source.CompositeContentTypes; + IEnumerable proposed = source.CompositeContentTypes; - var remove = current.Where(x => !proposed.Contains(x)); - var add = proposed.Where(x => !current.Contains(x)); + IEnumerable remove = current.Where(x => !proposed.Contains(x)); + IEnumerable add = proposed.Where(x => !current.Contains(x)); foreach (var alias in remove) + { target.RemoveContentType(alias); + } foreach (var alias in add) { // TODO: Remove N+1 lookup - var contentType = getContentType(alias); + IContentTypeComposition contentType = getContentType(alias); if (contentType != null) + { target.AddContentType(contentType); + } } } } diff --git a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs index 2380657180..e4d101ff06 100644 --- a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs @@ -251,7 +251,7 @@ namespace Umbraco.Cms.Core.Models.Mapping // the entity service due to too many Sql parameters. var list = new List(); - foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(2000)) + foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(Constants.Sql.MaxParameterCount)) list.AddRange(_entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray())); contentEntities = list.ToArray(); } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs index c29df4e85f..7f8d852115 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs @@ -1,15 +1,22 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; + namespace Umbraco.Cms.Core.Notifications { + // TODO (V10): Remove this class. + /// /// Notification that occurs during the Umbraco boot process, before instances of initialize. /// + [Obsolete("This notification was added to the core runtime start-up as a hook for Umbraco Cloud local connection string and database setup. " + + "Following re-work they are no longer used (from Deploy 9.2.0)." + + "Given they are non-documented and no other use is expected, they can be removed in the next major release")] public class UmbracoApplicationComponentsInstallingNotification : INotification { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The runtime level public UmbracoApplicationComponentsInstallingNotification(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel; diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs index 6a66e2413f..66593ab086 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs @@ -1,11 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; + namespace Umbraco.Cms.Core.Notifications { + // TODO (V10): Remove this class. + /// /// Notification that occurs during Umbraco boot after the MainDom has been acquired. /// + [Obsolete("This notification was added to the core runtime start-up as a hook for Umbraco Cloud local connection string and database setup. " + + "Following re-work they are no longer used (from Deploy 9.2.0)." + + "Given they are non-documented and no other use is expected, they can be removed in the next major release")] public class UmbracoApplicationMainDomAcquiredNotification : INotification { /// diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index f0f891d48e..680eee5ba2 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -26,6 +26,8 @@ namespace Umbraco.Cms.Core public const string Content = TableNamePrefix + "Content"; public const string ContentVersion = TableNamePrefix + "ContentVersion"; public const string ContentVersionCultureVariation = TableNamePrefix + "ContentVersionCultureVariation"; + public const string ContentVersionCleanupPolicy = TableNamePrefix + "ContentVersionCleanupPolicy"; + public const string Document = TableNamePrefix + "Document"; public const string DocumentCultureVariation = TableNamePrefix + "DocumentCultureVariation"; public const string DocumentVersion = TableNamePrefix + "DocumentVersion"; @@ -50,6 +52,7 @@ namespace Umbraco.Cms.Core public const string User2UserGroup = TableNamePrefix + "User2UserGroup"; public const string User2NodeNotify = TableNamePrefix + "User2NodeNotify"; public const string UserGroup2App = TableNamePrefix + "UserGroup2App"; + public const string UserGroup2Node = TableNamePrefix + "UserGroup2Node"; public const string UserGroup2NodePermission = TableNamePrefix + "UserGroup2NodePermission"; public const string ExternalLogin = TableNamePrefix + "ExternalLogin"; public const string ExternalLoginToken = TableNamePrefix + "ExternalLoginToken"; diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs new file mode 100644 index 0000000000..38f425d5a1 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Persistence.Repositories +{ + public interface IDocumentVersionRepository : IRepository + { + /// + /// Gets a list of all historic content versions. + /// + public IReadOnlyCollection GetDocumentVersionsEligibleForCleanup(); + + /// + /// Gets cleanup policy override settings per content type. + /// + public IReadOnlyCollection GetCleanupPolicies(); + + /// + /// Gets paginated content versions for given content id paginated. + /// + public IEnumerable GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null); + + /// + /// Deletes multiple content versions by ID. + /// + void DeleteVersions(IEnumerable versionIds); + + /// + /// Updates the prevent cleanup flag on a content version. + /// + void SetPreventCleanup(int versionId, bool preventCleanup); + + /// + /// Gets the content version metadata for a specific version. + /// + ContentVersionMeta Get(int versionId); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/VoidEditor.cs b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs index 716e722be1..8510c982ab 100644 --- a/src/Umbraco.Core/PropertyEditors/VoidEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Serialization; @@ -16,8 +16,6 @@ namespace Umbraco.Cms.Core.PropertyEditors [HideFromTypeFinder] public class VoidEditor : DataEditor { - private readonly IJsonSerializer _jsonSerializer; - /// /// Initializes a new instance of the class. /// diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs index 34152ebc0f..2c79e68e1d 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PublishedCache.Internal { - + // TODO: Only used in unit tests, needs to be moved to test project + [EditorBrowsable(EditorBrowsableState.Never)] public sealed class InternalPublishedContent : IPublishedContent { public InternalPublishedContent(IPublishedContentType contentType) diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs index 2a197affff..c2f8f27419 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Xml; namespace Umbraco.Cms.Core.PublishedCache.Internal { + // TODO: Only used in unit tests, needs to be moved to test project + [EditorBrowsable(EditorBrowsableState.Never)] public sealed class InternalPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache { private readonly Dictionary _content = new Dictionary(); diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs index a98eff6484..7fe46d8d75 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs @@ -1,7 +1,10 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using System.ComponentModel; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PublishedCache.Internal { + // TODO: Only used in unit tests, needs to be moved to test project + [EditorBrowsable(EditorBrowsableState.Never)] public class InternalPublishedProperty : IPublishedProperty { public IPublishedPropertyType PropertyType { get; set; } diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs index c73b6cef76..1ff8d99139 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs @@ -1,8 +1,12 @@ using System; +using System.ComponentModel; using Umbraco.Cms.Core.Cache; namespace Umbraco.Cms.Core.PublishedCache.Internal { + + // TODO: Only used in unit tests, needs to be moved to test project + [EditorBrowsable(EditorBrowsableState.Never)] public sealed class InternalPublishedSnapshot : IPublishedSnapshot { public InternalPublishedContentCache InnerContentCache { get; } = new InternalPublishedContentCache(); diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs index 5dcdc5189f..61e2a9c2a9 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs @@ -1,10 +1,13 @@ using System.Collections.Generic; +using System.ComponentModel; using System.Threading.Tasks; using Umbraco.Cms.Core.Cache; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PublishedCache.Internal { + // TODO: Only used in unit tests, needs to be moved to test project + [EditorBrowsable(EditorBrowsableState.Never)] public class InternalPublishedSnapshotService : IPublishedSnapshotService { private InternalPublishedSnapshot _snapshot; diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index 969e5bf98b..2a7bc97650 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -1,117 +1,111 @@ using System; using System.Collections.Generic; using System.Globalization; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Routing { /// - /// Provides urls. + /// Provides urls. /// public class DefaultUrlProvider : IUrlProvider { - private RequestHandlerSettings _requestSettings; + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; private readonly ILogger _logger; + private RequestHandlerSettings _requestSettings; private readonly ISiteDomainMapper _siteDomainMapper; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly UriUtility _uriUtility; - public DefaultUrlProvider(IOptionsMonitor requestSettings, ILogger logger, ISiteDomainMapper siteDomainMapper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility) + [Obsolete("Use ctor with all parameters")] + public DefaultUrlProvider(IOptionsMonitor requestSettings, ILogger logger, + ISiteDomainMapper siteDomainMapper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility) + : this(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public DefaultUrlProvider( + IOptionsMonitor requestSettings, + ILogger logger, + ISiteDomainMapper siteDomainMapper, + IUmbracoContextAccessor umbracoContextAccessor, + UriUtility uriUtility, + ILocalizationService localizationService) { _requestSettings = requestSettings.CurrentValue; _logger = logger; _siteDomainMapper = siteDomainMapper; - _uriUtility = uriUtility; _umbracoContextAccessor = umbracoContextAccessor; + _uriUtility = uriUtility; + _localizationService = localizationService; + requestSettings.OnChange(x => _requestSettings = x); } - #region GetUrl - - /// - public virtual UrlInfo GetUrl(IPublishedContent content, UrlMode mode, string culture, Uri current) - { - if (!current.IsAbsoluteUri) throw new ArgumentException("Current URL must be absolute.", nameof(current)); - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - // will not use cache if previewing - var route = umbracoContext.Content.GetRouteById(content.Id, culture); - - return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); - } - - internal UrlInfo GetUrlFromRoute(string route, IUmbracoContext umbracoContext, int id, Uri current, UrlMode mode, string culture) - { - if (string.IsNullOrWhiteSpace(route)) - { - _logger.LogDebug("Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.", id); - return null; - } - - // extract domainUri and path - // route is / or / - var pos = route.IndexOf('/'); - var path = pos == 0 ? route : route.Substring(pos); - var domainUri = pos == 0 - ? null - : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture); - - // assemble the URL from domainUri (maybe null) and path - var url = AssembleUrl(domainUri, path, current, mode).ToString(); - - return UrlInfo.Url(url, culture); - } - - #endregion - #region GetOtherUrls /// - /// Gets the other URLs of a published content. + /// Gets the other URLs of a published content. /// /// The Umbraco context. /// The published content id. /// The current absolute URL. /// The other URLs for the published content. /// - /// Other URLs are those that GetUrl would not return in the current context, but would be valid - /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// + /// Other URLs are those that GetUrl would not return in the current context, but would be valid + /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// /// public virtual IEnumerable GetOtherUrls(int id, Uri current) { - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - var node = umbracoContext.Content.GetById(id); + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + IPublishedContent node = umbracoContext.Content.GetById(id); if (node == null) { yield break; } // look for domains, walking up the tree - var n = node; - var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false); + IPublishedContent n = node; + IEnumerable domainUris = + DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, + current, false); while (domainUris == null && n != null) // n is null at root { n = n.Parent; // move to parent node - domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, excludeDefault: true); + domainUris = n == null + ? null + : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, + current); } // no domains = exit - if (domainUris ==null) + if (domainUris == null) { yield break; } - foreach (var d in domainUris) + foreach (DomainAndUri d in domainUris) { var culture = d?.Culture; // although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok var route = umbracoContext.Content.GetRouteById(id, culture); - if (route == null) continue; + if (route == null) + { + continue; + } // need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) var pos = route.IndexOf('/'); @@ -125,9 +119,57 @@ namespace Umbraco.Cms.Core.Routing #endregion + #region GetUrl + + /// + public virtual UrlInfo GetUrl(IPublishedContent content, UrlMode mode, string culture, Uri current) + { + if (!current.IsAbsoluteUri) + { + throw new ArgumentException("Current URL must be absolute.", nameof(current)); + } + + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + // will not use cache if previewing + var route = umbracoContext.Content.GetRouteById(content.Id, culture); + + return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); + } + + internal UrlInfo GetUrlFromRoute(string route, IUmbracoContext umbracoContext, int id, Uri current, + UrlMode mode, string culture) + { + if (string.IsNullOrWhiteSpace(route)) + { + _logger.LogDebug( + "Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.", + id); + return null; + } + + // extract domainUri and path + // route is / or / + var pos = route.IndexOf('/'); + var path = pos == 0 ? route : route.Substring(pos); + var domainUri = pos == 0 + ? null + : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture); + + var defaultCulture = _localizationService.GetDefaultLanguageIsoCode(); + if (domainUri is not null || culture == defaultCulture || culture is null) + { + var url = AssembleUrl(domainUri, path, current, mode).ToString(); + return UrlInfo.Url(url, culture); + } + + return null; + } + + #endregion + #region Utilities - Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode) + private Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode) { Uri uri; @@ -136,7 +178,9 @@ namespace Umbraco.Cms.Core.Routing if (domainUri == null) // no domain was found { if (current == null) + { mode = UrlMode.Relative; // best we can do + } switch (mode) { @@ -156,10 +200,15 @@ namespace Umbraco.Cms.Core.Routing if (mode == UrlMode.Auto) { //this check is a little tricky, we can't just compare domains - if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) + if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == + current.GetLeftPart(UriPartial.Authority)) + { mode = UrlMode.Relative; + } else + { mode = UrlMode.Absolute; + } } switch (mode) @@ -180,9 +229,9 @@ namespace Umbraco.Cms.Core.Routing return _uriUtility.UriFromUmbraco(uri, _requestSettings); } - string CombinePaths(string path1, string path2) + private string CombinePaths(string path1, string path2) { - string path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2; + var path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2; return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash); } diff --git a/src/Umbraco.Core/Routing/SiteDomainMapper.cs b/src/Umbraco.Core/Routing/SiteDomainMapper.cs index d877385ef1..455889d015 100644 --- a/src/Umbraco.Core/Routing/SiteDomainMapper.cs +++ b/src/Umbraco.Core/Routing/SiteDomainMapper.cs @@ -4,18 +4,36 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; using Umbraco.Extensions; -using System.ComponentModel; namespace Umbraco.Cms.Core.Routing { /// - /// Provides utilities to handle site domains. + /// Provides utilities to handle site domains. /// public class SiteDomainMapper : ISiteDomainMapper, IDisposable { + public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // This is pretty nasty disposing a static on an instance but it's because this whole class + // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics. + _configLock.Dispose(); + } + + _disposedValue = true; + } + } + #region Configure - private readonly ReaderWriterLockSlim _configLock = new ReaderWriterLockSlim(); + private readonly ReaderWriterLockSlim _configLock = new(); private Dictionary> _qualifiedSites; private bool _disposedValue; @@ -25,10 +43,12 @@ namespace Umbraco.Cms.Core.Routing // these are for validation //private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; - private static readonly Regex s_domainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static readonly Regex s_domainValidation = + new(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); /// - /// Clears the entire configuration. + /// Clears the entire configuration. /// public void Clear() { @@ -49,20 +69,21 @@ namespace Umbraco.Cms.Core.Routing } } - private IEnumerable ValidateDomains(IEnumerable domains) - { + private IEnumerable ValidateDomains(IEnumerable domains) => // must use authority format w/optional scheme and port, but no path // any domain should appear only once - return domains.Select(domain => + domains.Select(domain => + { + if (!s_domainValidation.IsMatch(domain)) { - if (!s_domainValidation.IsMatch(domain)) - throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\"."); - return domain; - }); - } + throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\"."); + } + + return domain; + }); /// - /// Adds a site. + /// Adds a site. /// /// A key uniquely identifying the site. /// The site domains. @@ -87,7 +108,7 @@ namespace Umbraco.Cms.Core.Routing } /// - /// Adds a site. + /// Adds a site. /// /// A key uniquely identifying the site. /// The site domains. @@ -112,7 +133,7 @@ namespace Umbraco.Cms.Core.Routing } /// - /// Removes a site. + /// Removes a site. /// /// A key uniquely identifying the site. internal void RemoveSite(string key) @@ -122,11 +143,15 @@ namespace Umbraco.Cms.Core.Routing _configLock.EnterWriteLock(); if (Sites == null || !Sites.ContainsKey(key)) + { return; + } Sites.Remove(key); if (Sites.Count == 0) + { Sites = null; + } if (Bindings != null && Bindings.ContainsKey(key)) { @@ -134,11 +159,16 @@ namespace Umbraco.Cms.Core.Routing { Bindings[b].Remove(key); if (Bindings[b].Count == 0) + { Bindings.Remove(b); + } } + Bindings.Remove(key); if (Bindings.Count > 0) + { Bindings = null; + } } _qualifiedSites = null; @@ -153,12 +183,12 @@ namespace Umbraco.Cms.Core.Routing } /// - /// Binds some sites. + /// Binds some sites. /// /// The keys uniquely identifying the sites to bind. /// - /// At the moment there is no public way to unbind sites. Clear and reconfigure. - /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3. + /// At the moment there is no public way to unbind sites. Clear and reconfigure. + /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3. /// public void BindSites(params string[] keys) { @@ -167,7 +197,9 @@ namespace Umbraco.Cms.Core.Routing _configLock.EnterWriteLock(); foreach (var key in keys.Where(key => !Sites.ContainsKey(key))) + { throw new ArgumentException($"Not an existing site key: {key}.", nameof(keys)); + } Bindings = Bindings ?? new Dictionary>(); @@ -180,9 +212,12 @@ namespace Umbraco.Cms.Core.Routing foreach (var key in allkeys) { if (!Bindings.ContainsKey(key)) + { Bindings[key] = new List(); + } + var xkey = key; - var addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]); + IEnumerable addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]); Bindings[key].AddRange(addKeys); } } @@ -200,16 +235,18 @@ namespace Umbraco.Cms.Core.Routing #region Map domains /// - public virtual DomainAndUri MapDomain(IReadOnlyCollection domainAndUris, Uri current, string culture, string defaultCulture) + public virtual DomainAndUri MapDomain(IReadOnlyCollection domainAndUris, Uri current, + string culture, string defaultCulture) { var currentAuthority = current.GetLeftPart(UriPartial.Authority); - var qualifiedSites = GetQualifiedSites(current); + Dictionary qualifiedSites = GetQualifiedSites(current); return MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture); } /// - public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, bool excludeDefault, string culture, string defaultCulture) + public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris, + Uri current, bool excludeDefault, string culture, string defaultCulture) { // TODO: ignoring cultures entirely? @@ -221,15 +258,18 @@ namespace Umbraco.Cms.Core.Routing { _configLock.EnterReadLock(); - var qualifiedSites = GetQualifiedSitesInsideLock(current); + Dictionary qualifiedSites = GetQualifiedSitesInsideLock(current); if (excludeDefault) { // exclude the current one (avoid producing the absolute equivalent of what GetUrl returns) - var hintWithSlash = current.EndPathWithSlash(); - var hinted = domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash)); + Uri hintWithSlash = current.EndPathWithSlash(); + DomainAndUri hinted = + domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash)); if (hinted != null) + { ret = ret.Where(d => d != hinted); + } // exclude the default one (avoid producing a possible duplicate of what GetUrl returns) // only if the default one cannot be the current one ie if hinted is not null @@ -237,17 +277,21 @@ namespace Umbraco.Cms.Core.Routing { // it is illegal to call MapDomain if domainAndUris is empty // also, domainAndUris should NOT contain current, hence the test on hinted - var mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture); // what GetUrl would get + DomainAndUri mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, + defaultCulture); // what GetUrl would get ret = ret.Where(d => d != mainDomain); } } // we do our best, but can't do the impossible if (qualifiedSites == null) + { return ret; + } // find a site that contains the current authority - var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); + KeyValuePair currentSite = + qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); // if current belongs to a site, pick every element from domainAndUris that also belong // to that site -- or to any site bound to that site @@ -257,7 +301,8 @@ namespace Umbraco.Cms.Core.Routing candidateSites = new[] { currentSite }; if (Bindings != null && Bindings.ContainsKey(currentSite.Key)) { - var boundSites = qualifiedSites.Where(site => Bindings[currentSite.Key].Contains(site.Key)); + IEnumerable> boundSites = + qualifiedSites.Where(site => Bindings[currentSite.Key].Contains(site.Key)); candidateSites = candidateSites.Union(boundSites).ToArray(); // .ToArray ensures it is evaluated before the configuration lock is exited @@ -273,7 +318,9 @@ namespace Umbraco.Cms.Core.Routing } // if we are able to filter, then filter, else return the whole lot - return candidateSites == null ? ret : ret.Where(d => + return candidateSites == null + ? ret + : ret.Where(d => { var authority = d.Uri.GetLeftPart(UriPartial.Authority); return candidateSites.Any(site => site.Value.Contains(authority)); @@ -301,11 +348,15 @@ namespace Umbraco.Cms.Core.Routing { // we do our best, but can't do the impossible if (Sites == null) + { return null; + } // cached? if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme)) + { return _qualifiedSites[current.Scheme]; + } _qualifiedSites = _qualifiedSites ?? new Dictionary>(); @@ -314,7 +365,10 @@ namespace Umbraco.Cms.Core.Routing return _qualifiedSites[current.Scheme] = Sites .ToDictionary( kvp => kvp.Key, - kvp => kvp.Value.Select(d => new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme)).GetLeftPart(UriPartial.Authority)).ToArray() + kvp => kvp.Value.Select(d => + new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme)) + .GetLeftPart(UriPartial.Authority)) + .ToArray() ); // .ToDictionary will evaluate and create the dictionary immediately @@ -322,61 +376,54 @@ namespace Umbraco.Cms.Core.Routing // therefore it is safe to return and exit the configuration lock } - private DomainAndUri MapDomain(IReadOnlyCollection domainAndUris, Dictionary qualifiedSites, string currentAuthority, string culture, string defaultCulture) + private DomainAndUri MapDomain(IReadOnlyCollection domainAndUris, + Dictionary qualifiedSites, string currentAuthority, string culture, string defaultCulture) { if (domainAndUris == null) + { throw new ArgumentNullException(nameof(domainAndUris)); + } + if (domainAndUris.Count == 0) + { throw new ArgumentException("Cannot be empty.", nameof(domainAndUris)); + } - // TODO: how shall we deal with cultures? - - // we do our best, but can't do the impossible - // get the "default" domain ie the first one for the culture, else the first one (exists, length > 0) if (qualifiedSites == null) { return domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) - ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)); + ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)) + ?? (culture is null ? domainAndUris.First() : null); } // find a site that contains the current authority - var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); + KeyValuePair currentSite = + qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); // if current belongs to a site - try to pick the first element // from domainAndUris that also belongs to that site - var ret = currentSite.Equals(default(KeyValuePair)) + DomainAndUri ret = currentSite.Equals(default(KeyValuePair)) ? null - : domainAndUris.FirstOrDefault(d => currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority))); + : domainAndUris.FirstOrDefault(d => + currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority))); // no match means that either current does not belong to a site, or the site it belongs to - // does not contain any of domainAndUris. + // does not contain any of domainAndUris. Yet we have to return something. here, it becomes + // a bit arbitrary. + + // look through sites in order and pick the first domainAndUri that belongs to a site + ret = ret ?? qualifiedSites + .Where(site => site.Key != currentSite.Key) + .Select(site => domainAndUris.FirstOrDefault(domainAndUri => + site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority)))) + .FirstOrDefault(domainAndUri => domainAndUri != null); + + // random, really + ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First(); + return ret; } - #endregion - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - // This is pretty nasty disposing a static on an instance but it's because this whole class - // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics. - _configLock.Dispose(); - } - - _disposedValue = true; - } - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - } - - } } diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs index 085629eac8..3853988aab 100644 --- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs @@ -15,11 +15,11 @@ namespace Umbraco.Extensions public static class UrlProviderExtensions { /// - /// Gets the URLs of the content item. + /// Gets the URLs of the content item. /// /// - /// Use when displaying URLs. If errors occur when generating the URLs, they will show in the list. - /// Contains all the URLs that we can figure out (based upon domains, etc). + /// Use when displaying URLs. If errors occur when generating the URLs, they will show in the list. + /// Contains all the URLs that we can figure out (based upon domains, etc). /// public static async Task> GetContentUrlsAsync( this IContent content, @@ -33,16 +33,55 @@ namespace Umbraco.Extensions UriUtility uriUtility, IPublishedUrlProvider publishedUrlProvider) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (publishedRouter == null) throw new ArgumentNullException(nameof(publishedRouter)); - if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); - if (localizationService == null) throw new ArgumentNullException(nameof(localizationService)); - if (textService == null) throw new ArgumentNullException(nameof(textService)); - if (contentService == null) throw new ArgumentNullException(nameof(contentService)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (publishedUrlProvider == null) throw new ArgumentNullException(nameof(publishedUrlProvider)); - if (uriUtility == null) throw new ArgumentNullException(nameof(uriUtility)); - if (variationContextAccessor == null) throw new ArgumentNullException(nameof(variationContextAccessor)); + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + + if (publishedRouter == null) + { + throw new ArgumentNullException(nameof(publishedRouter)); + } + + if (umbracoContext == null) + { + throw new ArgumentNullException(nameof(umbracoContext)); + } + + if (localizationService == null) + { + throw new ArgumentNullException(nameof(localizationService)); + } + + if (textService == null) + { + throw new ArgumentNullException(nameof(textService)); + } + + if (contentService == null) + { + throw new ArgumentNullException(nameof(contentService)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + if (publishedUrlProvider == null) + { + throw new ArgumentNullException(nameof(publishedUrlProvider)); + } + + if (uriUtility == null) + { + throw new ArgumentNullException(nameof(uriUtility)); + } + + if (variationContextAccessor == null) + { + throw new ArgumentNullException(nameof(variationContextAccessor)); + } var result = new List(); @@ -68,7 +107,9 @@ namespace Umbraco.Extensions // get all URLs for all cultures // in a HashSet, so de-duplicates too - foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider)) + foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, + umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, + publishedUrlProvider)) { urls.Add(cultureUrl); } @@ -79,15 +120,25 @@ namespace Umbraco.Extensions // in some cases there will be the same URL for multiple cultures: // * The entire branch is invariant // * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed - foreach (UrlInfo dUrl in urlGroup.LegacyDistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture)) + + if (urlGroup.Key) { - result.Add(dUrl); + result.AddRange(urlGroup.LegacyDistinctBy(x => x.Text.ToUpperInvariant()) + .OrderBy(x => x.Text).ThenBy(x => x.Culture)); } + else + { + result.AddRange(urlGroup); + } + + + } // get the 'other' URLs - ie not what you'd get with GetUrl() but URLs that would route to the document, nevertheless. // for these 'other' URLs, we don't check whether they are routable, collide, anything - we just report them. - foreach (var otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture)) + foreach (UrlInfo otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text) + .ThenBy(x => x.Culture)) { // avoid duplicates if (urls.Add(otherUrl)) @@ -100,7 +151,7 @@ namespace Umbraco.Extensions } /// - /// Tries to return a for each culture for the content while detecting collisions/errors + /// Tries to return a for each culture for the content while detecting collisions/errors /// private static async Task> GetContentUrlsByCultureAsync( IContent content, @@ -151,7 +202,8 @@ namespace Umbraco.Extensions // got a URL, deal with collisions, add URL default: // detect collisions, etc - Attempt hasCollision = await DetectCollisionAsync(logger, content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility); + Attempt hasCollision = await DetectCollisionAsync(logger, content, url, culture, + umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility); if (hasCollision) { result.Add(hasCollision.Result); @@ -168,7 +220,8 @@ namespace Umbraco.Extensions return result; } - private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService) + private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, + ILocalizedTextService textService) { // document has a published version yet its URL is "#" => a parent must be // unpublished, walk up the tree until we find it, and report. @@ -176,27 +229,31 @@ namespace Umbraco.Extensions do { parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null; - } - while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); + } while (parent != null && parent.Published && + (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); if (parent == null) { // oops, internal error return UrlInfo.Message(textService.Localize("content", "parentNotPublishedAnomaly"), culture); } - else if (!parent.Published) + + if (!parent.Published) { // totally not published - return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }), culture); - } - else - { - // culture not published - return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] {parent.Name}), culture); + return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }), + culture); } + + // culture not published + return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] { parent.Name }), + culture); } - private static async Task> DetectCollisionAsync(ILogger logger, IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility) + private static async Task> DetectCollisionAsync(ILogger logger, IContent content, string url, + string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, + ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, + UriUtility uriUtility) { // test for collisions on the 'main' URL var uri = new Uri(url.TrimEnd(Constants.CharArrays.ForwardSlash), UriKind.RelativeOrAbsolute); @@ -207,11 +264,13 @@ namespace Umbraco.Extensions uri = uriUtility.UriToUmbraco(uri); IPublishedRequestBuilder builder = await publishedRouter.CreateRequestAsync(uri); - IPublishedRequest pcr = await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound)); + IPublishedRequest pcr = + await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound)); if (!pcr.HasPublishedContent()) { - var logMsg = nameof(DetectCollisionAsync) + " did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}"; + var logMsg = nameof(DetectCollisionAsync) + + " did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}"; if (pcr.IgnorePublishedContentCollisions) { logger.LogDebug(logMsg, url, uri, culture); @@ -243,14 +302,7 @@ namespace Umbraco.Extensions l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; - var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture); - return Attempt.Succeed(urlInfo); - } - - // collisions with a different culture of the same content can never be routed. - if (!culture.InvariantEquals(pcr.Culture)) - { - var urlInfo = UrlInfo.Message(textService.Localize("content", "routeErrorCannotRoute"), culture); + var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture); return Attempt.Succeed(urlInfo); } diff --git a/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs b/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs new file mode 100644 index 0000000000..86e2988307 --- /dev/null +++ b/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + /// + /// Used to filter historic content versions for cleanup. + /// + public interface IContentVersionCleanupPolicy + { + /// + /// Filters a set of candidates historic content versions for cleanup according to policy settings. + /// + IEnumerable Apply(DateTime asAtDate, IEnumerable items); + } +} diff --git a/src/Umbraco.Core/Services/IContentVersionService.cs b/src/Umbraco.Core/Services/IContentVersionService.cs new file mode 100644 index 0000000000..1f56138c3d --- /dev/null +++ b/src/Umbraco.Core/Services/IContentVersionService.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + public interface IContentVersionService + { + /// + /// Removes historic content versions according to a policy. + /// + IReadOnlyCollection PerformContentVersionCleanup(DateTime asAtDate); + + /// + /// Gets paginated content versions for given content id paginated. + /// + /// Thrown when is invalid. + IEnumerable GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string culture = null); + + /// + /// Updates preventCleanup value for given content version. + /// + void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1); + } +} diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index a1f6c09d5c..57d09077fc 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -59,7 +59,7 @@ namespace Umbraco.Extensions /// public static void RemoveUserGroupPermissions(this IUserService userService, int groupId, params int[] entityIds) { - userService.ReplaceUserGroupPermissions(groupId, new char[] {}, entityIds); + userService.ReplaceUserGroupPermissions(groupId, null, entityIds); } /// @@ -69,7 +69,7 @@ namespace Umbraco.Extensions /// public static void RemoveUserGroupPermissions(this IUserService userService, int groupId) { - userService.ReplaceUserGroupPermissions(groupId, new char[] { }); + userService.ReplaceUserGroupPermissions(groupId, null); } diff --git a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs index a266b52f6b..fff22936a0 100644 --- a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs @@ -32,7 +32,13 @@ namespace Umbraco.Cms.Core.Strings if (content.HasProperty(Constants.Conventions.Content.UrlName)) source = (content.GetValue(Constants.Conventions.Content.UrlName, culture) ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(source)) - source = content.GetCultureName(culture); + { + // If the name of a node has been updated, but it has not been published, the url should use the published name, not the current node name + // If this node has never been published (GetPublishName is null), use the unpublished name + source = (content is IContent document) && document.Edited && document.GetPublishName(culture) != null + ? document.GetPublishName(culture) + : content.GetCultureName(culture); + } return source; } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5996d238bb..9a1da38222 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1,61 +1,65 @@ - - netstandard2.0 - Umbraco.Cms.Core - Umbraco CMS - Umbraco.Cms.Core - Umbraco CMS Core - Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco - Umbraco CMS - + + netstandard2.0 + Umbraco.Cms.Core + Umbraco CMS + Umbraco.Cms.Core + Umbraco CMS Core + Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco + Umbraco CMS + - - bin\Release\Umbraco.Core.xml - + + bin\Release\Umbraco.Core.xml + - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - - + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + + - - - <_Parameter1>Umbraco.Tests - - - <_Parameter1>Umbraco.Tests.Common - - - <_Parameter1>Umbraco.Tests.UnitTests - - - <_Parameter1>Umbraco.Tests.Benchmarks - - - <_Parameter1>Umbraco.Tests.Integration - - - <_Parameter1>DynamicProxyGenAssembly2 - - + + + <_Parameter1>Umbraco.Tests + + + <_Parameter1>Umbraco.Tests.Common + + + <_Parameter1>Umbraco.Tests.UnitTests + + + <_Parameter1>Umbraco.Tests.Benchmarks + + + <_Parameter1>Umbraco.Tests.Integration + + + <_Parameter1>DynamicProxyGenAssembly2 + + - - - + + + + + + + diff --git a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs index a77bee7c44..b8a942edc5 100644 --- a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs +++ b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs @@ -145,9 +145,7 @@ namespace Umbraco.Cms.Infrastructure.Examine totalFound = result.TotalItemCount; - var pagedResult = result.Skip(Convert.ToInt32(pageIndex)); - - return pagedResult; + return result; } private bool BuildQuery(StringBuilder sb, string query, string searchFrom, List fields, string type) diff --git a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs index 2f7cb646a2..5121c32d32 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs @@ -8,8 +8,6 @@ using Examine; using Examine.Lucene; using Examine.Lucene.Providers; using Lucene.Net.Documents; -using Lucene.Net.Index; -using Lucene.Net.Store; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; @@ -103,10 +101,7 @@ namespace Umbraco.Cms.Infrastructure.Examine //remove the original value so we can store it the correct way d.RemoveField(f.Key); - d.Add(new StringField( - f.Key, - f.Value[0].ToString(), - Field.Store.YES)); + d.Add(new StoredField(f.Key, f.Value[0].ToString())); } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 89d6f5ee2c..173c80ea7b 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -166,7 +166,9 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddScoped(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(sp => + new PublishedContentQueryAccessor(sp.GetRequiredService()) + ); builder.Services.AddScoped(factory => { var umbCtx = factory.GetRequiredService(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs index edb8033f3d..6582cfb0c6 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs @@ -39,6 +39,9 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection // register the scheme for media paths builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.SetMediaFileSystem(factory => { IIOHelper ioHelper = factory.GetRequiredService(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index 360e785906..d3ebb28f9c 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -25,6 +25,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 861a05b459..661ed93292 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Hosting; @@ -16,6 +15,7 @@ using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; +using Umbraco.Cms.Infrastructure.Services.Implement; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DependencyInjection @@ -43,6 +43,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs index b311b1f0da..e3839e152b 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs @@ -27,6 +27,19 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection return builder; } + /// + /// Sets the default view content provider + /// + /// The type of the provider. + /// The builder. + /// + public static IUmbracoBuilder SetDefaultViewContentProvider(this IUmbracoBuilder builder) + where T : class, IDefaultViewContentProvider + { + builder.Services.AddUnique(); + return builder; + } + /// /// Sets the culture dictionary factory. /// diff --git a/src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs b/src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs index 4fbbb35a6d..0d881888a1 100644 --- a/src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs +++ b/src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs @@ -194,9 +194,8 @@ namespace Umbraco.Cms.Infrastructure.Examine .Field("nodeType", id.ToInvariantString()) .Execute(QueryOptions.SkipTake(page * pageSize, pageSize)); total = results.TotalItemCount; - var paged = results.Skip(page * pageSize); - - foreach (ISearchResult item in paged) + + foreach (ISearchResult item in results) { if (int.TryParse(item.Id, NumberStyles.Integer, CultureInfo.InvariantCulture, out int contentId)) { diff --git a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs new file mode 100644 index 0000000000..5f3aba5f3f --- /dev/null +++ b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs @@ -0,0 +1,96 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Infrastructure.HostedServices +{ + /// + /// Recurring hosted service that executes the content history cleanup. + /// + public class ContentVersionCleanup : RecurringHostedServiceBase + { + private readonly IRuntimeState _runtimeState; + private readonly ILogger _logger; + private readonly IOptionsMonitor _settingsMonitor; + private readonly IContentVersionService _service; + private readonly IMainDom _mainDom; + private readonly IServerRoleAccessor _serverRoleAccessor; + + /// + /// Initializes a new instance of the class. + /// + public ContentVersionCleanup( + IRuntimeState runtimeState, + ILogger logger, + IOptionsMonitor settingsMonitor, + IContentVersionService service, + IMainDom mainDom, + IServerRoleAccessor serverRoleAccessor) + : base(TimeSpan.FromHours(1), TimeSpan.FromMinutes(3)) + { + _runtimeState = runtimeState; + _logger = logger; + _settingsMonitor = settingsMonitor; + _service = service; + _mainDom = mainDom; + _serverRoleAccessor = serverRoleAccessor; + } + + /// + public override Task PerformExecuteAsync(object state) + { + // Globally disabled by feature flag + if (!_settingsMonitor.CurrentValue.ContentVersionCleanupPolicy.EnableCleanup) + { + _logger.LogInformation("ContentVersionCleanup task will not run as it has been globally disabled via configuration"); + return Task.CompletedTask; + } + + if (_runtimeState.Level != RuntimeLevel.Run) + { + return Task.FromResult(true); // repeat... + } + + // Don't run on replicas nor unknown role servers + switch (_serverRoleAccessor.CurrentServerRole) + { + case ServerRole.Subscriber: + _logger.LogDebug("Does not run on subscriber servers"); + return Task.CompletedTask; + case ServerRole.Unknown: + _logger.LogDebug("Does not run on servers with unknown role"); + return Task.CompletedTask; + case ServerRole.Single: + case ServerRole.SchedulingPublisher: + default: + break; + } + + // Ensure we do not run if not main domain, but do NOT lock it + if (!_mainDom.IsMainDom) + { + _logger.LogDebug("Does not run if not MainDom"); + return Task.FromResult(false); // do NOT repeat, going down + } + + var count = _service.PerformContentVersionCleanup(DateTime.Now).Count; + + if (count > 0) + { + _logger.LogInformation("Deleted {count} ContentVersion(s)", count); + } + else + { + _logger.LogDebug("Task complete, no items were Deleted"); + } + + return Task.FromResult(true); + } + } +} diff --git a/src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs b/src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs index 68a15580e4..00adc5018b 100644 --- a/src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs +++ b/src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs @@ -1,7 +1,18 @@ -using Umbraco.Cms.Infrastructure; - namespace Umbraco.Cms.Core { + /// + /// Not intended for use in background threads where you should make use of + /// and instead resolve IPublishedContentQuery from a + /// e.g. using + /// + /// + /// // Background thread example + /// using UmbracoContextReference _ = _umbracoContextFactory.EnsureUmbracoContext(); + /// using IServiceScope serviceScope = _serviceProvider.CreateScope(); + /// IPublishedContentQuery query = serviceScope.ServiceProvider.GetRequiredService<IPublishedContentQuery>(); + /// + /// + /// public interface IPublishedContentQueryAccessor { bool TryGetValue(out IPublishedContentQuery publishedContentQuery); diff --git a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs index 73b9d702b7..4ef8fa4e28 100644 --- a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs @@ -117,7 +117,8 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps { minCharLength = _passwordConfiguration.RequiredLength, minNonAlphaNumericLength = _passwordConfiguration.GetMinNonAlphaNumericChars(), - quickInstallAvailable = DatabaseConfigureStep.IsSqlCeAvailable() + quickInstallAvailable = DatabaseConfigureStep.IsSqlCeAvailable() || DatabaseConfigureStep.IsLocalDbAvailable(), + customInstallAvailable = !GetInstallState().HasFlag(InstallState.ConnectionStringConfigured) }; } } diff --git a/src/Umbraco.Infrastructure/Mail/EmailSender.cs b/src/Umbraco.Infrastructure/Mail/EmailSender.cs index 098d690345..31b3d67bd0 100644 --- a/src/Umbraco.Infrastructure/Mail/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Mail/EmailSender.cs @@ -1,10 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; +using System.IO; using System.Net.Mail; using System.Threading.Tasks; +using MailKit.Net.Smtp; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using MimeKit; +using MimeKit.IO; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mail; @@ -71,12 +76,56 @@ namespace Umbraco.Cms.Infrastructure.Mail } } - if (_globalSettings.IsSmtpServerConfigured == false) + var isPickupDirectoryConfigured = !string.IsNullOrWhiteSpace(_globalSettings.Smtp?.PickupDirectoryLocation); + + if (_globalSettings.IsSmtpServerConfigured == false && !isPickupDirectoryConfigured) { _logger.LogDebug("Could not send email for {Subject}. It was not handled by a notification handler and there is no SMTP configured.", message.Subject); return; } + if (isPickupDirectoryConfigured && !string.IsNullOrWhiteSpace(_globalSettings.Smtp?.From)) + { + // The following code snippet is the recommended way to handle PickupDirectoryLocation. + // See more https://github.com/jstedfast/MailKit/blob/master/FAQ.md#q-how-can-i-send-email-to-a-specifiedpickupdirectory + do { + var path = Path.Combine(_globalSettings.Smtp?.PickupDirectoryLocation, Guid.NewGuid () + ".eml"); + Stream stream; + + try + { + stream = File.Open(path, FileMode.CreateNew); + } + catch (IOException) + { + if (File.Exists(path)) + { + continue; + } + throw; + } + + try { + using (stream) + { + using var filtered = new FilteredStream(stream); + filtered.Add(new SmtpDataFilter()); + + FormatOptions options = FormatOptions.Default.Clone(); + options.NewLineFormat = NewLineFormat.Dos; + + await message.ToMimeMessage(_globalSettings.Smtp?.From).WriteToAsync(options, filtered); + filtered.Flush(); + return; + + } + } catch { + File.Delete(path); + throw; + } + } while (true); + } + using var client = new SmtpClient(); await client.ConnectAsync(_globalSettings.Smtp.Host, diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs index b36a21b95b..b3db06fd5b 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs @@ -12,21 +12,83 @@ using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Extensions; +using ColumnInfo = Umbraco.Cms.Infrastructure.Persistence.SqlSyntax.ColumnInfo; namespace Umbraco.Cms.Infrastructure.Migrations.Install { /// - /// Creates the initial database schema during install. + /// Creates the initial database schema during install. /// public class DatabaseSchemaCreator { + // all tables, in order + internal static readonly List OrderedTables = new() + { + typeof(UserDto), + typeof(NodeDto), + typeof(ContentTypeDto), + typeof(TemplateDto), + typeof(ContentDto), + typeof(ContentVersionDto), + typeof(MediaVersionDto), + typeof(DocumentDto), + typeof(ContentTypeTemplateDto), + typeof(DataTypeDto), + typeof(DictionaryDto), + typeof(LanguageDto), + typeof(LanguageTextDto), + typeof(DomainDto), + typeof(LogDto), + typeof(MacroDto), + typeof(MacroPropertyDto), + typeof(MemberPropertyTypeDto), + typeof(MemberDto), + typeof(Member2MemberGroupDto), + typeof(PropertyTypeGroupDto), + typeof(PropertyTypeDto), + typeof(PropertyDataDto), + typeof(RelationTypeDto), + typeof(RelationDto), + typeof(TagDto), + typeof(TagRelationshipDto), + typeof(ContentType2ContentTypeDto), + typeof(ContentTypeAllowedContentTypeDto), + typeof(User2NodeNotifyDto), + typeof(ServerRegistrationDto), + typeof(AccessDto), + typeof(AccessRuleDto), + typeof(CacheInstructionDto), + typeof(ExternalLoginDto), + typeof(ExternalLoginTokenDto), + typeof(RedirectUrlDto), + typeof(LockDto), + typeof(UserGroupDto), + typeof(User2UserGroupDto), + typeof(UserGroup2NodePermissionDto), + typeof(UserGroup2AppDto), + typeof(UserStartNodeDto), + typeof(ContentNuDto), + typeof(DocumentVersionDto), + typeof(KeyValueDto), + typeof(UserLoginDto), + typeof(ConsentDto), + typeof(AuditEntryDto), + typeof(ContentVersionCultureVariationDto), + typeof(DocumentCultureVariationDto), + typeof(ContentScheduleDto), + typeof(LogViewerQueryDto), + typeof(ContentVersionCleanupPolicyDto), + typeof(UserGroup2NodeDto) + }; + private readonly IUmbracoDatabase _database; + private readonly IEventAggregator _eventAggregator; private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly IUmbracoVersion _umbracoVersion; - private readonly IEventAggregator _eventAggregator; - public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger, ILoggerFactory loggerFactory, IUmbracoVersion umbracoVersion, IEventAggregator eventAggregator) + public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger, + ILoggerFactory loggerFactory, IUmbracoVersion umbracoVersion, IEventAggregator eventAggregator) { _database = database ?? throw new ArgumentNullException(nameof(database)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -42,74 +104,16 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax; - // all tables, in order - internal static readonly List OrderedTables = new List - { - typeof (UserDto), - typeof (NodeDto), - typeof (ContentTypeDto), - typeof (TemplateDto), - typeof (ContentDto), - typeof (ContentVersionDto), - typeof (MediaVersionDto), - typeof (DocumentDto), - typeof (ContentTypeTemplateDto), - typeof (DataTypeDto), - typeof (DictionaryDto), - typeof (LanguageDto), - typeof (LanguageTextDto), - typeof (DomainDto), - typeof (LogDto), - typeof (MacroDto), - typeof (MacroPropertyDto), - typeof (MemberPropertyTypeDto), - typeof (MemberDto), - typeof (Member2MemberGroupDto), - typeof (PropertyTypeGroupDto), - typeof (PropertyTypeDto), - typeof (PropertyDataDto), - typeof (RelationTypeDto), - typeof (RelationDto), - typeof (TagDto), - typeof (TagRelationshipDto), - typeof (ContentType2ContentTypeDto), - typeof (ContentTypeAllowedContentTypeDto), - typeof (User2NodeNotifyDto), - typeof (ServerRegistrationDto), - typeof (AccessDto), - typeof (AccessRuleDto), - typeof (CacheInstructionDto), - typeof (ExternalLoginDto), - typeof (ExternalLoginTokenDto), - typeof (RedirectUrlDto), - typeof (LockDto), - typeof (UserGroupDto), - typeof (User2UserGroupDto), - typeof (UserGroup2NodePermissionDto), - typeof (UserGroup2AppDto), - typeof (UserStartNodeDto), - typeof (ContentNuDto), - typeof (DocumentVersionDto), - typeof (KeyValueDto), - typeof (UserLoginDto), - typeof (ConsentDto), - typeof (AuditEntryDto), - typeof (ContentVersionCultureVariationDto), - typeof (DocumentCultureVariationDto), - typeof (ContentScheduleDto), - typeof (LogViewerQueryDto) - }; - /// - /// Drops all Umbraco tables in the db. + /// Drops all Umbraco tables in the db. /// internal void UninstallDatabaseSchema() { _logger.LogInformation("Start UninstallDatabaseSchema"); - foreach (var table in OrderedTables.AsEnumerable().Reverse()) + foreach (Type table in OrderedTables.AsEnumerable().Reverse()) { - var tableNameAttribute = table.FirstAttribute(); + TableNameAttribute tableNameAttribute = table.FirstAttribute(); var tableName = tableNameAttribute == null ? table.Name : tableNameAttribute.Value; _logger.LogInformation("Uninstall {TableName}", tableName); @@ -117,7 +121,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install try { if (TableExists(tableName)) + { DropTable(tableName); + } } catch (Exception ex) { @@ -129,13 +135,15 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install } /// - /// Initializes the database by creating the umbraco db schema. + /// Initializes the database by creating the umbraco db schema. /// /// This needs to execute as part of a transaction. public void InitializeDatabaseSchema() { if (!_database.InTransaction) + { throw new InvalidOperationException("Database is not in a transaction."); + } var eventMessages = new EventMessages(); var creatingNotification = new DatabaseSchemaCreatingNotification(eventMessages); @@ -143,22 +151,23 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install if (creatingNotification.Cancel == false) { - var dataCreation = new DatabaseDataCreator(_database, _loggerFactory.CreateLogger(), _umbracoVersion); - foreach (var table in OrderedTables) + var dataCreation = new DatabaseDataCreator(_database, + _loggerFactory.CreateLogger(), _umbracoVersion); + foreach (Type table in OrderedTables) + { CreateTable(false, table, dataCreation); + } } - DatabaseSchemaCreatedNotification createdNotification = new DatabaseSchemaCreatedNotification(eventMessages).WithStateFrom(creatingNotification); + DatabaseSchemaCreatedNotification createdNotification = + new DatabaseSchemaCreatedNotification(eventMessages).WithStateFrom(creatingNotification); FireAfterCreation(createdNotification); } /// - /// Validates the schema of the current database. + /// Validates the schema of the current database. /// - internal DatabaseSchemaResult ValidateSchema() - { - return ValidateSchema(OrderedTables); - } + internal DatabaseSchemaResult ValidateSchema() => ValidateSchema(OrderedTables); internal DatabaseSchemaResult ValidateSchema(IEnumerable orderedTables) { @@ -179,26 +188,30 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install } /// - /// This validates the Primary/Foreign keys in the database + /// This validates the Primary/Foreign keys in the database /// /// /// - /// This does not validate any database constraints that are not PKs or FKs because Umbraco does not create a database with non PK/FK constraints. - /// Any unique "constraints" in the database are done with unique indexes. + /// This does not validate any database constraints that are not PKs or FKs because Umbraco does not create a database + /// with non PK/FK constraints. + /// Any unique "constraints" in the database are done with unique indexes. /// private void ValidateDbConstraints(DatabaseSchemaResult result) { //Check constraints in configured database against constraints in schema var constraintsInDatabase = SqlSyntax.GetConstraintsPerColumn(_database).LegacyDistinctBy(x => x.Item3).ToList(); - var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_")).Select(x => x.Item3).ToList(); - var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_")).Select(x => x.Item3).ToList(); + var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_")) + .Select(x => x.Item3).ToList(); + var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_")) + .Select(x => x.Item3).ToList(); var unknownConstraintsInDatabase = constraintsInDatabase.Where( x => - x.Item3.InvariantStartsWith("FK_") == false && x.Item3.InvariantStartsWith("PK_") == false && - x.Item3.InvariantStartsWith("IX_") == false).Select(x => x.Item3).ToList(); - var foreignKeysInSchema = result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList(); + x.Item3.InvariantStartsWith("FK_") == false && x.Item3.InvariantStartsWith("PK_") == false && + x.Item3.InvariantStartsWith("IX_") == false).Select(x => x.Item3).ToList(); + var foreignKeysInSchema = + result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList(); var primaryKeysInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => y.PrimaryKeyName)) .Where(x => x.IsNullOrWhiteSpace() == false).ToList(); @@ -219,14 +232,17 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install //Foreign keys: - var validForeignKeyDifferences = foreignKeysInDatabase.Intersect(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase); + IEnumerable validForeignKeyDifferences = + foreignKeysInDatabase.Intersect(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase); foreach (var foreignKey in validForeignKeyDifferences) { result.ValidConstraints.Add(foreignKey); } - var invalidForeignKeyDifferences = + + IEnumerable invalidForeignKeyDifferences = foreignKeysInDatabase.Except(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(foreignKeysInSchema.Except(foreignKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); + .Union(foreignKeysInSchema.Except(foreignKeysInDatabase, + StringComparer.InvariantCultureIgnoreCase)); foreach (var foreignKey in invalidForeignKeyDifferences) { result.Errors.Add(new Tuple("Constraint", foreignKey)); @@ -236,37 +252,43 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install //Primary keys: //Add valid and invalid primary key differences to the result object - var validPrimaryKeyDifferences = primaryKeysInDatabase.Intersect(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase); + IEnumerable validPrimaryKeyDifferences = + primaryKeysInDatabase.Intersect(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase); foreach (var primaryKey in validPrimaryKeyDifferences) { result.ValidConstraints.Add(primaryKey); } - var invalidPrimaryKeyDifferences = + + IEnumerable invalidPrimaryKeyDifferences = primaryKeysInDatabase.Except(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(primaryKeysInSchema.Except(primaryKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); + .Union(primaryKeysInSchema.Except(primaryKeysInDatabase, + StringComparer.InvariantCultureIgnoreCase)); foreach (var primaryKey in invalidPrimaryKeyDifferences) { result.Errors.Add(new Tuple("Constraint", primaryKey)); } - } private void ValidateDbColumns(DatabaseSchemaResult result) { //Check columns in configured database against columns in schema - var columnsInDatabase = SqlSyntax.GetColumnsInSchema(_database); - var columnsPerTableInDatabase = columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList(); - var columnsPerTableInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList(); + IEnumerable columnsInDatabase = SqlSyntax.GetColumnsInSchema(_database); + var columnsPerTableInDatabase = + columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList(); + var columnsPerTableInSchema = result.TableDefinitions + .SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList(); //Add valid and invalid column differences to the result object - var validColumnDifferences = columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase); + IEnumerable validColumnDifferences = + columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase); foreach (var column in validColumnDifferences) { result.ValidColumns.Add(column); } - var invalidColumnDifferences = + IEnumerable invalidColumnDifferences = columnsPerTableInDatabase.Except(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(columnsPerTableInSchema.Except(columnsPerTableInDatabase, StringComparer.InvariantCultureIgnoreCase)); + .Union(columnsPerTableInSchema.Except(columnsPerTableInDatabase, + StringComparer.InvariantCultureIgnoreCase)); foreach (var column in invalidColumnDifferences) { result.Errors.Add(new Tuple("Column", column)); @@ -279,15 +301,16 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install var tablesInDatabase = SqlSyntax.GetTablesInSchema(_database).ToList(); var tablesInSchema = result.TableDefinitions.Select(x => x.Name).ToList(); //Add valid and invalid table differences to the result object - var validTableDifferences = tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase); + IEnumerable validTableDifferences = + tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase); foreach (var tableName in validTableDifferences) { result.ValidTables.Add(tableName); } - var invalidTableDifferences = + IEnumerable invalidTableDifferences = tablesInDatabase.Except(tablesInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase)); + .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase)); foreach (var tableName in invalidTableDifferences) { result.Errors.Add(new Tuple("Table", tableName)); @@ -302,15 +325,16 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); //Add valid and invalid index differences to the result object - var validColIndexDifferences = colIndexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); + IEnumerable validColIndexDifferences = + colIndexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); foreach (var index in validColIndexDifferences) { result.ValidIndexes.Add(index); } - var invalidColIndexDifferences = + IEnumerable invalidColIndexDifferences = colIndexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(indexesInSchema.Except(colIndexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); + .Union(indexesInSchema.Except(colIndexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); foreach (var index in invalidColIndexDifferences) { result.Errors.Add(new Tuple("Index", index)); @@ -320,14 +344,14 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install #region Notifications /// - /// Publishes the notification. + /// Publishes the notification. /// /// Cancelable notification marking the creation having begun. internal virtual void FireBeforeCreation(DatabaseSchemaCreatingNotification notification) => _eventAggregator.Publish(notification); /// - /// Publishes the notification. + /// Publishes the notification. /// /// Notification marking the creation having completed. internal virtual void FireAfterCreation(DatabaseSchemaCreatedNotification notification) => @@ -338,30 +362,27 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install #region Utilities /// - /// Returns whether a table with the specified exists in the database. + /// Returns whether a table with the specified exists in the database. /// /// The name of the table. /// true if the table exists; otherwise false. /// - /// + /// /// if (schemaHelper.TableExist("MyTable")) /// { /// // do something when the table exists /// } /// /// - public bool TableExists(string tableName) - { - return SqlSyntax.DoesTableExist(_database, tableName); - } + public bool TableExists(string tableName) => SqlSyntax.DoesTableExist(_database, tableName); /// - /// Returns whether the table for the specified exists in the database. + /// Returns whether the table for the specified exists in the database. /// /// The type representing the DTO/table. /// true if the table exists; otherwise false. /// - /// + /// /// if (schemaHelper.TableExist<MyDto>) /// { /// // do something when the table exists @@ -369,66 +390,67 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install /// /// /// - /// If has been decorated with an , the name from that - /// attribute will be used for the table name. If the attribute is not present, the name - /// will be used instead. + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. /// public bool TableExists() { - var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + TableDefinition table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); return table != null && TableExists(table.Name); } /// - /// Creates a new table in the database based on the type of . + /// Creates a new table in the database based on the type of . /// /// The type representing the DTO/table. /// Whether the table should be overwritten if it already exists. /// - /// If has been decorated with an , the name from that - /// attribute will be used for the table name. If the attribute is not present, the name - /// will be used instead. - /// - /// If a table with the same name already exists, the parameter will determine - /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will - /// not do anything if the parameter is false. + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. /// internal void CreateTable(bool overwrite = false) where T : new() { - var tableType = typeof(T); - CreateTable(overwrite, tableType, new DatabaseDataCreator(_database, _loggerFactory.CreateLogger(), _umbracoVersion)); + Type tableType = typeof(T); + CreateTable(overwrite, tableType, + new DatabaseDataCreator(_database, _loggerFactory.CreateLogger(), + _umbracoVersion)); } /// - /// Creates a new table in the database for the specified . + /// Creates a new table in the database for the specified . /// /// Whether the table should be overwritten if it already exists. /// The representing the table. /// /// - /// If has been decorated with an , the name from - /// that attribute will be used for the table name. If the attribute is not present, the name - /// will be used instead. - /// - /// If a table with the same name already exists, the parameter will determine - /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will - /// not do anything if the parameter is false. - /// - /// This need to execute as part of a transaction. + /// If has been decorated with an , the name from + /// that attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. + /// This need to execute as part of a transaction. /// internal void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation) { if (!_database.InTransaction) + { throw new InvalidOperationException("Database is not in a transaction."); + } - var tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax); + TableDefinition tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax); var tableName = tableDefinition.Name; var createSql = SqlSyntax.Format(tableDefinition); var createPrimaryKeySql = SqlSyntax.FormatPrimaryKey(tableDefinition); - var foreignSql = SqlSyntax.Format(tableDefinition.ForeignKeys); - var indexSql = SqlSyntax.Format(tableDefinition.Indexes); + List foreignSql = SqlSyntax.Format(tableDefinition.ForeignKeys); + List indexSql = SqlSyntax.Format(tableDefinition.Indexes); var tableExist = TableExists(tableName); if (overwrite && tableExist) @@ -458,7 +480,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install } if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + { _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} ON ")); + } //Call the NewTable-event to trigger the insert of base/default data //OnNewTable(tableName, _db, e, _logger); @@ -466,7 +490,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install dataCreation.InitializeBaseData(tableName); if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + { _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} OFF;")); + } //Loop through index statements and execute sql foreach (var sql in indexSql) @@ -489,23 +515,22 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install else { _logger.LogInformation("New table {TableName} was created", tableName); - } } /// - /// Drops the table for the specified . + /// Drops the table for the specified . /// /// The type representing the DTO/table. /// - /// + /// /// schemaHelper.DropTable<MyDto>); /// /// /// - /// If has been decorated with an , the name from that - /// attribute will be used for the table name. If the attribute is not present, the name - /// will be used instead. + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. /// public void DropTable(string tableName) { diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 277a236e24..836981e73b 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.Common; @@ -12,47 +13,89 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_6_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_7_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_9_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_1_0; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_2_0; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade { /// - /// Represents the Umbraco CMS migration plan. + /// Represents the Umbraco CMS migration plan. /// /// public class UmbracoPlan : MigrationPlan { - private readonly IUmbracoVersion _umbracoVersion; private const string InitPrefix = "{init-"; private const string InitSuffix = "}"; + private readonly IUmbracoVersion _umbracoVersion; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The Umbraco version. public UmbracoPlan(IUmbracoVersion umbracoVersion) - : base(Core.Constants.Conventions.Migrations.UmbracoUpgradePlanName) + : base(Constants.Conventions.Migrations.UmbracoUpgradePlanName) { _umbracoVersion = umbracoVersion; DefinePlan(); } + /// + /// + /// The default initial state in plans is string.Empty. + /// + /// When upgrading from version 7, we want to use specific initial states + /// that are e.g. "{init-7.9.3}", "{init-7.11.1}", etc. so we can chain the proper + /// migrations. + /// + /// + /// This is also where we detect the current version, and reject invalid + /// upgrades (from a tool old version, or going back in time, etc). + /// + /// + public override string InitialState + { + get + { + SemVersion currentVersion = _umbracoVersion.SemanticVersion; + + // only from 8.0.0 and above + var minVersion = new SemVersion(8); + if (currentVersion < minVersion) + { + throw new InvalidOperationException( + $"Version {currentVersion} cannot be migrated to {_umbracoVersion.SemanticVersion}." + + $" Please upgrade first to at least {minVersion}."); + } + + // Force versions between 7.14.*-7.15.* into into 7.14 initial state. Because there is no db-changes, + // and we don't want users to workaround my putting in version 7.14.0 them self. + if (minVersion <= currentVersion && currentVersion < new SemVersion(7, 16)) + { + return GetInitState(minVersion); + } + + // initial state is eg "{init-7.14.0}" + return GetInitState(currentVersion); + } + } + /// - /// Gets the initial state corresponding to a version. + /// Gets the initial state corresponding to a version. /// /// The version. /// - /// The initial state. + /// The initial state. /// private static string GetInitState(SemVersion version) => InitPrefix + version + InitSuffix; /// - /// Tries to extract a version from an initial state. + /// Tries to extract a version from an initial state. /// /// The state. /// The version. /// - /// true when the state contains a version; otherwise, false.D + /// true when the state contains a version; otherwise, false.D /// private static bool TryGetInitStateVersion(string state, out string version) { @@ -66,51 +109,21 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade return false; } - /// - /// - /// The default initial state in plans is string.Empty. - /// When upgrading from version 7, we want to use specific initial states - /// that are e.g. "{init-7.9.3}", "{init-7.11.1}", etc. so we can chain the proper - /// migrations. - /// This is also where we detect the current version, and reject invalid - /// upgrades (from a tool old version, or going back in time, etc). - /// - public override string InitialState - { - get - { - var currentVersion = _umbracoVersion.SemanticVersion; - - // only from 8.0.0 and above - var minVersion = new SemVersion(8, 0); - if (currentVersion < minVersion) - throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {_umbracoVersion.SemanticVersion}." - + $" Please upgrade first to at least {minVersion}."); - - // Force versions between 7.14.*-7.15.* into into 7.14 initial state. Because there is no db-changes, - // and we don't want users to workaround my putting in version 7.14.0 them self. - if (minVersion <= currentVersion && currentVersion < new SemVersion(7, 16)) - return GetInitState(minVersion); - - // initial state is eg "{init-7.14.0}" - return GetInitState(currentVersion); - } - } - /// public override void ThrowOnUnknownInitialState(string state) { if (TryGetInitStateVersion(state, out var initVersion)) { - throw new InvalidOperationException($"Version {_umbracoVersion.SemanticVersion} does not support migrating from {initVersion}." - + $" Please verify which versions support migrating from {initVersion}."); + throw new InvalidOperationException( + $"Version {_umbracoVersion.SemanticVersion} does not support migrating from {initVersion}." + + $" Please verify which versions support migrating from {initVersion}."); } base.ThrowOnUnknownInitialState(state); } /// - /// Defines the plan. + /// Defines the plan. /// protected void DefinePlan() { @@ -131,7 +144,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade // plan starts at 7.14.0 (anything before 7.14.0 is not supported) - From(GetInitState(new SemVersion(7, 14, 0))); + From(GetInitState(new SemVersion(7, 14))); // begin migrating from v7 - remove all keys and indexes To("{B36B9ABD-374E-465B-9C5F-26AB0D39326F}"); @@ -146,14 +159,15 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade To("{9DF05B77-11D1-475C-A00A-B656AF7E0908}"); To("{6FE3EF34-44A0-4992-B379-B40BC4EF1C4D}"); To("{7F59355A-0EC9-4438-8157-EB517E6D2727}"); - ToWithReplace("{941B2ABA-2D06-4E04-81F5-74224F1DB037}", "{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}"); // kill AddVariationTable1 + ToWithReplace("{941B2ABA-2D06-4E04-81F5-74224F1DB037}", + "{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}"); // kill AddVariationTable1 To("{A7540C58-171D-462A-91C5-7A9AA5CB8BFD}"); Merge() .To("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}") - .With() + .With() .To("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}") - .As("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); + .As("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); To("{1350617A-4930-4D61-852F-E3AA9E692173}"); To("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); @@ -175,9 +189,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade Merge() .To("{CDBEDEE4-9496-4903-9CF2-4104E00FF960}") - .With() + .With() .To("{940FD19A-00A8-4D5C-B8FF-939143585726}") - .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); + .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); To("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}"); To("{DF470D86-E5CA-42AC-9780-9D28070E25F9}"); @@ -219,38 +233,40 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade // so we need to ensure that migrations from 8.15 are included in the next // v9*. - Merge() - // to 8.15.0 - .To("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}") - .To("{4695D0C9-0729-4976-985B-048D503665D8}") - .To("{5C424554-A32D-4852-8ED1-A13508187901}") - .With() - // to 9.0.0 RC1 - .To("{22D801BA-A1FF-4539-BFCC-2139B55594F8}") - .To("{50A43237-A6F4-49E2-A7A6-5DAD65C84669}") - .To("{3D8DADEF-0FDA-4377-A5F0-B52C2110E8F2}") - .To("{1303BDCF-2295-4645-9526-2F32E8B35ABD}") - .To("{86AC839A-0D08-4D09-B7B5-027445E255A1}") - .As("{5060F3D2-88BE-4D30-8755-CF51F28EAD12}"); + // to 8.15.0 + To("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}"); + To("{4695D0C9-0729-4976-985B-048D503665D8}"); + To("{5C424554-A32D-4852-8ED1-A13508187901}"); - Merge() - // to 8.17.0 - .To("{153865E9-7332-4C2A-9F9D-F20AEE078EC7}") - .With() - // This should be safe to execute again. We need it with a new name to ensure updates from all the following has executed this step. - // - 8.15.0 RC - Current state: {4695D0C9-0729-4976-985B-048D503665D8} - // - 8.15.0 Final - Current state: {5C424554-A32D-4852-8ED1-A13508187901} - // - 9.0.0 RC1 - Current state: {5060F3D2-88BE-4D30-8755-CF51F28EAD12} - .To("{622E5172-42E1-4662-AD80-9504AF5A4E53}") - .To("{10F7BB61-C550-426B-830B-7F954F689CDF}") - .To("{12DCDE7F-9AB7-4617-804F-AB66BF360980}") - .As("{5AAE6276-80DB-4ACF-B845-199BC6C37538}"); + // to 8.17.0 + To("{153865E9-7332-4C2A-9F9D-F20AEE078EC7}"); + + // This should be safe to execute again. We need it with a new name to ensure updates from all the following has executed this step. + // - 8.15.0 RC - Current state: {4695D0C9-0729-4976-985B-048D503665D8} + // - 8.15.0 Final - Current state: {5C424554-A32D-4852-8ED1-A13508187901} + // - 9.0.0 RC1 - Current state: {5060F3D2-88BE-4D30-8755-CF51F28EAD12} + To("{622E5172-42E1-4662-AD80-9504AF5A4E53}"); + To("{10F7BB61-C550-426B-830B-7F954F689CDF}"); + To("{5AAE6276-80DB-4ACF-B845-199BC6C37538}"); + + // to 9.0.0 RC1 + To("{22D801BA-A1FF-4539-BFCC-2139B55594F8}"); + To("{50A43237-A6F4-49E2-A7A6-5DAD65C84669}"); + To("{3D8DADEF-0FDA-4377-A5F0-B52C2110E8F2}"); + To("{1303BDCF-2295-4645-9526-2F32E8B35ABD}"); + To("{5060F3D2-88BE-4D30-8755-CF51F28EAD12}"); + To( + "{A2686B49-A082-4B22-97FD-AAB154D46A57}"); // Re-run this migration to make sure it has executed to account for migrations going out of sync between versions. // TO 9.0.0-rc4 - To("5E02F241-5253-403D-B5D3-7DB00157E20F"); + To( + "5E02F241-5253-403D-B5D3-7DB00157E20F"); // Jaddie: This GUID is missing the { }, although this likely can't be changed now as it will break installs going forwards - // TO 9.0.0 + // TO 9.1.0 + To("{8BAF5E6C-DCB7-41AE-824F-4215AE4F1F98}"); + // TO 9.2.0 + To("{0571C395-8F0B-44E9-8E3F-47BDD08D817B}"); } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_1_0/AddContentVersionCleanupFeature.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_1_0/AddContentVersionCleanupFeature.cs new file mode 100644 index 0000000000..aa0d4472e8 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_1_0/AddContentVersionCleanupFeature.cs @@ -0,0 +1,28 @@ +using Umbraco.Cms.Infrastructure.Migrations; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_1_0 +{ + class AddContentVersionCleanupFeature : MigrationBase + { + public AddContentVersionCleanupFeature(IMigrationContext context) + : base(context) { } + + /// + /// The conditionals are useful to enable the same migration to be used in multiple + /// migration paths x.x -> 8.18 and x.x -> 9.x + /// + protected override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database); + if (!tables.InvariantContains(ContentVersionCleanupPolicyDto.TableName)) + { + Create.Table().Do(); + } + + var columns = SqlSyntax.GetColumnsInSchema(Context.Database); + AddColumnIfNotExists(columns, "preventCleanup"); + } + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/AddUserGroup2NodeTable.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/AddUserGroup2NodeTable.cs new file mode 100644 index 0000000000..41692825d3 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/AddUserGroup2NodeTable.cs @@ -0,0 +1,30 @@ +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_2_0 +{ + class AddUserGroup2NodeTable : MigrationBase + { + public AddUserGroup2NodeTable(IMigrationContext context) + : base(context) { } + + protected override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database); + if (!tables.InvariantContains(UserGroup2NodeDto.TableName)) + { + Create.Table().Do(); + } + + // Insert if there exists specific permissions today. Can't do it directly in db in any nice way. + var allData = Database.Fetch(); + var toInsert = allData.Select(x => new UserGroup2NodeDto() { NodeId = x.NodeId, UserGroupId = x.UserGroupId }).Distinct( + new DelegateEqualityComparer( + (x, y) => x.NodeId == y.NodeId && x.UserGroupId == y.UserGroupId, + x => x.NodeId.GetHashCode() + x.UserGroupId.GetHashCode())).ToArray(); + Database.InsertBulk(toInsert); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionCleanupPolicyDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionCleanupPolicyDto.cs new file mode 100644 index 0000000000..4b2faa166f --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionCleanupPolicyDto.cs @@ -0,0 +1,34 @@ +using System; +using System.Data; +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; + +namespace Umbraco.Cms.Infrastructure.Persistence.Dtos +{ + [TableName(TableName)] + [PrimaryKey("contentTypeId", AutoIncrement = false)] + [ExplicitColumns] + internal class ContentVersionCleanupPolicyDto + { + public const string TableName = Constants.DatabaseSchema.Tables.ContentVersionCleanupPolicy; + + [Column("contentTypeId")] + [ForeignKey(typeof(ContentTypeDto), Column = "nodeId", OnDelete = Rule.Cascade)] + public int ContentTypeId { get; set; } + + [Column("preventCleanup")] + public bool PreventCleanup { get; set; } + + [Column("keepAllVersionsNewerThanDays")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? KeepAllVersionsNewerThanDays { get; set; } + + [Column("keepLatestVersionPerDayForDays")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? KeepLatestVersionPerDayForDays { get; set; } + + [Column("updated")] + public DateTime Updated { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs index 53e90859d9..63f2802af6 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs @@ -49,5 +49,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos [ResultColumn] [Reference(ReferenceType.OneToOne, ColumnName = "NodeId", ReferenceMemberName = "NodeId")] public ContentDto ContentDto { get; set; } + + [Column("preventCleanup")] + [Constraint(Default = "0")] + public bool PreventCleanup { get; set; } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/UserGroup2NodeDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/UserGroup2NodeDto.cs new file mode 100644 index 0000000000..ad172c846c --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/UserGroup2NodeDto.cs @@ -0,0 +1,23 @@ +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; + +namespace Umbraco.Cms.Infrastructure.Persistence.Dtos +{ + [TableName(TableName)] + [ExplicitColumns] + internal class UserGroup2NodeDto + { + public const string TableName = Constants.DatabaseSchema.Tables.UserGroup2Node; + + [Column("userGroupId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_" + TableName, OnColumns = "userGroupId, nodeId")] + [ForeignKey(typeof(UserGroupDto))] + public int UserGroupId { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(NodeDto))] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_nodeId")] + public int NodeId { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs index 443032c67a..f07867cccc 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs @@ -1,38 +1,43 @@ using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Data.SqlClient; using System.Linq; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Infrastructure.Persistence; namespace Umbraco.Extensions { /// - /// Provides extension methods to NPoco Database class. + /// Provides extension methods to NPoco Database class. /// public static partial class NPocoDatabaseExtensions { /// - /// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the underlying RetryDbConnection and ProfiledDbTransaction + /// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the + /// underlying RetryDbConnection and ProfiledDbTransaction /// /// - /// This is required to use NPoco's own method because we use wrapped DbConnection and DbTransaction instances. - /// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for bulk inserting of records for - /// any other database type and in which case will just insert records one at a time. - /// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own BulkInsertRecords methods - /// do not handle this scenario. + /// This is required to use NPoco's own method because we use + /// wrapped DbConnection and DbTransaction instances. + /// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for + /// bulk inserting of records for + /// any other database type and in which case will just insert records one at a time. + /// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own + /// BulkInsertRecords methods + /// do not handle this scenario. /// public static void ConfigureNPocoBulkExtensions() { - SqlBulkCopyHelper.SqlConnectionResolver = dbConn => GetTypedConnection(dbConn); SqlBulkCopyHelper.SqlTransactionResolver = dbTran => GetTypedTransaction(dbTran); } /// - /// Creates bulk-insert commands. + /// Creates bulk-insert commands. /// /// The type of the records. /// The database. @@ -40,17 +45,22 @@ namespace Umbraco.Extensions /// The sql commands to execute. internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase database, T[] records) { - if (database?.Connection == null) throw new ArgumentException("Null database?.connection.", nameof(database)); + if (database?.Connection == null) + { + throw new ArgumentException("Null database?.connection.", nameof(database)); + } - var pocoData = database.PocoDataFactory.ForType(typeof(T)); + PocoData pocoData = database.PocoDataFactory.ForType(typeof(T)); // get columns to include, = number of parameters per row - var columns = pocoData.Columns.Where(c => IncludeColumn(pocoData, c)).ToArray(); + KeyValuePair[] columns = + pocoData.Columns.Where(c => IncludeColumn(pocoData, c)).ToArray(); var paramsPerRecord = columns.Length; // format columns to sql var tableName = database.DatabaseType.EscapeTableName(pocoData.TableInfo.TableName); - var columnNames = string.Join(", ", columns.Select(c => tableName + "." + database.DatabaseType.EscapeSqlIdentifier(c.Key))); + var columnNames = string.Join(", ", + columns.Select(c => tableName + "." + database.DatabaseType.EscapeSqlIdentifier(c.Key))); // example: // assume 4168 records, each record containing 8 fields, ie 8 command parameters @@ -58,7 +68,9 @@ namespace Umbraco.Extensions // Math.Floor(2100 / 8) = 262 record per command // 4168 / 262 = 15.908... = there will be 16 command in total // (if we have disabled db parameters, then all records will be included, in only one command) - var recordsPerCommand = paramsPerRecord == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor(2000.00 / paramsPerRecord)); + var recordsPerCommand = paramsPerRecord == 0 + ? int.MaxValue + : Convert.ToInt32(Math.Floor((double)Constants.Sql.MaxParameterCount / paramsPerRecord)); var commandsCount = Convert.ToInt32(Math.Ceiling((double)records.Length / recordsPerCommand)); var commands = new IDbCommand[commandsCount]; @@ -67,23 +79,27 @@ namespace Umbraco.Extensions var prefix = database.DatabaseType.GetParameterPrefix(database.ConnectionString); for (var commandIndex = 0; commandIndex < commandsCount; commandIndex++) { - var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty); + DbCommand command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty); var parameterIndex = 0; var commandRecords = Math.Min(recordsPerCommand, recordsLeftToInsert); var recordsValues = new string[commandRecords]; - for (var commandRecordIndex = 0; commandRecordIndex < commandRecords; commandRecordIndex++, recordsIndex++, recordsLeftToInsert--) + for (var commandRecordIndex = 0; + commandRecordIndex < commandRecords; + commandRecordIndex++, recordsIndex++, recordsLeftToInsert--) { - var record = records[recordsIndex]; + T record = records[recordsIndex]; var recordValues = new string[columns.Length]; for (var columnIndex = 0; columnIndex < columns.Length; columnIndex++) { database.AddParameter(command, columns[columnIndex].Value.GetValue(record)); recordValues[columnIndex] = prefix + parameterIndex++; } + recordsValues[commandRecordIndex] = "(" + string.Join(",", recordValues) + ")"; } - command.CommandText = $"INSERT INTO {tableName} ({columnNames}) VALUES {string.Join(", ", recordsValues)}"; + command.CommandText = + $"INSERT INTO {tableName} ({columnNames}) VALUES {string.Join(", ", recordsValues)}"; commands[commandIndex] = command; } @@ -91,19 +107,14 @@ namespace Umbraco.Extensions } /// - /// Determines whether a column should be part of a bulk-insert. + /// Determines whether a column should be part of a bulk-insert. /// /// The PocoData object corresponding to the record's type. /// The column. /// A value indicating whether the column should be part of the bulk-insert. /// Columns that are primary keys and auto-incremental, or result columns, are excluded from bulk-inserts. - public static bool IncludeColumn(PocoData pocoData, KeyValuePair column) - { - return column.Value.ResultColumn == false - && (pocoData.TableInfo.AutoIncrement == false || column.Key != pocoData.TableInfo.PrimaryKey); - } - - - + public static bool IncludeColumn(PocoData pocoData, KeyValuePair column) => + column.Value.ResultColumn == false + && (pocoData.TableInfo.AutoIncrement == false || column.Key != pocoData.TableInfo.PrimaryKey); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs index 16c411c772..e3685dd32c 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -16,26 +17,47 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// Represents the NPoco implementation of . + /// Represents the NPoco implementation of . /// internal class AuditEntryRepository : EntityRepositoryBase, IAuditEntryRepository { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public AuditEntryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) - { } + { + } + + /// + public IEnumerable GetPage(long pageIndex, int pageCount, out long records) + { + Sql sql = Sql() + .Select() + .From() + .OrderByDescending(x => x.EventDateUtc); + + Page page = Database.Page(pageIndex + 1, pageCount, sql); + records = page.TotalItems; + return page.Items.Select(AuditEntryFactory.BuildEntity); + } + + /// + public bool IsAvailable() + { + var tables = SqlSyntax.GetTablesInSchema(Database).ToArray(); + return tables.InvariantContains(Constants.DatabaseSchema.Tables.AuditEntry); + } /// protected override IAuditEntry PerformGet(int id) { - var sql = Sql() + Sql sql = Sql() .Select() .From() .Where(x => x.Id == id); - var dto = Database.FirstOrDefault(sql); + AuditEntryDto dto = Database.FirstOrDefault(sql); return dto == null ? null : AuditEntryFactory.BuildEntity(dto); } @@ -44,7 +66,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { if (ids.Length == 0) { - var sql = Sql() + Sql sql = Sql() .Select() .From(); @@ -53,9 +75,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var entries = new List(); - foreach (var group in ids.InGroupsOf(2000)) + foreach (IEnumerable group in ids.InGroupsOf(Constants.Sql.MaxParameterCount)) { - var sql = Sql() + Sql sql = Sql() .Select() .From() .WhereIn(x => x.Id, group); @@ -69,68 +91,41 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement /// protected override IEnumerable PerformGetByQuery(IQuery query) { - var sqlClause = GetBaseQuery(false); + Sql sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); + Sql sql = translator.Translate(); return Database.Fetch(sql).Select(AuditEntryFactory.BuildEntity); } /// protected override Sql GetBaseQuery(bool isCount) { - var sql = Sql(); + Sql sql = Sql(); sql = isCount ? sql.SelectCount() : sql.Select(); sql = sql.From(); return sql; } /// - protected override string GetBaseWhereClause() - { - return $"{Cms.Core.Constants.DatabaseSchema.Tables.AuditEntry}.id = @id"; - } + protected override string GetBaseWhereClause() => $"{Constants.DatabaseSchema.Tables.AuditEntry}.id = @id"; /// - protected override IEnumerable GetDeleteClauses() - { + protected override IEnumerable GetDeleteClauses() => throw new NotSupportedException("Audit entries cannot be deleted."); - } /// protected override void PersistNewItem(IAuditEntry entity) { entity.AddingEntity(); - var dto = AuditEntryFactory.BuildDto(entity); + AuditEntryDto dto = AuditEntryFactory.BuildDto(entity); Database.Insert(dto); entity.Id = dto.Id; entity.ResetDirtyProperties(); } /// - protected override void PersistUpdatedItem(IAuditEntry entity) - { + protected override void PersistUpdatedItem(IAuditEntry entity) => throw new NotSupportedException("Audit entries cannot be updated."); - } - - /// - public IEnumerable GetPage(long pageIndex, int pageCount, out long records) - { - var sql = Sql() - .Select() - .From() - .OrderByDescending(x => x.EventDateUtc); - - var page = Database.Page(pageIndex + 1, pageCount, sql); - records = page.TotalItems; - return page.Items.Select(AuditEntryFactory.BuildEntity); - } - - /// - public bool IsAvailable() - { - var tables = SqlSyntax.GetTablesInSchema(Database).ToArray(); - return tables.InvariantContains(Cms.Core.Constants.DatabaseSchema.Tables.AuditEntry); - } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 239f0a89ed..fd4d1c33b9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -657,7 +657,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement // in the table? // get all PropertyDataDto for all definitions / versions - var allPropertyDataDtos = Database.FetchByGroups(versions, 2000, batch => + var allPropertyDataDtos = Database.FetchByGroups(versions, Constants.Sql.MaxParameterCount, batch => SqlContext.Sql() .Select() .From() @@ -666,7 +666,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement // get PropertyDataDto distinct PropertyTypeDto var allPropertyTypeIds = allPropertyDataDtos.Select(x => x.PropertyTypeId).Distinct().ToList(); - var allPropertyTypeDtos = Database.FetchByGroups(allPropertyTypeIds, 2000, batch => + var allPropertyTypeDtos = Database.FetchByGroups(allPropertyTypeIds, Constants.Sql.MaxParameterCount, batch => SqlContext.Sql() .Select(r => r.Select(x => x.DataTypeDto)) .From() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index e171a3a54f..2a14a745c9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -6,31 +6,35 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Extensions; +using Enumerable = System.Linq.Enumerable; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// Implements . + /// Implements . /// internal class ContentTypeCommonRepository : IContentTypeCommonRepository { - private const string CacheKey = "Umbraco.Core.Persistence.Repositories.Implement.ContentTypeCommonRepository::AllTypes"; + private const string CacheKey = + "Umbraco.Core.Persistence.Repositories.Implement.ContentTypeCommonRepository::AllTypes"; private readonly AppCaches _appCaches; - private readonly IShortStringHelper _shortStringHelper; private readonly IScopeAccessor _scopeAccessor; + private readonly IShortStringHelper _shortStringHelper; private readonly ITemplateRepository _templateRepository; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public ContentTypeCommonRepository(IScopeAccessor scopeAccessor, ITemplateRepository templateRepository, AppCaches appCaches, IShortStringHelper shortStringHelper) + public ContentTypeCommonRepository(IScopeAccessor scopeAccessor, ITemplateRepository templateRepository, + AppCaches appCaches, IShortStringHelper shortStringHelper) { _scopeAccessor = scopeAccessor; _templateRepository = templateRepository; @@ -40,83 +44,98 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement private IScope AmbientScope => _scopeAccessor.AmbientScope; private IUmbracoDatabase Database => AmbientScope.Database; + private ISqlContext SqlContext => AmbientScope.SqlContext; - private Sql Sql() => SqlContext.Sql(); //private Sql Sql(string sql, params object[] args) => SqlContext.Sql(sql, args); //private ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax; //private IQuery Query() => SqlContext.Query(); /// - public IEnumerable GetAllTypes() - { + public IEnumerable GetAllTypes() => // use a 5 minutes sliding cache - same as FullDataSet cache policy - return _appCaches.RuntimeCache.GetCacheItem(CacheKey, GetAllTypesInternal, TimeSpan.FromMinutes(5), true); - } + _appCaches.RuntimeCache.GetCacheItem(CacheKey, GetAllTypesInternal, TimeSpan.FromMinutes(5), true); /// - public void ClearCache() - { - _appCaches.RuntimeCache.Clear(CacheKey); - } + public void ClearCache() => _appCaches.RuntimeCache.Clear(CacheKey); + + private Sql Sql() => SqlContext.Sql(); private IEnumerable GetAllTypesInternal() { var contentTypes = new Dictionary(); // get content types - var sql1 = Sql() + Sql sql1 = Sql() .Select(r => r.Select(x => x.NodeDto)) .From() .InnerJoin().On((ct, n) => ct.NodeId == n.NodeId) .OrderBy(x => x.NodeId); - var contentTypeDtos = Database.Fetch(sql1); + List contentTypeDtos = Database.Fetch(sql1); // get allowed content types - var sql2 = Sql() + Sql sql2 = Sql() .Select() .From() .OrderBy(x => x.Id); - var allowedDtos = Database.Fetch(sql2); + List allowedDtos = Database.Fetch(sql2); // prepare // note: same alias could be used for media, content... but always different ids = ok - var aliases = contentTypeDtos.ToDictionary(x => x.NodeId, x => x.Alias); + var aliases = Enumerable.ToDictionary(contentTypeDtos, x => x.NodeId, x => x.Alias); // create var allowedDtoIx = 0; - foreach (var contentTypeDto in contentTypeDtos) + foreach (ContentTypeDto contentTypeDto in contentTypeDtos) { // create content type IContentTypeComposition contentType; - if (contentTypeDto.NodeDto.NodeObjectType == Cms.Core.Constants.ObjectTypes.MediaType) + if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MediaType) + { contentType = ContentTypeFactory.BuildMediaTypeEntity(_shortStringHelper, contentTypeDto); - else if (contentTypeDto.NodeDto.NodeObjectType == Cms.Core.Constants.ObjectTypes.DocumentType) + } + else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.DocumentType) + { contentType = ContentTypeFactory.BuildContentTypeEntity(_shortStringHelper, contentTypeDto); - else if (contentTypeDto.NodeDto.NodeObjectType == Cms.Core.Constants.ObjectTypes.MemberType) + } + else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType) + { contentType = ContentTypeFactory.BuildMemberTypeEntity(_shortStringHelper, contentTypeDto); - else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported"); + } + else + { + throw new PanicException( + $"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported"); + } + contentTypes.Add(contentType.Id, contentType); // map allowed content types var allowedContentTypes = new List(); while (allowedDtoIx < allowedDtos.Count && allowedDtos[allowedDtoIx].Id == contentTypeDto.NodeId) { - var allowedDto = allowedDtos[allowedDtoIx]; - if (!aliases.TryGetValue(allowedDto.AllowedId, out var alias)) continue; - allowedContentTypes.Add(new ContentTypeSort(new Lazy(() => allowedDto.AllowedId), allowedDto.SortOrder, alias)); + ContentTypeAllowedContentTypeDto allowedDto = allowedDtos[allowedDtoIx]; + if (!aliases.TryGetValue(allowedDto.AllowedId, out var alias)) + { + continue; + } + + allowedContentTypes.Add(new ContentTypeSort(new Lazy(() => allowedDto.AllowedId), + allowedDto.SortOrder, alias)); allowedDtoIx++; } + contentType.AllowedContentTypes = allowedContentTypes; } MapTemplates(contentTypes); MapComposition(contentTypes); MapGroupsAndProperties(contentTypes); + MapHistoryCleanup(contentTypes); // finalize - foreach (var contentType in contentTypes.Values) + foreach (IContentTypeComposition contentType in contentTypes.Values) { contentType.ResetDirtyProperties(false); } @@ -124,36 +143,79 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return contentTypes.Values; } + private void MapHistoryCleanup(Dictionary contentTypes) + { + // get templates + Sql sql1 = Sql() + .Select() + .From() + .OrderBy(x => x.ContentTypeId); + + var contentVersionCleanupPolicyDtos = Database.Fetch(sql1); + + var contentVersionCleanupPolicyDictionary = + contentVersionCleanupPolicyDtos.ToDictionary(x => x.ContentTypeId); + foreach (IContentTypeComposition c in contentTypes.Values) + { + if (!(c is ContentType contentType)) + { + continue; + } + + var historyCleanup = new HistoryCleanup(); + + if (contentVersionCleanupPolicyDictionary.TryGetValue(contentType.Id, out var versionCleanup)) + { + historyCleanup.PreventCleanup = versionCleanup.PreventCleanup; + historyCleanup.KeepAllVersionsNewerThanDays = versionCleanup.KeepAllVersionsNewerThanDays; + historyCleanup.KeepLatestVersionPerDayForDays = versionCleanup.KeepLatestVersionPerDayForDays; + } + + contentType.HistoryCleanup = historyCleanup; + } + } + private void MapTemplates(Dictionary contentTypes) { // get templates - var sql1 = Sql() + Sql sql1 = Sql() .Select() .From() .OrderBy(x => x.ContentTypeNodeId); - var templateDtos = Database.Fetch(sql1); + List templateDtos = Database.Fetch(sql1); //var templates = templateRepository.GetMany(templateDtos.Select(x => x.TemplateNodeId).ToArray()).ToDictionary(x => x.Id, x => x); - var templates = _templateRepository.GetMany().ToDictionary(x => x.Id, x => x); + var templates = Enumerable.ToDictionary(_templateRepository.GetMany(), x => x.Id, x => x); var templateDtoIx = 0; - foreach (var c in contentTypes.Values) + foreach (IContentTypeComposition c in contentTypes.Values) { - if (!(c is IContentType contentType)) continue; + if (!(c is IContentType contentType)) + { + continue; + } // map allowed templates var allowedTemplates = new List(); var defaultTemplateId = 0; - while (templateDtoIx < templateDtos.Count && templateDtos[templateDtoIx].ContentTypeNodeId == contentType.Id) + while (templateDtoIx < templateDtos.Count && + templateDtos[templateDtoIx].ContentTypeNodeId == contentType.Id) { - var allowedDto = templateDtos[templateDtoIx]; + ContentTypeTemplateDto allowedDto = templateDtos[templateDtoIx]; templateDtoIx++; - if (!templates.TryGetValue(allowedDto.TemplateNodeId, out var template)) continue; + if (!templates.TryGetValue(allowedDto.TemplateNodeId, out ITemplate template)) + { + continue; + } + allowedTemplates.Add(template); if (allowedDto.IsDefault) + { defaultTemplateId = template.Id; + } } + contentType.AllowedTemplates = allowedTemplates; contentType.DefaultTemplateId = defaultTemplateId; } @@ -162,24 +224,28 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement private void MapComposition(IDictionary contentTypes) { // get parent/child - var sql1 = Sql() + Sql sql1 = Sql() .Select() .From() .OrderBy(x => x.ChildId); - var compositionDtos = Database.Fetch(sql1); + List compositionDtos = Database.Fetch(sql1); // map var compositionIx = 0; - foreach (var contentType in contentTypes.Values) + foreach (IContentTypeComposition contentType in contentTypes.Values) { - while (compositionIx < compositionDtos.Count && compositionDtos[compositionIx].ChildId == contentType.Id) + while (compositionIx < compositionDtos.Count && + compositionDtos[compositionIx].ChildId == contentType.Id) { - var parentDto = compositionDtos[compositionIx]; + ContentType2ContentTypeDto parentDto = compositionDtos[compositionIx]; compositionIx++; - if (!contentTypes.TryGetValue(parentDto.ParentId, out var parentContentType)) + if (!contentTypes.TryGetValue(parentDto.ParentId, out IContentTypeComposition parentContentType)) + { continue; + } + contentType.AddContentType(parentContentType); } } @@ -187,41 +253,49 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement private void MapGroupsAndProperties(IDictionary contentTypes) { - var sql1 = Sql() + Sql sql1 = Sql() .Select() .From() - .InnerJoin().On((ptg, ct) => ptg.ContentTypeNodeId == ct.NodeId) + .InnerJoin() + .On((ptg, ct) => ptg.ContentTypeNodeId == ct.NodeId) .OrderBy(x => x.NodeId) .AndBy(x => x.SortOrder, x => x.Id); - var groupDtos = Database.Fetch(sql1); + List groupDtos = Database.Fetch(sql1); - var sql2 = Sql() + Sql sql2 = Sql() .Select(r => r.Select(x => x.DataTypeDto, r1 => r1.Select(x => x.NodeDto))) .AndSelect() .From() .InnerJoin().On((pt, dt) => pt.DataTypeId == dt.NodeId) .InnerJoin().On((dt, n) => dt.NodeId == n.NodeId) - .InnerJoin().On((pt, ct) => pt.ContentTypeId == ct.NodeId) - .LeftJoin().On((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id) - .LeftJoin().On((pt, mpt) => pt.Id == mpt.PropertyTypeId) + .InnerJoin() + .On((pt, ct) => pt.ContentTypeId == ct.NodeId) + .LeftJoin() + .On((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id) + .LeftJoin() + .On((pt, mpt) => pt.Id == mpt.PropertyTypeId) .OrderBy(x => x.NodeId) - .AndBy(x => x.SortOrder, x => x.Id) // NULLs will come first or last, never mind, we deal with it below + .AndBy< + PropertyTypeGroupDto>(x => x.SortOrder, + x => x.Id) // NULLs will come first or last, never mind, we deal with it below .AndBy(x => x.SortOrder, x => x.Id); - var propertyDtos = Database.Fetch(sql2); - var builtinProperties = ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper); + List propertyDtos = Database.Fetch(sql2); + Dictionary builtinProperties = + ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper); var groupIx = 0; var propertyIx = 0; - foreach (var contentType in contentTypes.Values) + foreach (IContentTypeComposition contentType in contentTypes.Values) { // only IContentType is publishing var isPublishing = contentType is IContentType; // get group-less properties (in case NULL is ordered first) var noGroupPropertyTypes = new PropertyTypeCollection(isPublishing); - while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == null) + while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && + propertyDtos[propertyIx].PropertyTypeGroupId == null) { noGroupPropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties)); propertyIx++; @@ -231,36 +305,45 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var groupCollection = new PropertyGroupCollection(); while (groupIx < groupDtos.Count && groupDtos[groupIx].ContentTypeNodeId == contentType.Id) { - var group = MapPropertyGroup(groupDtos[groupIx], isPublishing); + PropertyGroup group = MapPropertyGroup(groupDtos[groupIx], isPublishing); groupCollection.Add(group); groupIx++; - while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == group.Id) + while (propertyIx < propertyDtos.Count && + propertyDtos[propertyIx].ContentTypeId == contentType.Id && + propertyDtos[propertyIx].PropertyTypeGroupId == group.Id) { - group.PropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties)); + group.PropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], + builtinProperties)); propertyIx++; } } + contentType.PropertyGroups = groupCollection; // get group-less properties (in case NULL is ordered last) - while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == null) + while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && + propertyDtos[propertyIx].PropertyTypeGroupId == null) { noGroupPropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties)); propertyIx++; } + contentType.NoGroupPropertyTypes = noGroupPropertyTypes; // ensure builtin properties if (contentType is IMemberType memberType) { // ensure that the group exists (ok if it already exists) - memberType.AddPropertyGroup(Cms.Core.Constants.Conventions.Member.StandardPropertiesGroupAlias, Cms.Core.Constants.Conventions.Member.StandardPropertiesGroupName); + memberType.AddPropertyGroup(Constants.Conventions.Member.StandardPropertiesGroupAlias, + Constants.Conventions.Member.StandardPropertiesGroupName); // ensure that property types exist (ok if they already exist) - foreach (var (alias, propertyType) in builtinProperties) + foreach ((var alias, PropertyType propertyType) in builtinProperties) { - var added = memberType.AddPropertyType(propertyType, Cms.Core.Constants.Conventions.Member.StandardPropertiesGroupAlias, Cms.Core.Constants.Conventions.Member.StandardPropertiesGroupName); + var added = memberType.AddPropertyType(propertyType, + Constants.Conventions.Member.StandardPropertiesGroupAlias, + Constants.Conventions.Member.StandardPropertiesGroupName); if (added) { @@ -273,9 +356,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } } - private PropertyGroup MapPropertyGroup(PropertyTypeGroupDto dto, bool isPublishing) - { - return new PropertyGroup(new PropertyTypeCollection(isPublishing)) + private PropertyGroup MapPropertyGroup(PropertyTypeGroupDto dto, bool isPublishing) => + new PropertyGroup(new PropertyTypeCollection(isPublishing)) { Id = dto.Id, Key = dto.UniqueId, @@ -284,14 +366,16 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement Alias = dto.Alias, SortOrder = dto.SortOrder }; - } - private PropertyType MapPropertyType(IContentTypeComposition contentType, PropertyTypeCommonDto dto, IDictionary builtinProperties) + private PropertyType MapPropertyType(IContentTypeComposition contentType, PropertyTypeCommonDto dto, + IDictionary builtinProperties) { var groupId = dto.PropertyTypeGroupId; - var readonlyStorageType = builtinProperties.TryGetValue(dto.Alias, out var propertyType); - var storageType = readonlyStorageType ? propertyType.ValueStorageType : Enum.Parse(dto.DataTypeDto.DbType); + var readonlyStorageType = builtinProperties.TryGetValue(dto.Alias, out PropertyType propertyType); + ValueStorageType storageType = readonlyStorageType + ? propertyType.ValueStorageType + : Enum.Parse(dto.DataTypeDto.DbType); if (contentType is IMemberType memberType) { @@ -300,23 +384,25 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement memberType.SetMemberCanViewProperty(dto.Alias, dto.ViewOnProfile); } - return new PropertyType(_shortStringHelper, dto.DataTypeDto.EditorAlias, storageType, readonlyStorageType, dto.Alias) - { - Description = dto.Description, - DataTypeId = dto.DataTypeId, - DataTypeKey = dto.DataTypeDto.NodeDto.UniqueId, - Id = dto.Id, - Key = dto.UniqueId, - Mandatory = dto.Mandatory, - MandatoryMessage = dto.MandatoryMessage, - Name = dto.Name, - PropertyGroupId = groupId.HasValue ? new Lazy(() => groupId.Value) : null, - SortOrder = dto.SortOrder, - ValidationRegExp = dto.ValidationRegExp, - ValidationRegExpMessage = dto.ValidationRegExpMessage, - Variations = (ContentVariation)dto.Variations, - LabelOnTop = dto.LabelOnTop - }; + return new + PropertyType(_shortStringHelper, dto.DataTypeDto.EditorAlias, storageType, readonlyStorageType, + dto.Alias) + { + Description = dto.Description, + DataTypeId = dto.DataTypeId, + DataTypeKey = dto.DataTypeDto.NodeDto.UniqueId, + Id = dto.Id, + Key = dto.UniqueId, + Mandatory = dto.Mandatory, + MandatoryMessage = dto.MandatoryMessage, + Name = dto.Name, + PropertyGroupId = groupId.HasValue ? new Lazy(() => groupId.Value) : null, + SortOrder = dto.SortOrder, + ValidationRegExp = dto.ValidationRegExp, + ValidationRegExpMessage = dto.ValidationRegExpMessage, + Variations = (ContentVariation)dto.Variations, + LabelOnTop = dto.LabelOnTop + }; } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs index 1977e0fce1..6ab97c971f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -236,6 +236,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement PersistNewBaseContentType(entity); PersistTemplates(entity, false); + PersistHistoryCleanup(entity); entity.ResetDirtyProperties(); } @@ -289,8 +290,26 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement PersistUpdatedBaseContentType(entity); PersistTemplates(entity, true); + PersistHistoryCleanup(entity); entity.ResetDirtyProperties(); } + + private void PersistHistoryCleanup(IContentType entity) + { + if (entity is IContentTypeWithHistoryCleanup entityWithHistoryCleanup) + { + ContentVersionCleanupPolicyDto dto = new ContentVersionCleanupPolicyDto() + { + ContentTypeId = entity.Id, + Updated = DateTime.Now, + PreventCleanup = entityWithHistoryCleanup.HistoryCleanup.PreventCleanup, + KeepAllVersionsNewerThanDays = entityWithHistoryCleanup.HistoryCleanup.KeepAllVersionsNewerThanDays, + KeepLatestVersionPerDayForDays = entityWithHistoryCleanup.HistoryCleanup.KeepLatestVersionPerDayForDays + }; + Database.InsertOrUpdate(dto); + } + + } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index d93c2c8322..38e8a99d08 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -795,7 +795,7 @@ AND umbracoNode.id <> @id", // note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0); - if (whereInArgsCount > 2000) + if (whereInArgsCount > Constants.Sql.MaxParameterCount) throw new NotSupportedException("Too many property/content types."); // delete existing relations (for target language) @@ -933,7 +933,7 @@ AND umbracoNode.id <> @id", // note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers // var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0); - if (whereInArgsCount > 2000) + if (whereInArgsCount > Constants.Sql.MaxParameterCount) throw new NotSupportedException("Too many property/content types."); //first clear out any existing property data that might already exists under the target language @@ -1032,7 +1032,7 @@ AND umbracoNode.id <> @id", //based on the current variance of each item to see if it's 'edited' value should be true/false. var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0); - if (whereInArgsCount > 2000) + if (whereInArgsCount > Constants.Sql.MaxParameterCount) throw new NotSupportedException("Too many property/content types."); var propertySql = Sql() @@ -1121,14 +1121,20 @@ AND umbracoNode.id <> @id", } } - //lookup all matching rows in umbracoDocumentCultureVariation - var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000) - .SelectMany(_ => Database.Fetch( - Sql().Select().From() - .WhereIn(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList()) - .WhereIn(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId)))) - //convert to dictionary with the same key type - .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); + // lookup all matching rows in umbracoDocumentCultureVariation + // fetch in batches to account for maximum parameter count (distinct languages can't exceed 2000) + var languageIds = editedLanguageVersions.Keys.Select(x => x.langId).Distinct().ToArray(); + var nodeIds = editedLanguageVersions.Keys.Select(x => x.nodeId).Distinct(); + var docCultureVariationsToUpdate = nodeIds.InGroupsOf(Constants.Sql.MaxParameterCount - languageIds.Length) + .SelectMany(group => + { + var sql = Sql().Select().From() + .WhereIn(x => x.LanguageId, languageIds) + .WhereIn(x => x.NodeId, group); + + return Database.Fetch(sql); + }) + .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); //convert to dictionary with the same key type var toUpdate = new List(); foreach (var ev in editedLanguageVersions) @@ -1377,6 +1383,7 @@ WHERE {Cms.Core.Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cm var list = new List { "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @id", + "DELETE FROM umbracoUserGroup2Node WHERE nodeId = @id", "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @id", "DELETE FROM cmsTagRelationship WHERE nodeId = @id", "DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @id", diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs index 0ec31d843f..bc9892b1ee 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs @@ -263,13 +263,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement Func>> getItemsFromParents = guids => { - //needs to be in groups of 2000 because we are doing an IN clause and there's a max parameter count that can be used. - return guids.InGroupsOf(2000) - .Select(@group => + return guids.InGroupsOf(Constants.Sql.MaxParameterCount) + .Select(group => { var sqlClause = GetBaseQuery(false) .Where(x => x.Parent != null) - .Where($"{SqlSyntax.GetQuotedColumnName("parent")} IN (@parentIds)", new { parentIds = @group }); + .WhereIn(x => x.Parent, group); var translator = new SqlTranslator(sqlClause, Query()); var sql = translator.Translate(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index af98cd7f10..41645dba27 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -233,6 +233,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.ContentSchedule + " WHERE nodeId = @id", "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.RedirectUrl + " WHERE contentKey IN (SELECT uniqueId FROM " + Cms.Core.Constants.DatabaseSchema.Tables.Node + " WHERE id = @id)", "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.User2NodeNotify + " WHERE nodeId = @id", + "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.UserGroup2Node + " WHERE nodeId = @id", "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.UserGroup2NodePermission + " WHERE nodeId = @id", "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.UserStartNode + " WHERE startNode = @id", "UPDATE " + Cms.Core.Constants.DatabaseSchema.Tables.UserGroup + " SET startContentId = NULL WHERE startContentId = @id", @@ -292,9 +293,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .OrderByDescending(x => x.Current) .AndByDescending(x => x.VersionDate); - return MapDtosToContent(Database.Fetch(sql), true, + var pageIndex = skip / take; + + return MapDtosToContent(Database.Page(pageIndex+1, take, sql).Items, true, // load bare minimum, need variants though since this is used to rollback with variants - false, false, true).Skip(skip).Take(take); + false, false, true); } public override IContent GetVersion(int versionId) @@ -625,6 +628,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement documentVersionDto.Published = true; // now published contentVersionDto.Current = false; // no more current } + + // Ensure existing version retains current preventCleanup flag (both saving and publishing). + contentVersionDto.PreventCleanup = version.PreventCleanup; + Database.Update(contentVersionDto); Database.Update(documentVersionDto); @@ -636,6 +643,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement contentVersionDto.Id = 0; // want a new id contentVersionDto.Current = true; // current version contentVersionDto.Text = entity.Name; + contentVersionDto.PreventCleanup = false; // new draft version disregards prevent cleanup flag Database.Insert(contentVersionDto); entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id @@ -1421,7 +1429,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement if (versions.Count == 0) return new Dictionary>(); - var dtos = Database.FetchByGroups(versions, 2000, batch + var dtos = Database.FetchByGroups(versions, Constants.Sql.MaxParameterCount, batch => Sql() .Select() .From() @@ -1450,7 +1458,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { var ids = temps.Select(x => x.Id); - var dtos = Database.FetchByGroups(ids, 2000, batch => + var dtos = Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch => Sql() .Select() .From() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs new file mode 100644 index 0000000000..21bd883c68 --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement +{ + internal class DocumentVersionRepository : IDocumentVersionRepository + { + private readonly IScopeAccessor _scopeAccessor; + + public DocumentVersionRepository(IScopeAccessor scopeAccessor) => + _scopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor)); + + /// + /// + /// Never includes current draft version.
+ /// Never includes current published version.
+ /// Never includes versions marked as "preventCleanup".
+ ///
+ public IReadOnlyCollection GetDocumentVersionsEligibleForCleanup() + { + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql(); + + query.Select(@"umbracoDocument.nodeId as contentId, + umbracoContent.contentTypeId as contentTypeId, + umbracoContentVersion.id as versionId, + umbracoContentVersion.userId as userId, + umbracoContentVersion.versionDate as versionDate, + umbracoDocumentVersion.published as currentPublishedVersion, + umbracoContentVersion.[current] as currentDraftVersion, + umbracoContentVersion.preventCleanup as preventCleanup, + umbracoUser.userName as username") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.Id, right => right.Id) + .LeftJoin() + .On(left => left.Id, right => right.UserId) + .Where(x => !x.Current) // Never delete current draft version + .Where(x => !x.PreventCleanup) // Never delete "pinned" versions + .Where(x => !x.Published); // Never delete published version + + return _scopeAccessor.AmbientScope.Database.Fetch(query); + } + + /// + public IReadOnlyCollection GetCleanupPolicies() + { + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql(); + + query.Select() + .From(); + + return _scopeAccessor.AmbientScope.Database.Fetch(query); + } + + /// + public IEnumerable GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null) + { + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql(); + + query.Select(@"umbracoDocument.nodeId as contentId, + umbracoContent.contentTypeId as contentTypeId, + umbracoContentVersion.id as versionId, + umbracoContentVersion.userId as userId, + umbracoContentVersion.versionDate as versionDate, + umbracoDocumentVersion.published as currentPublishedVersion, + umbracoContentVersion.[current] as currentDraftVersion, + umbracoContentVersion.preventCleanup as preventCleanup, + umbracoUser.userName as username") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.Id, right => right.Id) + .LeftJoin() + .On(left => left.Id, right => right.UserId) + .LeftJoin() + .On(left => left.VersionId, right => right.Id) + .Where(x => x.NodeId == contentId); + + // TODO: If there's not a better way to write this then we need a better way to write this. + query = languageId.HasValue + ? query.Where(x => x.LanguageId == languageId.Value) + : query.Where("umbracoContentVersionCultureVariation.languageId is null"); + + query = query.OrderByDescending(x => x.Id); + + Page page = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, query); + + totalRecords = page.TotalItems; + + return page.Items; + } + + /// + /// + /// Deletes in batches of + /// + public void DeleteVersions(IEnumerable versionIds) + { + foreach (IEnumerable group in versionIds.InGroupsOf(Constants.Sql.MaxParameterCount)) + { + var groupedVersionIds = group.ToList(); + + /* Note: We had discussed doing this in a single SQL Command. + * If you can work out how to make that work with SQL CE, let me know! + * Can use test PerformContentVersionCleanup_WithNoKeepPeriods_DeletesEverythingExceptActive to try things out. + */ + + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql() + .Delete() + .WhereIn(x => x.VersionId, groupedVersionIds); + _scopeAccessor.AmbientScope.Database.Execute(query); + + query = _scopeAccessor.AmbientScope.SqlContext.Sql() + .Delete() + .WhereIn(x => x.VersionId, groupedVersionIds); + _scopeAccessor.AmbientScope.Database.Execute(query); + + query = _scopeAccessor.AmbientScope.SqlContext.Sql() + .Delete() + .WhereIn(x => x.Id, groupedVersionIds); + _scopeAccessor.AmbientScope.Database.Execute(query); + + query = _scopeAccessor.AmbientScope.SqlContext.Sql() + .Delete() + .WhereIn(x => x.Id, groupedVersionIds); + _scopeAccessor.AmbientScope.Database.Execute(query); + } + } + + /// + public void SetPreventCleanup(int versionId, bool preventCleanup) + { + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql() + .Update(x => x.Set(y => y.PreventCleanup, preventCleanup)) + .Where(x => x.Id == versionId); + + _scopeAccessor.AmbientScope.Database.Execute(query); + } + + /// + public ContentVersionMeta Get(int versionId) + { + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql(); + + query.Select(@"umbracoDocument.nodeId as contentId, + umbracoContent.contentTypeId as contentTypeId, + umbracoContentVersion.id as versionId, + umbracoContentVersion.userId as userId, + umbracoContentVersion.versionDate as versionDate, + umbracoDocumentVersion.published as currentPublishedVersion, + umbracoContentVersion.[current] as currentDraftVersion, + umbracoContentVersion.preventCleanup as preventCleanup, + umbracoUser.userName as username") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.Id, right => right.Id) + .LeftJoin() + .On(left => left.Id, right => right.UserId) + .Where(x => x.Id == versionId); + + return _scopeAccessor.AmbientScope.Database.Single(query); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs index bc1b1b1881..b30c5ae1a4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -14,47 +15,55 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// An internal repository for managing entity containers such as doc type, media type, data type containers. + /// An internal repository for managing entity containers such as doc type, media type, data type containers. /// internal class EntityContainerRepository : EntityRepositoryBase, IEntityContainerRepository { - private readonly Guid _containerObjectType; - - public EntityContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, Guid containerObjectType) + public EntityContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, + ILogger logger, Guid containerObjectType) : base(scopeAccessor, cache, logger) { - var allowedContainers = new[] { Cms.Core.Constants.ObjectTypes.DocumentTypeContainer, Cms.Core.Constants.ObjectTypes.MediaTypeContainer, Cms.Core.Constants.ObjectTypes.DataTypeContainer }; - _containerObjectType = containerObjectType; - if (allowedContainers.Contains(_containerObjectType) == false) - throw new InvalidOperationException("No container type exists with ID: " + _containerObjectType); + Guid[] allowedContainers = new[] + { + Constants.ObjectTypes.DocumentTypeContainer, Constants.ObjectTypes.MediaTypeContainer, + Constants.ObjectTypes.DataTypeContainer + }; + NodeObjectTypeId = containerObjectType; + if (allowedContainers.Contains(NodeObjectTypeId) == false) + { + throw new InvalidOperationException("No container type exists with ID: " + NodeObjectTypeId); + } } + protected Guid NodeObjectTypeId { get; } + // never cache - protected override IRepositoryCachePolicy CreateCachePolicy() - { - return NoCacheRepositoryCachePolicy.Instance; - } + protected override IRepositoryCachePolicy CreateCachePolicy() => + NoCacheRepositoryCachePolicy.Instance; protected override EntityContainer PerformGet(int id) { - var sql = GetBaseQuery(false).Where(GetBaseWhereClause(), new { id = id, NodeObjectType = NodeObjectTypeId }); + Sql sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { id, NodeObjectType = NodeObjectTypeId }); - var nodeDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + NodeDto nodeDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return nodeDto == null ? null : CreateEntity(nodeDto); } // temp - so we don't have to implement GetByQuery public EntityContainer Get(Guid id) { - var sql = GetBaseQuery(false).Where("UniqueId=@uniqueId", new { uniqueId = id }); + Sql sql = GetBaseQuery(false).Where("UniqueId=@uniqueId", new { uniqueId = id }); - var nodeDto = Database.Fetch(sql).FirstOrDefault(); + NodeDto nodeDto = Database.Fetch(sql).FirstOrDefault(); return nodeDto == null ? null : CreateEntity(nodeDto); } public IEnumerable Get(string name, int level) { - var sql = GetBaseQuery(false).Where("text=@name AND level=@level AND nodeObjectType=@umbracoObjectTypeId", new { name, level, umbracoObjectTypeId = NodeObjectTypeId }); + Sql sql = GetBaseQuery(false) + .Where("text=@name AND level=@level AND nodeObjectType=@umbracoObjectTypeId", + new { name, level, umbracoObjectTypeId = NodeObjectTypeId }); return Database.Fetch(sql).Select(CreateEntity); } @@ -62,39 +71,39 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { if (ids.Any()) { - return Database.FetchByGroups(ids, 2000, batch => - GetBaseQuery(false) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .WhereIn(x => x.NodeId, batch)) + return Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch => + GetBaseQuery(false) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .WhereIn(x => x.NodeId, batch)) .Select(CreateEntity); } // else - var sql = GetBaseQuery(false) + Sql sql = GetBaseQuery(false) .Where("nodeObjectType=@umbracoObjectTypeId", new { umbracoObjectTypeId = NodeObjectTypeId }) .OrderBy(x => x.Level); return Database.Fetch(sql).Select(CreateEntity); } - protected override IEnumerable PerformGetByQuery(IQuery query) - { + protected override IEnumerable PerformGetByQuery(IQuery query) => throw new NotImplementedException(); - } private static EntityContainer CreateEntity(NodeDto nodeDto) { if (nodeDto.NodeObjectType.HasValue == false) + { throw new InvalidOperationException("Node with id " + nodeDto.NodeId + " has no object type."); + } // throws if node is not a container - var containedObjectType = EntityContainer.GetContainedObjectType(nodeDto.NodeObjectType.Value); + Guid containedObjectType = EntityContainer.GetContainedObjectType(nodeDto.NodeObjectType.Value); var entity = new EntityContainer(nodeDto.NodeId, nodeDto.UniqueId, nodeDto.ParentId, nodeDto.Path, nodeDto.Level, nodeDto.SortOrder, containedObjectType, - nodeDto.Text, nodeDto.UserId ?? Cms.Core.Constants.Security.UnknownUserId); + nodeDto.Text, nodeDto.UserId ?? Constants.Security.UnknownUserId); // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); @@ -104,11 +113,16 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override Sql GetBaseQuery(bool isCount) { - var sql = Sql(); + Sql sql = Sql(); if (isCount) + { sql.SelectCount(); + } else + { sql.SelectAll(); + } + sql.From(); return sql; } @@ -117,23 +131,29 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override IEnumerable GetDeleteClauses() => throw new NotImplementedException(); - protected Guid NodeObjectTypeId => _containerObjectType; - protected override void PersistDeletedItem(EntityContainer entity) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); + if (entity == null) + { + throw new ArgumentNullException(nameof(entity)); + } + EnsureContainerType(entity); - var nodeDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); - if (nodeDto == null) return; + if (nodeDto == null) + { + return; + } // move children to the parent so they are not orphans - var childDtos = Database.Fetch(Sql().SelectAll() + List childDtos = Database.Fetch(Sql().SelectAll() .From() - .Where("parentID=@parentID AND (nodeObjectType=@containedObjectType OR nodeObjectType=@containerObjectType)", + .Where( + "parentID=@parentID AND (nodeObjectType=@containedObjectType OR nodeObjectType=@containerObjectType)", new { parentID = entity.Id, @@ -141,7 +161,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement containerObjectType = entity.ContainerObjectType })); - foreach (var childDto in childDtos) + foreach (NodeDto childDto in childDtos) { childDto.ParentId = nodeDto.ParentId; Database.Update(childDto); @@ -155,31 +175,51 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override void PersistNewItem(EntityContainer entity) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); + if (entity == null) + { + throw new ArgumentNullException(nameof(entity)); + } + EnsureContainerType(entity); - if (entity.Name == null) throw new InvalidOperationException("Entity name can't be null."); - if (string.IsNullOrWhiteSpace(entity.Name)) throw new InvalidOperationException("Entity name can't be empty or consist only of white-space characters."); + if (entity.Name == null) + { + throw new InvalidOperationException("Entity name can't be null."); + } + + if (string.IsNullOrWhiteSpace(entity.Name)) + { + throw new InvalidOperationException( + "Entity name can't be empty or consist only of white-space characters."); + } + entity.Name = entity.Name.Trim(); // guard against duplicates - var nodeDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() - .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); + .Where(dto => + dto.ParentId == entity.ParentId && dto.Text == entity.Name && + dto.NodeObjectType == entity.ContainerObjectType)); if (nodeDto != null) + { throw new InvalidOperationException("A container with the same name already exists."); + } // create var level = 0; var path = "-1"; if (entity.ParentId > -1) { - var parentDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto parentDto = Database.FirstOrDefault(Sql().SelectAll() .From() - .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); + .Where(dto => + dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); if (parentDto == null) + { throw new InvalidOperationException("Could not find parent container with id " + entity.ParentId); + } level = parentDto.Level; path = parentDto.Path; @@ -203,7 +243,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement // insert, get the id, update the path with the id var id = Convert.ToInt32(Database.Insert(nodeDto)); nodeDto.Path = nodeDto.Path + "," + nodeDto.NodeId; - Database.Save(nodeDto); + Database.Save(nodeDto); // refresh the entity entity.Id = id; @@ -218,26 +258,45 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement // protected override void PersistUpdatedItem(EntityContainer entity) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); + if (entity == null) + { + throw new ArgumentNullException(nameof(entity)); + } + EnsureContainerType(entity); - if (entity.Name == null) throw new InvalidOperationException("Entity name can't be null."); - if (string.IsNullOrWhiteSpace(entity.Name)) throw new InvalidOperationException("Entity name can't be empty or consist only of white-space characters."); + if (entity.Name == null) + { + throw new InvalidOperationException("Entity name can't be null."); + } + + if (string.IsNullOrWhiteSpace(entity.Name)) + { + throw new InvalidOperationException( + "Entity name can't be empty or consist only of white-space characters."); + } + entity.Name = entity.Name.Trim(); // find container to update - var nodeDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); if (nodeDto == null) + { throw new InvalidOperationException("Could not find container with id " + entity.Id); + } // guard against duplicates - var dupNodeDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto dupNodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() - .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); + .Where(dto => + dto.ParentId == entity.ParentId && dto.Text == entity.Name && + dto.NodeObjectType == entity.ContainerObjectType)); if (dupNodeDto != null && dupNodeDto.NodeId != nodeDto.NodeId) + { throw new InvalidOperationException("A container with the same name already exists."); + } // update nodeDto.Text = entity.Name; @@ -247,16 +306,21 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement nodeDto.Path = "-1"; if (entity.ParentId > -1) { - var parent = Database.FirstOrDefault( Sql().SelectAll() + NodeDto parent = Database.FirstOrDefault(Sql().SelectAll() .From() - .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); + .Where(dto => + dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); if (parent == null) - throw new InvalidOperationException("Could not find parent container with id " + entity.ParentId); + { + throw new InvalidOperationException( + "Could not find parent container with id " + entity.ParentId); + } nodeDto.Level = Convert.ToInt16(parent.Level + 1); nodeDto.Path = parent.Path + "," + nodeDto.NodeId; } + nodeDto.ParentId = entity.ParentId; } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs index 4eb4f108ce..f1b9c77d0a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs @@ -279,7 +279,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement if (v == null) return entitiesList; // fetch all variant info dtos - var dtos = Database.FetchByGroups(v.Select(x => x.Id), 2000, GetVariantInfos); + var dtos = Database.FetchByGroups(v.Select(x => x.Id), Constants.Sql.MaxParameterCount, GetVariantInfos); // group by node id (each group contains all languages) var xdtos = dtos.GroupBy(x => x.NodeId).ToDictionary(x => x.Key, x => x); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs index 25927213e5..79e6f732a2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence; @@ -14,38 +15,37 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// Provides a base class to all based repositories. + /// Provides a base class to all based repositories. /// /// The type of the entity's unique identifier. /// The type of the entity managed by this repository. public abstract class EntityRepositoryBase : RepositoryBase, IReadWriteQueryRepository where TEntity : class, IEntity { + private static RepositoryCachePolicyOptions s_defaultOptions; private IRepositoryCachePolicy _cachePolicy; private IQuery _hasIdQuery; - private static RepositoryCachePolicyOptions s_defaultOptions; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected EntityRepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger> logger) - : base(scopeAccessor, appCaches) - { + protected EntityRepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, + ILogger> logger) + : base(scopeAccessor, appCaches) => Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } /// - /// Gets the logger + /// Gets the logger /// protected ILogger> Logger { get; } /// - /// Gets the isolated cache for the + /// Gets the isolated cache for the /// protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate(); /// - /// Gets the isolated cache. + /// Gets the isolated cache. /// /// Depends on the ambient scope cache mode. protected IAppPolicyCache IsolatedCache @@ -67,19 +67,20 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } /// - /// Gets the default + /// Gets the default /// protected virtual RepositoryCachePolicyOptions DefaultOptions => s_defaultOptions ?? (s_defaultOptions - = new RepositoryCachePolicyOptions(() => - { - // get count of all entities of current type (TEntity) to ensure cached result is correct - // create query once if it is needed (no need for locking here) - query is static! - IQuery query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0)); - return PerformCount(query); - })); + = new RepositoryCachePolicyOptions(() => + { + // get count of all entities of current type (TEntity) to ensure cached result is correct + // create query once if it is needed (no need for locking here) - query is static! + IQuery query = _hasIdQuery ?? + (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0)); + return PerformCount(query); + })); /// - /// Gets the repository cache policy + /// Gets the repository cache policy /// protected IRepositoryCachePolicy CachePolicy { @@ -110,21 +111,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } /// - /// Get the entity id for the + /// Adds or Updates an entity of type TEntity /// - protected virtual TId GetEntityId(TEntity entity) - => (TId)(object)entity.Id; - - /// - /// Create the repository cache policy - /// - protected virtual IRepositoryCachePolicy CreateCachePolicy() - => new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); - - /// - /// Adds or Updates an entity of type TEntity - /// - /// This method is backed by an cache + /// This method is backed by an cache public virtual void Save(TEntity entity) { if (entity.HasIdentity == false) @@ -138,11 +127,77 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } /// - /// Deletes the passed in entity + /// Deletes the passed in entity /// public virtual void Delete(TEntity entity) => CachePolicy.Delete(entity, PersistDeletedItem); + /// + /// Gets an entity by the passed in Id utilizing the repository's cache policy + /// + public TEntity Get(TId id) + => CachePolicy.Get(id, PerformGet, PerformGetAll); + + /// + /// Gets all entities of type TEntity or a list according to the passed in Ids + /// + public IEnumerable GetMany(params TId[] ids) + { + // ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries + ids = ids.Distinct() + + // don't query by anything that is a default of T (like a zero) + // TODO: I think we should enabled this in case accidental calls are made to get all with invalid ids + // .Where(x => Equals(x, default(TId)) == false) + .ToArray(); + + // can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities, + // the additional overhead of fetching them in groups is minimal compared to the lookup time of each group + if (ids.Length <= Constants.Sql.MaxParameterCount) + { + return CachePolicy.GetAll(ids, PerformGetAll); + } + + var entities = new List(); + foreach (IEnumerable group in ids.InGroupsOf(Constants.Sql.MaxParameterCount)) + { + entities.AddRange(CachePolicy.GetAll(group.ToArray(), PerformGetAll)); + } + + return entities; + } + + /// + /// Gets a list of entities by the passed in query + /// + public IEnumerable Get(IQuery query) + => PerformGetByQuery(query) + .WhereNotNull(); // ensure we don't include any null refs in the returned collection! + + /// + /// Returns a boolean indicating whether an entity with the passed Id exists + /// + public bool Exists(TId id) + => CachePolicy.Exists(id, PerformExists, PerformGetAll); + + /// + /// Returns an integer with the count of entities found with the passed in query + /// + public int Count(IQuery query) + => PerformCount(query); + + /// + /// Get the entity id for the + /// + protected virtual TId GetEntityId(TEntity entity) + => (TId)(object)entity.Id; + + /// + /// Create the repository cache policy + /// + protected virtual IRepositoryCachePolicy CreateCachePolicy() + => new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); + protected abstract TEntity PerformGet(TId id); protected abstract IEnumerable PerformGetAll(params TId[] ids); @@ -162,24 +217,24 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected virtual bool PerformExists(TId id) { - var sql = GetBaseQuery(true); - sql.Where(GetBaseWhereClause(), new { id = id }); + Sql sql = GetBaseQuery(true); + sql.Where(GetBaseWhereClause(), new { id }); var count = Database.ExecuteScalar(sql); return count == 1; } protected virtual int PerformCount(IQuery query) { - var sqlClause = GetBaseQuery(true); + Sql sqlClause = GetBaseQuery(true); var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); + Sql sql = translator.Translate(); return Database.ExecuteScalar(sql); } protected virtual void PersistDeletedItem(TEntity entity) { - var deletes = GetDeleteClauses(); + IEnumerable deletes = GetDeleteClauses(); foreach (var delete in deletes) { Database.Execute(delete, new { id = GetEntityId(entity) }); @@ -187,60 +242,5 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement entity.DeleteDate = DateTime.Now; } - - /// - /// Gets an entity by the passed in Id utilizing the repository's cache policy - /// - public TEntity Get(TId id) - => CachePolicy.Get(id, PerformGet, PerformGetAll); - - /// - /// Gets all entities of type TEntity or a list according to the passed in Ids - /// - public IEnumerable GetMany(params TId[] ids) - { - // ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries - ids = ids.Distinct() - - // don't query by anything that is a default of T (like a zero) - // TODO: I think we should enabled this in case accidental calls are made to get all with invalid ids - // .Where(x => Equals(x, default(TId)) == false) - .ToArray(); - - // can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities, - // the additional overhead of fetching them in groups is minimal compared to the lookup time of each group - const int maxParams = 2000; - if (ids.Length <= maxParams) - { - return CachePolicy.GetAll(ids, PerformGetAll); - } - - var entities = new List(); - foreach (var groupOfIds in ids.InGroupsOf(maxParams)) - { - entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll)); - } - - return entities; - } - - /// - /// Gets a list of entities by the passed in query - /// - public IEnumerable Get(IQuery query) - => PerformGetByQuery(query) - .WhereNotNull(); // ensure we don't include any null refs in the returned collection! - - /// - /// Returns a boolean indicating whether an entity with the passed Id exists - /// - public bool Exists(TId id) - => CachePolicy.Exists(id, PerformExists, PerformGetAll); - - /// - /// Returns an integer with the count of entities found with the passed in query - /// - public int Count(IQuery query) - => PerformCount(query); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs index cc188787ba..29960a4044 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs @@ -165,6 +165,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var list = new List { "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.User2NodeNotify + " WHERE nodeId = @id", + "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.UserGroup2Node + " WHERE nodeId = @id", "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.UserGroup2NodePermission + " WHERE nodeId = @id", "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.UserStartNode + " WHERE startNode = @id", "UPDATE " + Cms.Core.Constants.DatabaseSchema.Tables.UserGroup + " SET startContentId = NULL WHERE startContentId = @id", diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs index 82d9255bb5..970e498b26 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -82,6 +82,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var list = new[] { "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @id", + "DELETE FROM umbracoUserGroup2Node WHERE nodeId = @id", "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @id", "DELETE FROM umbracoRelation WHERE parentId = @id", "DELETE FROM umbracoRelation WHERE childId = @id", diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index 22083eae30..cf382db164 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; @@ -26,17 +27,17 @@ using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// Represents a repository for doing CRUD operations for + /// Represents a repository for doing CRUD operations for /// public class MemberRepository : ContentRepositoryBase, IMemberRepository { - private readonly MemberPasswordConfigurationSettings _passwordConfiguration; - private readonly IMemberTypeRepository _memberTypeRepository; - private readonly ITagRepository _tagRepository; - private readonly IPasswordHasher _passwordHasher; private readonly IJsonSerializer _jsonSerializer; - private readonly IMemberGroupRepository _memberGroupRepository; private readonly IRepositoryCachePolicy _memberByUsernameCachePolicy; + private readonly IMemberGroupRepository _memberGroupRepository; + private readonly IMemberTypeRepository _memberTypeRepository; + private readonly MemberPasswordConfigurationSettings _passwordConfiguration; + private readonly IPasswordHasher _passwordHasher; + private readonly ITagRepository _tagRepository; private bool _passwordConfigInitialized; private string _passwordConfigJson; @@ -57,19 +58,22 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement IJsonSerializer serializer, IEventAggregator eventAggregator, IOptions passwordConfiguration) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories, dataTypeService, eventAggregator) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, + propertyEditors, dataValueReferenceFactories, dataTypeService, eventAggregator) { - _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); + _memberTypeRepository = + memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); _passwordHasher = passwordHasher; _jsonSerializer = serializer; _memberGroupRepository = memberGroupRepository; _passwordConfiguration = passwordConfiguration.Value; - _memberByUsernameCachePolicy = new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); + _memberByUsernameCachePolicy = + new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); } /// - /// Returns a serialized dictionary of the password configuration that is stored against the member in the database + /// Returns a serialized dictionary of the password configuration that is stored against the member in the database /// private string DefaultPasswordConfigJson { @@ -95,17 +99,341 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public override int RecycleBinId => throw new NotSupportedException(); + public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, + StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + //get the group id + IQuery grpQry = Query().Where(group => group.Name.Equals(roleName)); + IMemberGroup memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault(); + if (memberGroup == null) + { + return Enumerable.Empty(); + } + + // get the members by username + IQuery query = Query(); + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Username.Equals(usernameToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Username.Contains(usernameToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Username.StartsWith(usernameToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Username.EndsWith(usernameToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Username.SqlWildcard(usernameToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); + } + + IMember[] matchedMembers = Get(query).ToArray(); + + var membersInGroup = new List(); + + //then we need to filter the matched members that are in the role + foreach (IEnumerable group in matchedMembers.Select(x => x.Id) + .InGroupsOf(Constants.Sql.MaxParameterCount)) + { + Sql sql = Sql().SelectAll().From() + .Where(dto => dto.MemberGroup == memberGroup.Id) + .WhereIn(dto => dto.Member, group); + + var memberIdsInGroup = Database.Fetch(sql) + .Select(x => x.Member).ToArray(); + + membersInGroup.AddRange(matchedMembers.Where(x => memberIdsInGroup.Contains(x.Id))); + } + + return membersInGroup; + } + + /// + /// Get all members in a specific group + /// + /// + /// + public IEnumerable GetByMemberGroup(string groupName) + { + IQuery grpQry = Query().Where(group => group.Name.Equals(groupName)); + IMemberGroup memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault(); + if (memberGroup == null) + { + return Enumerable.Empty(); + } + + Sql subQuery = Sql().Select("Member").From() + .Where(dto => dto.MemberGroup == memberGroup.Id); + + Sql sql = GetBaseQuery(false) + // TODO: An inner join would be better, though I've read that the query optimizer will always turn a + // subquery with an IN clause into an inner join anyways. + .Append("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + return MapDtosToContent(Database.Fetch(sql)); + } + + public bool Exists(string username) + { + Sql sql = Sql() + .SelectCount() + .From() + .Where(x => x.LoginName == username); + + return Database.ExecuteScalar(sql) > 0; + } + + public int GetCountByQuery(IQuery query) + { + Sql sqlWithProps = GetNodeIdQueryWithPropertyData(); + var translator = new SqlTranslator(sqlWithProps, query); + Sql sql = translator.Translate(); + + //get the COUNT base query + Sql fullSql = GetBaseQuery(true) + .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); + + return Database.ExecuteScalar(fullSql); + } + + /// + public void SetLastLogin(string username, DateTime date) + { + // Important - these queries are designed to execute without an exclusive WriteLock taken in our distributed lock + // table. However due to the data that we are updating which relies on version data we cannot update this data + // without taking some locks, otherwise we'll end up with strange situations because when a member is updated, that operation + // deletes and re-inserts all property data. So if there are concurrent transactions, one deleting and re-inserting and another trying + // to update there can be problems. This is only an issue for cmsPropertyData, not umbracoContentVersion because that table just + // maintains a single row and it isn't deleted/re-inserted. + // So the important part here is the ForUpdate() call on the select to fetch the property data to update. + + // Update the cms property value for the member + + SqlTemplate sqlSelectTemplateProperty = SqlContext.Templates.Get( + "Umbraco.Core.MemberRepository.SetLastLogin1", s => s + .Select(x => x.Id) + .From() + .InnerJoin() + .On((l, r) => l.Id == r.PropertyTypeId) + .InnerJoin() + .On((l, r) => l.Id == r.VersionId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) + .Where(x => x.Alias == SqlTemplate.Arg("propertyTypeAlias")) + .Where(x => x.LoginName == SqlTemplate.Arg("username")) + .ForUpdate()); + Sql sqlSelectProperty = sqlSelectTemplateProperty.Sql(Constants.ObjectTypes.Member, + Constants.Conventions.Member.LastLoginDate, username); + + Sql update = Sql() + .Update(u => u + .Set(x => x.DateValue, date)) + .WhereIn(x => x.Id, sqlSelectProperty); + + Database.Execute(update); + + // Update the umbracoContentVersion value for the member + + SqlTemplate sqlSelectTemplateVersion = SqlContext.Templates.Get( + "Umbraco.Core.MemberRepository.SetLastLogin2", s => s + .Select(x => x.Id) + .From() + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) + .Where(x => x.LoginName == SqlTemplate.Arg("username"))); + Sql sqlSelectVersion = sqlSelectTemplateVersion.Sql(Constants.ObjectTypes.Member, username); + + Database.Execute(Sql() + .Update(u => u + .Set(x => x.VersionDate, date)) + .WhereIn(x => x.Id, sqlSelectVersion)); + } + + /// + /// Gets paged member results. + /// + public override IEnumerable GetPage(IQuery query, + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, + Ordering ordering) + { + Sql filterSql = null; + + if (filter != null) + { + filterSql = Sql(); + foreach (Tuple clause in filter.GetWhereClauses()) + { + filterSql = filterSql.Append($"AND ({clause.Item1})", clause.Item2); + } + } + + return GetPage(query, pageIndex, pageSize, out totalRecords, + x => MapDtosToContent(x), + filterSql, + ordering); + } + + public IMember GetByUsername(string username) => + _memberByUsernameCachePolicy.Get(username, PerformGetByUsername, PerformGetAllByUsername); + + public int[] GetMemberIds(string[] usernames) + { + Guid memberObjectType = Constants.ObjectTypes.Member; + + Sql memberSql = Sql() + .Select("umbracoNode.id") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(x => x.NodeObjectType == memberObjectType) + .Where("cmsMember.LoginName in (@usernames)", new + { + /*usernames =*/ + usernames + }); + return Database.Fetch(memberSql).ToArray(); + } + + protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) + { + if (ordering.OrderBy.InvariantEquals("email")) + { + return SqlSyntax.GetFieldName(x => x.Email); + } + + if (ordering.OrderBy.InvariantEquals("loginName")) + { + return SqlSyntax.GetFieldName(x => x.LoginName); + } + + if (ordering.OrderBy.InvariantEquals("userName")) + { + return SqlSyntax.GetFieldName(x => x.LoginName); + } + + if (ordering.OrderBy.InvariantEquals("updateDate")) + { + return SqlSyntax.GetFieldName(x => x.VersionDate); + } + + if (ordering.OrderBy.InvariantEquals("createDate")) + { + return SqlSyntax.GetFieldName(x => x.CreateDate); + } + + if (ordering.OrderBy.InvariantEquals("contentTypeAlias")) + { + return SqlSyntax.GetFieldName(x => x.Alias); + } + + return base.ApplySystemOrdering(ref sql, ordering); + } + + private IEnumerable MapDtosToContent(List dtos, bool withCache = false) + { + var temps = new List>(); + var contentTypes = new Dictionary(); + var content = new Member[dtos.Count]; + + for (var i = 0; i < dtos.Count; i++) + { + MemberDto dto = dtos[i]; + + if (withCache) + { + // if the cache contains the (proper version of the) item, use it + IMember cached = + IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); + if (cached != null && cached.VersionId == dto.ContentVersionDto.Id) + { + content[i] = (Member)cached; + continue; + } + } + + // else, need to build it + + // get the content type - the repository is full cache *but* still deep-clones + // whatever comes out of it, so use our own local index here to avoid this + var contentTypeId = dto.ContentDto.ContentTypeId; + if (contentTypes.TryGetValue(contentTypeId, out IMemberType contentType) == false) + { + contentTypes[contentTypeId] = contentType = _memberTypeRepository.Get(contentTypeId); + } + + Member c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType); + + // need properties + var versionId = dto.ContentVersionDto.Id; + temps.Add(new TempContent(dto.NodeId, versionId, 0, contentType, c)); + } + + // load all properties for all documents from database in 1 query - indexed by version id + IDictionary properties = GetPropertyCollections(temps); + + // assign properties + foreach (TempContent temp in temps) + { + temp.Content.Properties = properties[temp.VersionId]; + + // reset dirty initial properties (U4-1946) + temp.Content.ResetDirtyProperties(false); + } + + return content; + } + + private IMember MapDtoToContent(MemberDto dto) + { + IMemberType memberType = _memberTypeRepository.Get(dto.ContentDto.ContentTypeId); + Member member = ContentBaseFactory.BuildEntity(dto, memberType); + + // get properties - indexed by version id + var versionId = dto.ContentVersionDto.Id; + var temp = new TempContent(dto.ContentDto.NodeId, versionId, 0, memberType); + IDictionary properties = + GetPropertyCollections(new List> { temp }); + member.Properties = properties[versionId]; + + // reset dirty initial properties (U4-1946) + member.ResetDirtyProperties(false); + return member; + } + + private IMember PerformGetByUsername(string username) + { + IQuery query = Query().Where(x => x.Username.Equals(username)); + return PerformGetByQuery(query).FirstOrDefault(); + } + + private IEnumerable PerformGetAllByUsername(params string[] usernames) + { + IQuery query = Query().WhereIn(x => x.Username, usernames); + return PerformGetByQuery(query); + } + #region Repository Base - protected override Guid NodeObjectTypeId => Cms.Core.Constants.ObjectTypes.Member; + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.Member; protected override IMember PerformGet(int id) { - var sql = GetBaseQuery(QueryType.Single) + Sql sql = GetBaseQuery(QueryType.Single) .Where(x => x.NodeId == id) .SelectTop(1); - var dto = Database.Fetch(sql).FirstOrDefault(); + MemberDto dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : MapDtoToContent(dto); @@ -113,29 +441,31 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(QueryType.Many); + Sql sql = GetBaseQuery(QueryType.Many); if (ids.Any()) + { sql.WhereIn(x => x.NodeId, ids); + } return MapDtosToContent(Database.Fetch(sql)); } protected override IEnumerable PerformGetByQuery(IQuery query) { - var baseQuery = GetBaseQuery(false); + Sql baseQuery = GetBaseQuery(false); // TODO: why is this different from content/media?! // check if the query is based on properties or not - var wheres = query.GetWhereClauses(); + IEnumerable> wheres = query.GetWhereClauses(); //this is a pretty rudimentary check but will work, we just need to know if this query requires property // level queries if (wheres.Any(x => x.Item1.Contains("cmsPropertyType"))) { - var sqlWithProps = GetNodeIdQueryWithPropertyData(); + Sql sqlWithProps = GetNodeIdQueryWithPropertyData(); var translator = new SqlTranslator(sqlWithProps, query); - var sql = translator.Translate(); + Sql sql = translator.Translate(); baseQuery.Append("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments) .OrderBy(x => x.SortOrder); @@ -145,22 +475,18 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement else { var translator = new SqlTranslator(baseQuery, query); - var sql = translator.Translate() + Sql sql = translator.Translate() .OrderBy(x => x.SortOrder); return MapDtosToContent(Database.Fetch(sql)); } - } - protected override Sql GetBaseQuery(QueryType queryType) - { - return GetBaseQuery(queryType, true); - } + protected override Sql GetBaseQuery(QueryType queryType) => GetBaseQuery(queryType, true); protected virtual Sql GetBaseQuery(QueryType queryType, bool current) { - var sql = SqlContext.Sql(); + Sql sql = SqlContext.Sql(); switch (queryType) // TODO: pretend we still need these queries for now { @@ -187,67 +513,70 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) .InnerJoin().On(left => left.NodeId, right => right.NodeId) - .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) // joining the type so we can do a query against the member type - not sure if this adds much overhead or not? // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content // types by default on the document and media repos so we can query by content type there too. - .InnerJoin().On(left => left.ContentTypeId, right => right.NodeId); + .InnerJoin() + .On(left => left.ContentTypeId, right => right.NodeId); sql.Where(x => x.NodeObjectType == NodeObjectTypeId); if (current) + { sql.Where(x => x.Current); // always get the current version + } return sql; } // TODO: move that one up to Versionable! or better: kill it! - protected override Sql GetBaseQuery(bool isCount) - { - return GetBaseQuery(isCount ? QueryType.Count : QueryType.Single); - } + protected override Sql GetBaseQuery(bool isCount) => + GetBaseQuery(isCount ? QueryType.Count : QueryType.Single); protected override string GetBaseWhereClause() // TODO: can we kill / refactor this? - { - return "umbracoNode.id = @id"; - } + => + "umbracoNode.id = @id"; // TODO: document/understand that one - protected Sql GetNodeIdQueryWithPropertyData() - { - return Sql() + protected Sql GetNodeIdQueryWithPropertyData() => + Sql() .Select("DISTINCT(umbracoNode.id)") .From() .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.ContentTypeId == right.NodeId) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .InnerJoin() + .On((left, right) => left.ContentTypeId == right.NodeId) + .InnerJoin() + .On((left, right) => left.NodeId == right.NodeId) .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - - .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) - .LeftJoin().On(left => left.DataTypeId, right => right.NodeId) - + .LeftJoin() + .On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin() + .On(left => left.DataTypeId, right => right.NodeId) .LeftJoin().On(x => x .Where((left, right) => left.PropertyTypeId == right.Id) .Where((left, right) => left.VersionId == right.Id)) - .Where(x => x.NodeObjectType == NodeObjectTypeId); - } protected override IEnumerable GetDeleteClauses() { var list = new List { "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @id", + "DELETE FROM umbracoUserGroup2Node WHERE nodeId = @id", "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @id", "DELETE FROM umbracoRelation WHERE parentId = @id", "DELETE FROM umbracoRelation WHERE childId = @id", "DELETE FROM cmsTagRelationship WHERE nodeId = @id", - "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.PropertyData + " WHERE versionId IN (SELECT id FROM " + Cms.Core.Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)", + "DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + + " WHERE nodeId = @id)", "DELETE FROM cmsMember2MemberGroup WHERE Member = @id", "DELETE FROM cmsMember WHERE nodeId = @id", - "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id", - "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id", "DELETE FROM umbracoNode WHERE id = @id" }; return list; @@ -259,7 +588,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public override IEnumerable GetAllVersions(int nodeId) { - var sql = GetBaseQuery(QueryType.Many, false) + Sql sql = GetBaseQuery(QueryType.Many, false) .Where(x => x.NodeId == nodeId) .OrderByDescending(x => x.Current) .AndByDescending(x => x.VersionDate); @@ -269,10 +598,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public override IMember GetVersion(int versionId) { - var sql = GetBaseQuery(QueryType.Single) + Sql sql = GetBaseQuery(QueryType.Single) .Where(x => x.Id == versionId); - var dto = Database.Fetch(sql).FirstOrDefault(); + MemberDto dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : MapDtoToContent(dto); } @@ -346,13 +675,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement entity.Level = level; // persist the content dto - var contentDto = memberDto.ContentDto; + ContentDto contentDto = memberDto.ContentDto; contentDto.NodeId = nodeDto.NodeId; Database.Insert(contentDto); // persist the content version dto // assumes a new version id and version date (modified date) has been set - var contentVersionDto = memberDto.ContentVersionDto; + ContentVersionDto contentVersionDto = memberDto.ContentVersionDto; contentVersionDto.NodeId = nodeDto.NodeId; contentVersionDto.Current = true; Database.Insert(contentVersionDto); @@ -365,8 +694,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement // this will hash the guid with a salt so should be nicely random if (entity.RawPasswordValue.IsNullOrWhiteSpace()) { - - memberDto.Password = Cms.Core.Constants.Security.EmptyPasswordPrefix + _passwordHasher.HashPassword(Guid.NewGuid().ToString("N")); + memberDto.Password = Constants.Security.EmptyPasswordPrefix + + _passwordHasher.HashPassword(Guid.NewGuid().ToString("N")); entity.RawPasswordValue = memberDto.Password; } @@ -496,301 +825,5 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } #endregion - - public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - //get the group id - var grpQry = Query().Where(group => group.Name.Equals(roleName)); - var memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault(); - if (memberGroup == null) - return Enumerable.Empty(); - - // get the members by username - var query = Query(); - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Username.Equals(usernameToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Username.Contains(usernameToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Username.StartsWith(usernameToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Username.EndsWith(usernameToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Username.SqlWildcard(usernameToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(matchType)); - } - var matchedMembers = Get(query).ToArray(); - - var membersInGroup = new List(); - //then we need to filter the matched members that are in the role - //since the max sql params are 2100 on sql server, we'll reduce that to be safe for potentially other servers and run the queries in batches - var inGroups = matchedMembers.InGroupsOf(1000); - foreach (var batch in inGroups) - { - var memberIdBatch = batch.Select(x => x.Id); - - var sql = Sql().SelectAll().From() - .Where(dto => dto.MemberGroup == memberGroup.Id) - .WhereIn(dto => dto.Member, memberIdBatch); - - var memberIdsInGroup = Database.Fetch(sql) - .Select(x => x.Member).ToArray(); - - membersInGroup.AddRange(matchedMembers.Where(x => memberIdsInGroup.Contains(x.Id))); - } - - return membersInGroup; - - } - - /// - /// Get all members in a specific group - /// - /// - /// - public IEnumerable GetByMemberGroup(string groupName) - { - var grpQry = Query().Where(group => group.Name.Equals(groupName)); - var memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault(); - if (memberGroup == null) - return Enumerable.Empty(); - - var subQuery = Sql().Select("Member").From().Where(dto => dto.MemberGroup == memberGroup.Id); - - var sql = GetBaseQuery(false) - // TODO: An inner join would be better, though I've read that the query optimizer will always turn a - // subquery with an IN clause into an inner join anyways. - .Append("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments) - .OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.SortOrder); - - return MapDtosToContent(Database.Fetch(sql)); - - } - - public bool Exists(string username) - { - var sql = Sql() - .SelectCount() - .From() - .Where(x => x.LoginName == username); - - return Database.ExecuteScalar(sql) > 0; - } - - public int GetCountByQuery(IQuery query) - { - var sqlWithProps = GetNodeIdQueryWithPropertyData(); - var translator = new SqlTranslator(sqlWithProps, query); - var sql = translator.Translate(); - - //get the COUNT base query - var fullSql = GetBaseQuery(true) - .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); - - return Database.ExecuteScalar(fullSql); - } - - /// - public void SetLastLogin(string username, DateTime date) - { - // Important - these queries are designed to execute without an exclusive WriteLock taken in our distributed lock - // table. However due to the data that we are updating which relies on version data we cannot update this data - // without taking some locks, otherwise we'll end up with strange situations because when a member is updated, that operation - // deletes and re-inserts all property data. So if there are concurrent transactions, one deleting and re-inserting and another trying - // to update there can be problems. This is only an issue for cmsPropertyData, not umbracoContentVersion because that table just - // maintains a single row and it isn't deleted/re-inserted. - // So the important part here is the ForUpdate() call on the select to fetch the property data to update. - - // Update the cms property value for the member - - var sqlSelectTemplateProperty = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin1", s => s - .Select(x => x.Id) - .From() - .InnerJoin().On((l, r) => l.Id == r.PropertyTypeId) - .InnerJoin().On((l, r) => l.Id == r.VersionId) - .InnerJoin().On((l, r) => l.NodeId == r.NodeId) - .InnerJoin().On((l, r) => l.NodeId == r.NodeId) - .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) - .Where(x => x.Alias == SqlTemplate.Arg("propertyTypeAlias")) - .Where(x => x.LoginName == SqlTemplate.Arg("username")) - .ForUpdate()); - var sqlSelectProperty = sqlSelectTemplateProperty.Sql(Cms.Core.Constants.ObjectTypes.Member, Cms.Core.Constants.Conventions.Member.LastLoginDate, username); - - var update = Sql() - .Update(u => u - .Set(x => x.DateValue, date)) - .WhereIn(x => x.Id, sqlSelectProperty); - - Database.Execute(update); - - // Update the umbracoContentVersion value for the member - - var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s - .Select(x => x.Id) - .From() - .InnerJoin().On((l, r) => l.NodeId == r.NodeId) - .InnerJoin().On((l, r) => l.NodeId == r.NodeId) - .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) - .Where(x => x.LoginName == SqlTemplate.Arg("username"))); - var sqlSelectVersion = sqlSelectTemplateVersion.Sql(Cms.Core.Constants.ObjectTypes.Member, username); - - Database.Execute(Sql() - .Update(u => u - .Set(x => x.VersionDate, date)) - .WhereIn(x => x.Id, sqlSelectVersion)); - } - - /// - /// Gets paged member results. - /// - public override IEnumerable GetPage(IQuery query, - long pageIndex, int pageSize, out long totalRecords, - IQuery filter, - Ordering ordering) - { - Sql filterSql = null; - - if (filter != null) - { - filterSql = Sql(); - foreach (var clause in filter.GetWhereClauses()) - filterSql = filterSql.Append($"AND ({clause.Item1})", clause.Item2); - } - - return GetPage(query, pageIndex, pageSize, out totalRecords, - x => MapDtosToContent(x), - filterSql, - ordering); - } - - protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) - { - if (ordering.OrderBy.InvariantEquals("email")) - return SqlSyntax.GetFieldName(x => x.Email); - - if (ordering.OrderBy.InvariantEquals("loginName")) - return SqlSyntax.GetFieldName(x => x.LoginName); - - if (ordering.OrderBy.InvariantEquals("userName")) - return SqlSyntax.GetFieldName(x => x.LoginName); - - if (ordering.OrderBy.InvariantEquals("updateDate")) - return SqlSyntax.GetFieldName(x => x.VersionDate); - - if (ordering.OrderBy.InvariantEquals("createDate")) - return SqlSyntax.GetFieldName(x => x.CreateDate); - - if (ordering.OrderBy.InvariantEquals("contentTypeAlias")) - return SqlSyntax.GetFieldName(x => x.Alias); - - return base.ApplySystemOrdering(ref sql, ordering); - } - - private IEnumerable MapDtosToContent(List dtos, bool withCache = false) - { - var temps = new List>(); - var contentTypes = new Dictionary(); - var content = new Member[dtos.Count]; - - for (var i = 0; i < dtos.Count; i++) - { - var dto = dtos[i]; - - if (withCache) - { - // if the cache contains the (proper version of the) item, use it - var cached = IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); - if (cached != null && cached.VersionId == dto.ContentVersionDto.Id) - { - content[i] = (Member)cached; - continue; - } - } - - // else, need to build it - - // get the content type - the repository is full cache *but* still deep-clones - // whatever comes out of it, so use our own local index here to avoid this - var contentTypeId = dto.ContentDto.ContentTypeId; - if (contentTypes.TryGetValue(contentTypeId, out var contentType) == false) - contentTypes[contentTypeId] = contentType = _memberTypeRepository.Get(contentTypeId); - - var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType); - - // need properties - var versionId = dto.ContentVersionDto.Id; - temps.Add(new TempContent(dto.NodeId, versionId, 0, contentType, c)); - } - - // load all properties for all documents from database in 1 query - indexed by version id - var properties = GetPropertyCollections(temps); - - // assign properties - foreach (var temp in temps) - { - temp.Content.Properties = properties[temp.VersionId]; - - // reset dirty initial properties (U4-1946) - temp.Content.ResetDirtyProperties(false); - } - - return content; - } - - private IMember MapDtoToContent(MemberDto dto) - { - IMemberType memberType = _memberTypeRepository.Get(dto.ContentDto.ContentTypeId); - Member member = ContentBaseFactory.BuildEntity(dto, memberType); - - // get properties - indexed by version id - var versionId = dto.ContentVersionDto.Id; - var temp = new TempContent(dto.ContentDto.NodeId, versionId, 0, memberType); - var properties = GetPropertyCollections(new List> { temp }); - member.Properties = properties[versionId]; - - // reset dirty initial properties (U4-1946) - member.ResetDirtyProperties(false); - return member; - } - - public IMember GetByUsername(string username) - { - return _memberByUsernameCachePolicy.Get(username, PerformGetByUsername, PerformGetAllByUsername); - } - - public int[] GetMemberIds(string[] usernames) - { - var memberObjectType = Cms.Core.Constants.ObjectTypes.Member; - - var memberSql = Sql() - .Select("umbracoNode.id") - .From() - .InnerJoin() - .On(dto => dto.NodeId, dto => dto.NodeId) - .Where(x => x.NodeObjectType == memberObjectType) - .Where("cmsMember.LoginName in (@usernames)", new { /*usernames =*/ usernames }); - return Database.Fetch(memberSql).ToArray(); - } - - private IMember PerformGetByUsername(string username) - { - var query = Query().Where(x => x.Username.Equals(username)); - return PerformGetByQuery(query).FirstOrDefault(); - } - - private IEnumerable PerformGetAllByUsername(params string[] usernames) - { - var query = Query().WhereIn(x => x.Username, usernames); - return PerformGetByQuery(query); - } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs index 582120992b..2b38d55212 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; @@ -15,67 +16,76 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// A (sub) repository that exposes functionality to modify assigned permissions to a node + /// A (sub) repository that exposes functionality to modify assigned permissions to a node /// /// /// - /// This repo implements the base class so that permissions can be queued to be persisted - /// like the normal repository pattern but the standard repository Get commands don't apply and will throw + /// This repo implements the base class so that permissions can be + /// queued to be persisted + /// like the normal repository pattern but the standard repository Get commands don't apply and will throw + /// /// internal class PermissionRepository : EntityRepositoryBase where TEntity : class, IEntity { - public PermissionRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger> logger) + public PermissionRepository(IScopeAccessor scopeAccessor, AppCaches cache, + ILogger> logger) : base(scopeAccessor, cache, logger) - { } + { + } /// - /// Returns explicitly defined permissions for a user group for any number of nodes + /// Returns explicitly defined permissions for a user group for any number of nodes /// /// - /// The group ids to lookup permissions for + /// The group ids to lookup permissions for /// /// /// /// - /// This method will not support passing in more than 2000 group Ids + /// This method will not support passing in more than 2000 group IDs when also passing in entity IDs. /// public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, params int[] entityIds) { var result = new EntityPermissionCollection(); - foreach (var groupOfGroupIds in groupIds.InGroupsOf(2000)) + if (entityIds.Length == 0) { - //copy local - var localIds = groupOfGroupIds.ToArray(); - - if (entityIds.Length == 0) + foreach (IEnumerable group in groupIds.InGroupsOf(Constants.Sql.MaxParameterCount)) { - var sql = Sql() + Sql sql = Sql() .SelectAll() - .From() - .Where(dto => localIds.Contains(dto.UserGroupId)); - var permissions = AmbientScope.Database.Fetch(sql); - foreach (var permission in ConvertToPermissionList(permissions)) + .From() + .LeftJoin().On( + (left, right) => left.NodeId == right.NodeId && left.UserGroupId == right.UserGroupId) + .Where(dto => group.Contains(dto.UserGroupId)); + + List permissions = + AmbientScope.Database.Fetch(sql); + foreach (EntityPermission permission in ConvertToPermissionList(permissions)) { result.Add(permission); } } - else + } + else + { + foreach (IEnumerable group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount - + groupIds.Length)) { - //iterate in groups of 2000 since we don't want to exceed the max SQL param count - foreach (var groupOfEntityIds in entityIds.InGroupsOf(2000)) + Sql sql = Sql() + .SelectAll() + .From() + .LeftJoin().On( + (left, right) => left.NodeId == right.NodeId && left.UserGroupId == right.UserGroupId) + .Where(dto => + groupIds.Contains(dto.UserGroupId) && group.Contains(dto.NodeId)); + + List permissions = + AmbientScope.Database.Fetch(sql); + foreach (EntityPermission permission in ConvertToPermissionList(permissions)) { - var ids = groupOfEntityIds; - var sql = Sql() - .SelectAll() - .From() - .Where(dto => localIds.Contains(dto.UserGroupId) && ids.Contains(dto.NodeId)); - var permissions = AmbientScope.Database.Fetch(sql); - foreach (var permission in ConvertToPermissionList(permissions)) - { - result.Add(permission); - } + result.Add(permission); } } } @@ -84,121 +94,142 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } /// - /// Returns permissions directly assigned to the content items for all user groups + /// Returns permissions directly assigned to the content items for all user groups /// /// /// public IEnumerable GetPermissionsForEntities(int[] entityIds) { - var sql = Sql() + Sql sql = Sql() .SelectAll() - .From() - .Where(dto => entityIds.Contains(dto.NodeId)) - .OrderBy(dto => dto.NodeId); + .From() + .LeftJoin() + .On((left, right) => + left.NodeId == right.NodeId && left.UserGroupId == right.UserGroupId) + .Where(dto => entityIds.Contains(dto.NodeId)) + .OrderBy(dto => dto.NodeId); - var result = AmbientScope.Database.Fetch(sql); + List result = AmbientScope.Database.Fetch(sql); return ConvertToPermissionList(result); } /// - /// Returns permissions directly assigned to the content item for all user groups + /// Returns permissions directly assigned to the content item for all user groups /// /// /// public EntityPermissionCollection GetPermissionsForEntity(int entityId) { - var sql = Sql() + Sql sql = Sql() .SelectAll() - .From() - .Where(dto => dto.NodeId == entityId) - .OrderBy(dto => dto.NodeId); + .From() + .LeftJoin() + .On((left, right) => + left.NodeId == right.NodeId && left.UserGroupId == right.UserGroupId) + .Where(dto => dto.NodeId == entityId) + .OrderBy(dto => dto.NodeId); - var result = AmbientScope.Database.Fetch(sql); + List result = AmbientScope.Database.Fetch(sql); return ConvertToPermissionList(result); } /// - /// Assigns the same permission set for a single group to any number of entities + /// Assigns the same permission set for a single group to any number of entities /// /// - /// + /// The permissions to assign or null to remove the connection between group and entityIds /// /// - /// This will first clear the permissions for this user and entities and recreate them + /// This will first clear the permissions for this user and entities and recreate them /// public void ReplacePermissions(int groupId, IEnumerable permissions, params int[] entityIds) { if (entityIds.Length == 0) - return; - - var db = AmbientScope.Database; - - //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit - var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)"; - foreach (var idGroup in entityIds.InGroupsOf(2000)) { - db.Execute(sql, new { groupId, nodeIds = idGroup }); + return; } - var toInsert = new List(); - foreach (var p in permissions) + IUmbracoDatabase db = AmbientScope.Database; + + foreach (IEnumerable group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount)) { + + db.Execute("DELETE FROM umbracoUserGroup2Node WHERE userGroupId = @groupId AND nodeId in (@nodeIds)", + new { groupId, nodeIds = group }); + + db.Execute("DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)", + new { groupId, nodeIds = group }); + } + + + if (permissions is not null) + { + var toInsert = new List(); + var toInsertPermissions = new List(); + foreach (var e in entityIds) { - toInsert.Add(new UserGroup2NodePermissionDto + toInsert.Add(new UserGroup2NodeDto() { NodeId = e, UserGroupId = groupId }); + foreach (var p in permissions) { - NodeId = e, - Permission = p.ToString(CultureInfo.InvariantCulture), - UserGroupId = groupId - }); + toInsertPermissions.Add(new UserGroup2NodePermissionDto + { + NodeId = e, Permission = p.ToString(CultureInfo.InvariantCulture), UserGroupId = groupId + }); + } } - } - db.BulkInsertRecords(toInsert); + db.BulkInsertRecords(toInsert); + db.BulkInsertRecords(toInsertPermissions); + } } /// - /// Assigns one permission for a user to many entities + /// Assigns one permission for a user to many entities /// /// /// /// public void AssignPermission(int groupId, char permission, params int[] entityIds) { - var db = AmbientScope.Database; + IUmbracoDatabase db = AmbientScope.Database; - var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)"; - db.Execute(sql, - new - { - groupId, - permission = permission.ToString(CultureInfo.InvariantCulture), - entityIds - }); + db.Execute("DELETE FROM umbracoUserGroup2Node WHERE userGroupId = @groupId AND nodeId in (@entityIds)", + new { groupId, entityIds }); + db.Execute("DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)", + new { groupId, permission = permission.ToString(CultureInfo.InvariantCulture), entityIds }); - var actions = entityIds.Select(id => new UserGroup2NodePermissionDto + UserGroup2NodeDto[] actionsPermissions = entityIds.Select(id => new UserGroup2NodeDto { - NodeId = id, - Permission = permission.ToString(CultureInfo.InvariantCulture), - UserGroupId = groupId + NodeId = id, UserGroupId = groupId + }).ToArray(); + + UserGroup2NodePermissionDto[] actions = entityIds.Select(id => new UserGroup2NodePermissionDto + { + NodeId = id, Permission = permission.ToString(CultureInfo.InvariantCulture), UserGroupId = groupId }).ToArray(); db.BulkInsertRecords(actions); + db.BulkInsertRecords(actionsPermissions); } /// - /// Assigns one permission to an entity for multiple groups + /// Assigns one permission to an entity for multiple groups /// /// /// /// public void AssignEntityPermission(TEntity entity, char permission, IEnumerable groupIds) { - var db = AmbientScope.Database; + IUmbracoDatabase db = AmbientScope.Database; var groupIdsA = groupIds.ToArray(); - const string sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)"; - db.Execute(sql, + db.Execute("DELETE FROM umbracoUserGroup2Node WHERE nodeId = @nodeId AND userGroupId in (@groupIds)", + new { + nodeId = entity.Id, + groupIds = groupIdsA + }); + db.Execute("DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)", new { nodeId = entity.Id, @@ -206,37 +237,47 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement groupIds = groupIdsA }); - var actions = groupIdsA.Select(id => new UserGroup2NodePermissionDto + UserGroup2NodePermissionDto[] actionsPermissions = groupIdsA.Select(id => new UserGroup2NodePermissionDto { - NodeId = entity.Id, - Permission = permission.ToString(CultureInfo.InvariantCulture), - UserGroupId = id + NodeId = entity.Id, Permission = permission.ToString(CultureInfo.InvariantCulture), UserGroupId = id + }).ToArray(); + + UserGroup2NodeDto[] actions = groupIdsA.Select(id => new UserGroup2NodeDto + { + NodeId = entity.Id, UserGroupId = id }).ToArray(); db.BulkInsertRecords(actions); + db.BulkInsertRecords(actionsPermissions); } /// - /// Assigns permissions to an entity for multiple group/permission entries + /// Assigns permissions to an entity for multiple group/permission entries /// /// /// /// - /// This will first clear the permissions for this entity then re-create them + /// This will first clear the permissions for this entity then re-create them /// public void ReplaceEntityPermissions(EntityPermissionSet permissionSet) { - var db = AmbientScope.Database; + IUmbracoDatabase db = AmbientScope.Database; - const string sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId"; - db.Execute(sql, new { nodeId = permissionSet.EntityId }); + db.Execute("DELETE FROM umbracoUserGroup2Node WHERE nodeId = @nodeId", new { nodeId = permissionSet.EntityId }); + db.Execute("DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId", new { nodeId = permissionSet.EntityId }); - var toInsert = new List(); - foreach (var entityPermission in permissionSet.PermissionsSet) + var toInsert = new List(); + var toInsertPermissions = new List(); + foreach (EntityPermission entityPermission in permissionSet.PermissionsSet) { + toInsert.Add(new UserGroup2NodeDto + { + NodeId = permissionSet.EntityId, + UserGroupId = entityPermission.UserGroupId + }); foreach (var permission in entityPermission.AssignedPermissions) { - toInsert.Add(new UserGroup2NodePermissionDto + toInsertPermissions.Add(new UserGroup2NodePermissionDto { NodeId = permissionSet.EntityId, Permission = permission, @@ -246,64 +287,24 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } db.BulkInsertRecords(toInsert); + db.BulkInsertRecords(toInsertPermissions); } - #region Not implemented (don't need to for the purposes of this repo) - - protected override ContentPermissionSet PerformGet(int id) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override Sql GetBaseQuery(bool isCount) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override string GetBaseWhereClause() - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override IEnumerable GetDeleteClauses() - { - return new List(); - } - - protected override void PersistDeletedItem(ContentPermissionSet entity) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - #endregion - /// - /// Used to add or update entity permissions during a content item being updated + /// Used to add or update entity permissions during a content item being updated /// /// - protected override void PersistNewItem(ContentPermissionSet entity) - { + protected override void PersistNewItem(ContentPermissionSet entity) => //does the same thing as update PersistUpdatedItem(entity); - } /// - /// Used to add or update entity permissions during a content item being updated + /// Used to add or update entity permissions during a content item being updated /// /// protected override void PersistUpdatedItem(ContentPermissionSet entity) { - var asIEntity = (IEntity) entity; + var asIEntity = (IEntity)entity; if (asIEntity.HasIdentity == false) { throw new InvalidOperationException("Cannot create permissions for an entity without an Id"); @@ -312,21 +313,50 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement ReplaceEntityPermissions(entity); } - private static EntityPermissionCollection ConvertToPermissionList(IEnumerable result) + private static EntityPermissionCollection ConvertToPermissionList( + IEnumerable result) { var permissions = new EntityPermissionCollection(); - var nodePermissions = result.GroupBy(x => x.NodeId); - foreach (var np in nodePermissions) + IEnumerable> nodePermissions = result.GroupBy(x => x.NodeId); + foreach (IGrouping np in nodePermissions) { - var userGroupPermissions = np.GroupBy(x => x.UserGroupId); - foreach (var permission in userGroupPermissions) + IEnumerable> userGroupPermissions = + np.GroupBy(x => x.UserGroupId); + foreach (IGrouping permission in userGroupPermissions) { var perms = permission.Select(x => x.Permission).Distinct().ToArray(); - permissions.Add(new EntityPermission(permission.Key, np.Key, perms)); + + // perms can contain null if there are no permissions assigned, but the node is chosen in the UI. + permissions.Add(new EntityPermission(permission.Key, np.Key, + perms.Where(x => x is not null).ToArray())); } } return permissions; } + + #region Not implemented (don't need to for the purposes of this repo) + + protected override ContentPermissionSet PerformGet(int id) => + throw new InvalidOperationException("This method won't be implemented."); + + protected override IEnumerable PerformGetAll(params int[] ids) => + throw new InvalidOperationException("This method won't be implemented."); + + protected override IEnumerable PerformGetByQuery(IQuery query) => + throw new InvalidOperationException("This method won't be implemented."); + + protected override Sql GetBaseQuery(bool isCount) => + throw new InvalidOperationException("This method won't be implemented."); + + protected override string GetBaseWhereClause() => + throw new InvalidOperationException("This method won't be implemented."); + + protected override IEnumerable GetDeleteClauses() => new List(); + + protected override void PersistDeletedItem(ContentPermissionSet entity) => + throw new InvalidOperationException("This method won't be implemented."); + + #endregion } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 1536768bae..6ab29aa47e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Cryptography; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -16,85 +17,183 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { internal class RedirectUrlRepository : EntityRepositoryBase, IRedirectUrlRepository { - public RedirectUrlRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) + public RedirectUrlRepository(IScopeAccessor scopeAccessor, AppCaches cache, + ILogger logger) : base(scopeAccessor, cache, logger) - { } - - protected override int PerformCount(IQuery query) { + } + + public IRedirectUrl Get(string url, Guid contentKey, string culture) + { + var urlHash = url.GenerateHash(); + Sql sql = GetBaseQuery(false).Where(x => + x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey && x.Culture == culture); + RedirectUrlDto dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + public void DeleteAll() => Database.Execute("DELETE FROM umbracoRedirectUrl"); + + public void DeleteContentUrls(Guid contentKey) => + Database.Execute("DELETE FROM umbracoRedirectUrl WHERE contentKey=@contentKey", new { contentKey }); + + public void Delete(Guid id) => Database.Delete(id); + + public IRedirectUrl GetMostRecentUrl(string url) + { + var urlHash = url.GenerateHash(); + Sql sql = GetBaseQuery(false) + .Where(x => x.Url == url && x.UrlHash == urlHash) + .OrderByDescending(x => x.CreateDateUtc); + List dtos = Database.Fetch(sql); + RedirectUrlDto dto = dtos.FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + public IRedirectUrl GetMostRecentUrl(string url, string culture) + { + if (string.IsNullOrWhiteSpace(culture)) + { + return GetMostRecentUrl(url); + } + + var urlHash = url.GenerateHash(); + Sql sql = GetBaseQuery(false) + .Where(x => x.Url == url && x.UrlHash == urlHash && + (x.Culture == culture.ToLower() || x.Culture == string.Empty)) + .OrderByDescending(x => x.CreateDateUtc); + List dtos = Database.Fetch(sql); + RedirectUrlDto dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); + + if (dto == null) + { + dto = dtos.FirstOrDefault(f => f.Culture == string.Empty); + } + + return dto == null ? null : Map(dto); + } + + public IEnumerable GetContentUrls(Guid contentKey) + { + Sql sql = GetBaseQuery(false) + .Where(x => x.ContentKey == contentKey) + .OrderByDescending(x => x.CreateDateUtc); + List dtos = Database.Fetch(sql); + return dtos.Select(Map); + } + + public IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total) + { + Sql sql = GetBaseQuery(false) + .OrderByDescending(x => x.CreateDateUtc); + Page result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + return result.Items.Select(Map); + } + + public IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total) + { + Sql sql = GetBaseQuery(false) + .Where( + string.Format("{0}.{1} LIKE @path", SqlSyntax.GetQuotedTableName("umbracoNode"), + SqlSyntax.GetQuotedColumnName("path")), new { path = "%," + rootContentId + ",%" }) + .OrderByDescending(x => x.CreateDateUtc); + Page result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + + IEnumerable rules = result.Items.Select(Map); + return rules; + } + + public IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total) + { + Sql sql = GetBaseQuery(false) + .Where( + string.Format("{0}.{1} LIKE @url", SqlSyntax.GetQuotedTableName("umbracoRedirectUrl"), + SqlSyntax.GetQuotedColumnName("Url")), + new { url = "%" + searchTerm.Trim().ToLowerInvariant() + "%" }) + .OrderByDescending(x => x.CreateDateUtc); + Page result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + + IEnumerable rules = result.Items.Select(Map); + return rules; + } + + protected override int PerformCount(IQuery query) => throw new NotSupportedException("This repository does not support this method."); - } - protected override bool PerformExists(Guid id) - { - return PerformGet(id) != null; - } + protected override bool PerformExists(Guid id) => PerformGet(id) != null; protected override IRedirectUrl PerformGet(Guid id) { - var sql = GetBaseQuery(false).Where(x => x.Id == id); - var dto = Database.Fetch(sql.SelectTop(1)).FirstOrDefault(); + Sql sql = GetBaseQuery(false).Where(x => x.Id == id); + RedirectUrlDto dto = Database.Fetch(sql.SelectTop(1)).FirstOrDefault(); return dto == null ? null : Map(dto); } protected override IEnumerable PerformGetAll(params Guid[] ids) { - if (ids.Length > 2000) - throw new NotSupportedException("This repository does not support more than 2000 ids."); - var sql = GetBaseQuery(false).WhereIn(x => x.Id, ids); - var dtos = Database.Fetch(sql); + if (ids.Length > Constants.Sql.MaxParameterCount) + { + throw new NotSupportedException( + $"This repository does not support more than {Constants.Sql.MaxParameterCount} ids."); + } + + Sql sql = GetBaseQuery(false).WhereIn(x => x.Id, ids); + List dtos = Database.Fetch(sql); return dtos.WhereNotNull().Select(Map); } - protected override IEnumerable PerformGetByQuery(IQuery query) - { + protected override IEnumerable PerformGetByQuery(IQuery query) => throw new NotSupportedException("This repository does not support this method."); - } protected override Sql GetBaseQuery(bool isCount) { - var sql = Sql(); + Sql sql = Sql(); if (isCount) + { sql.Select(@"COUNT(*) FROM umbracoRedirectUrl JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); + } else + { sql.Select(@"umbracoRedirectUrl.*, umbracoNode.id AS contentId FROM umbracoRedirectUrl JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); + } + return sql; } - protected override string GetBaseWhereClause() - { - return "id = @id"; - } + protected override string GetBaseWhereClause() => "id = @id"; protected override IEnumerable GetDeleteClauses() { - var list = new List - { - "DELETE FROM umbracoRedirectUrl WHERE id = @id" - }; + var list = new List { "DELETE FROM umbracoRedirectUrl WHERE id = @id" }; return list; } protected override void PersistNewItem(IRedirectUrl entity) { - var dto = Map(entity); + RedirectUrlDto dto = Map(entity); Database.Insert(dto); entity.Id = entity.Key.GetHashCode(); } protected override void PersistUpdatedItem(IRedirectUrl entity) { - var dto = Map(entity); + RedirectUrlDto dto = Map(entity); Database.Update(dto); } private static RedirectUrlDto Map(IRedirectUrl redirectUrl) { - if (redirectUrl == null) return null; + if (redirectUrl == null) + { + return null; + } return new RedirectUrlDto { @@ -109,7 +208,10 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); private static IRedirectUrl Map(RedirectUrlDto dto) { - if (dto == null) return null; + if (dto == null) + { + return null; + } var url = new RedirectUrl(); try @@ -129,98 +231,5 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); url.EnableChangeTracking(); } } - - public IRedirectUrl Get(string url, Guid contentKey, string culture) - { - var urlHash = url.GenerateHash(); - var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey && x.Culture == culture); - var dto = Database.Fetch(sql).FirstOrDefault(); - return dto == null ? null : Map(dto); - } - - public void DeleteAll() - { - Database.Execute("DELETE FROM umbracoRedirectUrl"); - } - - public void DeleteContentUrls(Guid contentKey) - { - Database.Execute("DELETE FROM umbracoRedirectUrl WHERE contentKey=@contentKey", new { contentKey }); - } - - public void Delete(Guid id) - { - Database.Delete(id); - } - - public IRedirectUrl GetMostRecentUrl(string url) - { - var urlHash = url.GenerateHash(); - var sql = GetBaseQuery(false) - .Where(x => x.Url == url && x.UrlHash == urlHash) - .OrderByDescending(x => x.CreateDateUtc); - var dtos = Database.Fetch(sql); - var dto = dtos.FirstOrDefault(); - return dto == null ? null : Map(dto); - } - - public IRedirectUrl GetMostRecentUrl(string url, string culture) - { - if (string.IsNullOrWhiteSpace(culture)) return GetMostRecentUrl(url); - var urlHash = url.GenerateHash(); - var sql = GetBaseQuery(false) - .Where(x => x.Url == url && x.UrlHash == urlHash && - (x.Culture == culture.ToLower() || x.Culture == string.Empty)) - .OrderByDescending(x => x.CreateDateUtc); - var dtos = Database.Fetch(sql); - var dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); - - if (dto == null) - dto = dtos.FirstOrDefault(f => f.Culture == string.Empty); - - return dto == null ? null : Map(dto); - } - - public IEnumerable GetContentUrls(Guid contentKey) - { - var sql = GetBaseQuery(false) - .Where(x => x.ContentKey == contentKey) - .OrderByDescending(x => x.CreateDateUtc); - var dtos = Database.Fetch(sql); - return dtos.Select(Map); - } - - public IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total) - { - var sql = GetBaseQuery(false) - .OrderByDescending(x => x.CreateDateUtc); - var result = Database.Page(pageIndex + 1, pageSize, sql); - total = Convert.ToInt32(result.TotalItems); - return result.Items.Select(Map); - } - - public IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total) - { - var sql = GetBaseQuery(false) - .Where(string.Format("{0}.{1} LIKE @path", SqlSyntax.GetQuotedTableName("umbracoNode"), SqlSyntax.GetQuotedColumnName("path")), new { path = "%," + rootContentId + ",%" }) - .OrderByDescending(x => x.CreateDateUtc); - var result = Database.Page(pageIndex + 1, pageSize, sql); - total = Convert.ToInt32(result.TotalItems); - - var rules = result.Items.Select(Map); - return rules; - } - - public IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total) - { - var sql = GetBaseQuery(false) - .Where(string.Format("{0}.{1} LIKE @url", SqlSyntax.GetQuotedTableName("umbracoRedirectUrl"), SqlSyntax.GetQuotedColumnName("Url")), new { url = "%" + searchTerm.Trim().ToLowerInvariant() + "%" }) - .OrderByDescending(x => x.CreateDateUtc); - var result = Database.Page(pageIndex + 1, pageSize, sql); - total = Convert.ToInt32(result.TotalItems); - - var rules = result.Items.Select(Map); - return rules; - } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs index 8fbc784576..919bbeea31 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -21,24 +22,26 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { public TagRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) - { } + { + } #region Manage Tag Entities /// protected override ITag PerformGet(int id) { - var sql = Sql().Select().From().Where(x => x.Id == id); - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + Sql sql = Sql().Select().From().Where(x => x.Id == id); + TagDto dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : TagFactory.BuildEntity(dto); } /// protected override IEnumerable PerformGetAll(params int[] ids) { - var dtos = ids.Length == 0 + IEnumerable dtos = ids.Length == 0 ? Database.Fetch(Sql().Select().From()) - : Database.FetchByGroups(ids, 2000, batch => Sql().Select().From().WhereIn(x => x.Id, batch)); + : Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, + batch => Sql().Select().From().WhereIn(x => x.Id, batch)); return dtos.Select(TagFactory.BuildEntity).ToList(); } @@ -46,7 +49,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement /// protected override IEnumerable PerformGetByQuery(IQuery query) { - var sql = Sql().Select().From(); + Sql sql = Sql().Select().From(); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); @@ -54,30 +57,21 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } /// - protected override Sql GetBaseQuery(bool isCount) - { - return isCount ? Sql().SelectCount().From() : GetBaseQuery(); - } + protected override Sql GetBaseQuery(bool isCount) => + isCount ? Sql().SelectCount().From() : GetBaseQuery(); - private Sql GetBaseQuery() - { - return Sql().Select().From(); - } + private Sql GetBaseQuery() => Sql().Select().From(); /// - protected override string GetBaseWhereClause() - { - return "id = @id"; - } + protected override string GetBaseWhereClause() => "id = @id"; /// protected override IEnumerable GetDeleteClauses() { var list = new List - { - "DELETE FROM cmsTagRelationship WHERE tagId = @id", - "DELETE FROM cmsTags WHERE id = @id" - }; + { + "DELETE FROM cmsTagRelationship WHERE tagId = @id", "DELETE FROM cmsTags WHERE id = @id" + }; return list; } @@ -86,7 +80,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { entity.AddingEntity(); - var dto = TagFactory.BuildDto(entity); + TagDto dto = TagFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(dto)); entity.Id = id; @@ -98,7 +92,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { entity.UpdatingEntity(); - var dto = TagFactory.BuildDto(entity); + TagDto dto = TagFactory.BuildDto(entity); Database.Update(dto); entity.ResetDirtyProperties(); @@ -113,18 +107,21 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public void Assign(int contentId, int propertyTypeId, IEnumerable tags, bool replaceTags = true) { // to no-duplicates array - var tagsA = tags.Distinct(new TagComparer()).ToArray(); + ITag[] tagsA = tags.Distinct(new TagComparer()).ToArray(); // replacing = clear all if (replaceTags) { - var sql0 = Sql().Delete().Where(x => x.NodeId == contentId && x.PropertyTypeId == propertyTypeId); + Sql sql0 = Sql().Delete() + .Where(x => x.NodeId == contentId && x.PropertyTypeId == propertyTypeId); Database.Execute(sql0); } // no tags? nothing else to do if (tagsA.Length == 0) + { return; + } // tags // using some clever logic (?) to insert tags that don't exist in 1 query @@ -163,7 +160,8 @@ WHERE r.tagId IS NULL"; var tagSetSql = GetTagSet(tags); var group = SqlSyntax.GetQuotedColumnName("group"); - var deleteSql = $@"DELETE FROM cmsTagRelationship WHERE nodeId = {contentId} AND propertyTypeId = {propertyTypeId} AND tagId IN ( + var deleteSql = + $@"DELETE FROM cmsTagRelationship WHERE nodeId = {contentId} AND propertyTypeId = {propertyTypeId} AND tagId IN ( SELECT id FROM cmsTags INNER JOIN {tagSetSql} ON ( tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTags.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1) ) @@ -173,18 +171,15 @@ WHERE r.tagId IS NULL"; } /// - public void RemoveAll(int contentId, int propertyTypeId) - { - Database.Execute("DELETE FROM cmsTagRelationship WHERE nodeId = @nodeId AND propertyTypeId = @propertyTypeId", - new { nodeId = contentId, propertyTypeId = propertyTypeId }); - } + public void RemoveAll(int contentId, int propertyTypeId) => + Database.Execute( + "DELETE FROM cmsTagRelationship WHERE nodeId = @nodeId AND propertyTypeId = @propertyTypeId", + new { nodeId = contentId, propertyTypeId }); /// - public void RemoveAll(int contentId) - { + public void RemoveAll(int contentId) => Database.Execute("DELETE FROM cmsTagRelationship WHERE nodeId = @nodeId", new { nodeId = contentId }); - } // this is a clever way to produce an SQL statement like this: // @@ -204,10 +199,16 @@ WHERE r.tagId IS NULL"; sql.Append("("); - foreach (var tag in tags) + foreach (ITag tag in tags) { - if (first) first = false; - else sql.Append(" UNION "); + if (first) + { + first = false; + } + else + { + sql.Append(" UNION "); + } sql.Append("SELECT N'"); sql.Append(SqlSyntax.EscapeString(tag.Text)); @@ -217,9 +218,14 @@ WHERE r.tagId IS NULL"; sql.Append(group); sql.Append(" , "); if (tag.LanguageId.HasValue) + { sql.Append(tag.LanguageId); + } else + { sql.Append("NULL"); + } + sql.Append(" AS languageId"); } @@ -231,19 +237,17 @@ WHERE r.tagId IS NULL"; // used to run Distinct() on tags private class TagComparer : IEqualityComparer { - public bool Equals(ITag x, ITag y) - { - return ReferenceEquals(x, y) // takes care of both being null - || x != null && y != null && x.Text == y.Text && x.Group == y.Group && x.LanguageId == y.LanguageId; - } + public bool Equals(ITag x, ITag y) => + ReferenceEquals(x, y) // takes care of both being null + || (x != null && y != null && x.Text == y.Text && x.Group == y.Group && x.LanguageId == y.LanguageId); public int GetHashCode(ITag obj) { unchecked { var h = obj.Text.GetHashCode(); - h = h * 397 ^ obj.Group.GetHashCode(); - h = h * 397 ^ (obj.LanguageId?.GetHashCode() ?? 0); + h = (h * 397) ^ obj.Group.GetHashCode(); + h = (h * 397) ^ (obj.LanguageId?.GetHashCode() ?? 0); return h; } } @@ -273,7 +277,7 @@ WHERE r.tagId IS NULL"; /// public TaggedEntity GetTaggedEntityByKey(Guid key) { - var sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*"); + Sql sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*"); sql = sql .Where(dto => dto.UniqueId == key); @@ -284,7 +288,7 @@ WHERE r.tagId IS NULL"; /// public TaggedEntity GetTaggedEntityById(int id) { - var sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*"); + Sql sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*"); sql = sql .Where(dto => dto.NodeId == id); @@ -293,9 +297,10 @@ WHERE r.tagId IS NULL"; } /// - public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, string culture = null) + public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, + string culture = null) { - var sql = GetTaggedEntitiesSql(objectType, culture); + Sql sql = GetTaggedEntitiesSql(objectType, culture); sql = sql .Where(x => x.Group == group); @@ -304,30 +309,37 @@ WHERE r.tagId IS NULL"; } /// - public IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string group = null, string culture = null) + public IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, + string group = null, string culture = null) { - var sql = GetTaggedEntitiesSql(objectType, culture); + Sql sql = GetTaggedEntitiesSql(objectType, culture); sql = sql .Where(dto => dto.Text == tag); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return Map(Database.Fetch(sql)); } private Sql GetTaggedEntitiesSql(TaggableObjectTypes objectType, string culture) { - var sql = Sql() + Sql sql = Sql() .Select(x => Alias(x.NodeId, "NodeId")) - .AndSelect(x => Alias(x.Alias, "PropertyTypeAlias"), x => Alias(x.Id, "PropertyTypeId")) - .AndSelect(x => Alias(x.Id, "TagId"), x => Alias(x.Text, "TagText"), x => Alias(x.Group, "TagGroup"), x => Alias(x.LanguageId, "TagLanguage")) + .AndSelect(x => Alias(x.Alias, "PropertyTypeAlias"), + x => Alias(x.Id, "PropertyTypeId")) + .AndSelect(x => Alias(x.Id, "TagId"), x => Alias(x.Text, "TagText"), + x => Alias(x.Group, "TagGroup"), x => Alias(x.LanguageId, "TagLanguage")) .From() .InnerJoin().On((tag, rel) => tag.Id == rel.TagId) - .InnerJoin().On((rel, content) => rel.NodeId == content.NodeId) - .InnerJoin().On((rel, prop) => rel.PropertyTypeId == prop.Id) + .InnerJoin() + .On((rel, content) => rel.NodeId == content.NodeId) + .InnerJoin() + .On((rel, prop) => rel.PropertyTypeId == prop.Id) .InnerJoin().On((content, node) => content.NodeId == node.NodeId); if (culture == null) @@ -344,16 +356,15 @@ WHERE r.tagId IS NULL"; if (objectType != TaggableObjectTypes.All) { - var nodeObjectType = GetNodeObjectType(objectType); + Guid nodeObjectType = GetNodeObjectType(objectType); sql = sql.Where(dto => dto.NodeObjectType == nodeObjectType); } return sql; } - private static IEnumerable Map(IEnumerable dtos) - { - return dtos.GroupBy(x => x.NodeId).Select(dtosForNode => + private static IEnumerable Map(IEnumerable dtos) => + dtos.GroupBy(x => x.NodeId).Select(dtosForNode => { var taggedProperties = dtosForNode.GroupBy(x => x.PropertyTypeId).Select(dtosForProperty => { @@ -368,25 +379,27 @@ WHERE r.tagId IS NULL"; return new TaggedEntity(dtosForNode.Key, taggedProperties); }).ToList(); - } /// - public IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string group = null, string culture = null) + public IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string group = null, + string culture = null) { - var sql = GetTagsSql(culture, true); + Sql sql = GetTagsSql(culture, true); AddTagsSqlWhere(sql, culture); if (objectType != TaggableObjectTypes.All) { - var nodeObjectType = GetNodeObjectType(objectType); + Guid nodeObjectType = GetNodeObjectType(objectType); sql = sql .Where(dto => dto.NodeObjectType == nodeObjectType); } if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } sql = sql .GroupBy(x => x.Id, x => x.Text, x => x.Group, x => x.LanguageId); @@ -397,7 +410,7 @@ WHERE r.tagId IS NULL"; /// public IEnumerable GetTagsForEntity(int contentId, string group = null, string culture = null) { - var sql = GetTagsSql(culture); + Sql sql = GetTagsSql(culture); AddTagsSqlWhere(sql, culture); @@ -405,8 +418,10 @@ WHERE r.tagId IS NULL"; .Where(dto => dto.NodeId == contentId); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return ExecuteTagsQuery(sql); } @@ -414,7 +429,7 @@ WHERE r.tagId IS NULL"; /// public IEnumerable GetTagsForEntity(Guid contentId, string group = null, string culture = null) { - var sql = GetTagsSql(culture); + Sql sql = GetTagsSql(culture); AddTagsSqlWhere(sql, culture); @@ -422,63 +437,76 @@ WHERE r.tagId IS NULL"; .Where(dto => dto.UniqueId == contentId); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return ExecuteTagsQuery(sql); } /// - public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, string culture = null) + public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, + string culture = null) { - var sql = GetTagsSql(culture); + Sql sql = GetTagsSql(culture); sql = sql - .InnerJoin().On((prop, rel) => prop.Id == rel.PropertyTypeId) + .InnerJoin() + .On((prop, rel) => prop.Id == rel.PropertyTypeId) .Where(x => x.NodeId == contentId) .Where(x => x.Alias == propertyTypeAlias); AddTagsSqlWhere(sql, culture); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return ExecuteTagsQuery(sql); } /// - public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null, string culture = null) + public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null, + string culture = null) { - var sql = GetTagsSql(culture); + Sql sql = GetTagsSql(culture); sql = sql - .InnerJoin().On((prop, rel) => prop.Id == rel.PropertyTypeId) + .InnerJoin() + .On((prop, rel) => prop.Id == rel.PropertyTypeId) .Where(dto => dto.UniqueId == contentId) .Where(dto => dto.Alias == propertyTypeAlias); AddTagsSqlWhere(sql, culture); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return ExecuteTagsQuery(sql); } private Sql GetTagsSql(string culture, bool withGrouping = false) { - var sql = Sql() + Sql sql = Sql() .Select(); if (withGrouping) + { sql = sql .AndSelectCount("NodeCount"); + } sql = sql .From() .InnerJoin().On((rel, tag) => tag.Id == rel.TagId) - .InnerJoin().On((content, rel) => content.NodeId == rel.NodeId) + .InnerJoin() + .On((content, rel) => content.NodeId == rel.NodeId) .InnerJoin().On((node, content) => node.NodeId == content.NodeId); if (culture != null && culture != "*") @@ -506,21 +534,19 @@ WHERE r.tagId IS NULL"; return sql; } - private IEnumerable ExecuteTagsQuery(Sql sql) - { - return Database.Fetch(sql).Select(TagFactory.BuildEntity); - } + private IEnumerable ExecuteTagsQuery(Sql sql) => + Database.Fetch(sql).Select(TagFactory.BuildEntity); private Guid GetNodeObjectType(TaggableObjectTypes type) { switch (type) { case TaggableObjectTypes.Content: - return Cms.Core.Constants.ObjectTypes.Document; + return Constants.ObjectTypes.Document; case TaggableObjectTypes.Media: - return Cms.Core.Constants.ObjectTypes.Media; + return Constants.ObjectTypes.Media; case TaggableObjectTypes.Member: - return Cms.Core.Constants.ObjectTypes.Member; + return Constants.ObjectTypes.Member; default: throw new ArgumentOutOfRangeException(nameof(type)); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs index b0cabe5312..6d2c02484a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs @@ -29,15 +29,15 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement private readonly IIOHelper _ioHelper; private readonly IShortStringHelper _shortStringHelper; private readonly IFileSystem _viewsFileSystem; - private readonly ViewHelper _viewHelper; + private readonly IViewHelper _viewHelper; - public TemplateRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, FileSystems fileSystems, IIOHelper ioHelper, IShortStringHelper shortStringHelper) + public TemplateRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, FileSystems fileSystems, IIOHelper ioHelper, IShortStringHelper shortStringHelper, IViewHelper viewHelper) : base(scopeAccessor, cache, logger) { _ioHelper = ioHelper; _shortStringHelper = shortStringHelper; _viewsFileSystem = fileSystems.MvcViewsFileSystem; - _viewHelper = new ViewHelper(_viewsFileSystem); + _viewHelper = viewHelper; } protected override IRepositoryCachePolicy CreateCachePolicy() => @@ -131,6 +131,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var list = new List { "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.User2NodeNotify + " WHERE nodeId = @id", + "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.UserGroup2Node + " WHERE nodeId = @id", "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.UserGroup2NodePermission + " WHERE nodeId = @id", "UPDATE " + Cms.Core.Constants.DatabaseSchema.Tables.DocumentVersion + " SET templateId = NULL WHERE templateId = @id", "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.DocumentType + " WHERE templateNodeId = @id", diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs index a8c6334416..d6be3cf730 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -286,6 +286,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { "DELETE FROM umbracoUser2UserGroup WHERE userGroupId = @id", "DELETE FROM umbracoUserGroup2App WHERE userGroupId = @id", + "DELETE FROM umbracoUserGroup2Node WHERE userGroupId = @id", "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @id", "DELETE FROM umbracoUserGroup WHERE id = @id" }; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyEditor.cs index 550a64b14d..93e7d5be50 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyEditor.cs @@ -7,7 +7,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -264,7 +263,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _textService.Localize("validation", "entriesShort", new[] { validationLimit.Min.ToString(), - (validationLimit.Min - blockEditorData.Layout.Count()).ToString() + (validationLimit.Min - (blockEditorData?.Layout.Count() ?? 0)).ToString() }), new[] { "minCount" }); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs index 9a7ecdbf29..cfa1c4b3cb 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs @@ -122,7 +122,7 @@ namespace Umbraco.Cms.Core.PropertyEditors { json = JToken.Parse(asString); } - catch (Exception e) + catch (Exception) { // See issue https://github.com/umbraco/Umbraco-CMS/issues/10879 // We are detecting JSON data by seeing if a string is surrounded by [] or {} diff --git a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs index 14627db05c..4da1ae53c2 100644 --- a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs +++ b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs @@ -25,6 +25,7 @@ namespace Umbraco.Cms.Core.PublishedCache private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; private readonly ILogger _logger; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + private bool _disposedValue; // default ctor public PublishedContentTypeCache(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, IPublishedContentTypeFactory publishedContentTypeFactory, ILogger logger) @@ -259,8 +260,6 @@ namespace Umbraco.Cms.Core.PublishedCache private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias) { - if (GetPublishedContentTypeByAlias != null) - return GetPublishedContentTypeByAlias(alias); IContentTypeComposition contentType = itemType switch { PublishedItemType.Content => _contentTypeService.Get(alias), @@ -276,8 +275,6 @@ namespace Umbraco.Cms.Core.PublishedCache private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, int id) { - if (GetPublishedContentTypeById != null) - return GetPublishedContentTypeById(id); IContentTypeComposition contentType = itemType switch { PublishedItemType.Content => _contentTypeService.Get(id), @@ -291,56 +288,6 @@ namespace Umbraco.Cms.Core.PublishedCache return _publishedContentTypeFactory.CreateContentType(contentType); } - // for unit tests - changing the callback must reset the cache obviously - // TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id - private Func _getPublishedContentTypeByAlias; - internal Func GetPublishedContentTypeByAlias - { - get => _getPublishedContentTypeByAlias; - set - { - try - { - _lock.EnterWriteLock(); - - _typesByAlias.Clear(); - _typesById.Clear(); - _getPublishedContentTypeByAlias = value; - } - finally - { - if (_lock.IsWriteLockHeld) - _lock.ExitWriteLock(); - } - } - } - - // for unit tests - changing the callback must reset the cache obviously - // TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id - private Func _getPublishedContentTypeById; - private bool _disposedValue; - - internal Func GetPublishedContentTypeById - { - get => _getPublishedContentTypeById; - set - { - try - { - _lock.EnterWriteLock(); - - _typesByAlias.Clear(); - _typesById.Clear(); - _getPublishedContentTypeById = value; - } - finally - { - if (_lock.IsWriteLockHeld) - _lock.ExitWriteLock(); - } - } - } - private static string GetAliasKey(PublishedItemType itemType, string alias) { string k; diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs index e149e092d8..d8119f919c 100644 --- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs @@ -295,7 +295,7 @@ namespace Umbraco.Cms.Infrastructure totalRecords = results.TotalItemCount; return new CultureContextualSearchResults( - results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, + results.ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture); } @@ -324,7 +324,7 @@ namespace Umbraco.Cms.Infrastructure totalRecords = results.TotalItemCount; - return results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot); + return results.ToPublishedSearchResults(_publishedSnapshot); } /// diff --git a/src/Umbraco.Infrastructure/PublishedContentQueryAccessor.cs b/src/Umbraco.Infrastructure/PublishedContentQueryAccessor.cs index 9f716365a0..d61900df95 100644 --- a/src/Umbraco.Infrastructure/PublishedContentQueryAccessor.cs +++ b/src/Umbraco.Infrastructure/PublishedContentQueryAccessor.cs @@ -1,17 +1,21 @@ using System; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; namespace Umbraco.Cms.Core { public class PublishedContentQueryAccessor : IPublishedContentQueryAccessor { - private readonly IServiceProvider _serviceProvider; + private readonly IScopedServiceProvider _scopedServiceProvider; - public PublishedContentQueryAccessor(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + [Obsolete("Please use alternative constructor")] + public PublishedContentQueryAccessor(IServiceProvider serviceProvider) => _scopedServiceProvider = serviceProvider.GetRequiredService(); + + public PublishedContentQueryAccessor(IScopedServiceProvider scopedServiceProvider) => _scopedServiceProvider = scopedServiceProvider; public bool TryGetValue(out IPublishedContentQuery publishedContentQuery) { - publishedContentQuery = _serviceProvider.GetRequiredService(); + publishedContentQuery = _scopedServiceProvider.ServiceProvider?.GetService(); return publishedContentQuery is not null; } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 4ec87dfde7..175bceb9e0 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -133,6 +133,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate AcquireMainDom(); + // TODO (V10): Remove this obsoleted notification publish. await _eventAggregator.PublishAsync(new UmbracoApplicationMainDomAcquiredNotification(), cancellationToken); // notify for unattended install @@ -171,6 +172,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime break; } + // TODO (V10): Remove this obsoleted notification publish. await _eventAggregator.PublishAsync(new UmbracoApplicationComponentsInstallingNotification(State.Level), cancellationToken); // create & initialize the components diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index e0423cc340..2383e8eb92 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -5,65 +5,56 @@ using System.Linq; using System.Text; using System.Threading; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Umbraco.Extensions; -using Umbraco.Core.Collections; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Infrastructure.Persistence; -using CoreDebugSettings = Umbraco.Cms.Core.Configuration.Models.CoreDebugSettings; +using Umbraco.Core.Collections; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Scoping { /// - /// Implements . + /// Implements . /// /// Not thread-safe obviously. internal class Scope : IScope { - private enum LockType - { - ReadLock, - WriteLock - } - - private readonly ScopeProvider _scopeProvider; - private readonly CoreDebugSettings _coreDebugSettings; - private readonly MediaFileManager _mediaFileManager; - private readonly IEventAggregator _eventAggregator; - private readonly ILogger _logger; - private readonly IsolationLevel _isolationLevel; - private readonly RepositoryCacheMode _repositoryCacheMode; - private readonly bool? _scopeFileSystem; private readonly bool _autoComplete; - private bool _callContext; - - private bool _disposed; - private bool? _completed; - - private IsolatedCaches _isolatedCaches; - private IUmbracoDatabase _database; - private EventMessages _messages; - private ICompletable _fscope; - private IEventDispatcher _eventDispatcher; - private IScopedNotificationPublisher _notificationPublisher; + private readonly CoreDebugSettings _coreDebugSettings; private readonly object _dictionaryLocker; - private readonly object _lockQueueLocker = new object(); + private readonly IEventAggregator _eventAggregator; + private readonly IsolationLevel _isolationLevel; + private readonly object _lockQueueLocker = new(); + private readonly ILogger _logger; + private readonly MediaFileManager _mediaFileManager; + private readonly RepositoryCacheMode _repositoryCacheMode; + private readonly bool? _scopeFileSystem; + + private readonly ScopeProvider _scopeProvider; + private bool _callContext; + private bool? _completed; + private IUmbracoDatabase _database; + + private bool _disposed; + private IEventDispatcher _eventDispatcher; + private ICompletable _fscope; + + private IsolatedCaches _isolatedCaches; + private EventMessages _messages; + private IScopedNotificationPublisher _notificationPublisher; + + private StackQueue<(LockType lockType, TimeSpan timeout, Guid instanceId, int lockId)> _queuedLocks; // This is all used to safely track read/write locks at given Scope levels so that // when we dispose we can verify that everything has been cleaned up correctly. private HashSet _readLocks; - private HashSet _writeLocks; private Dictionary> _readLocksDictionary; + private HashSet _writeLocks; private Dictionary> _writeLocksDictionary; - private StackQueue<(LockType lockType, TimeSpan timeout, Guid instanceId, int lockId)> _queuedLocks; - - internal Dictionary> ReadLocks => _readLocksDictionary; - internal Dictionary> WriteLocks => _writeLocksDictionary; - // initializes a new scope private Scope( ScopeProvider scopeProvider, @@ -103,7 +94,8 @@ namespace Umbraco.Cms.Core.Scoping #if DEBUG_SCOPES _scopeProvider.RegisterScope(this); #endif - logger.LogTrace("Create {InstanceId} on thread {ThreadId}", InstanceId.ToString("N").Substring(0, 8), Thread.CurrentThread.ManagedThreadId); + logger.LogTrace("Create {InstanceId} on thread {ThreadId}", InstanceId.ToString("N").Substring(0, 8), + Thread.CurrentThread.ManagedThreadId); if (detachable) { @@ -141,9 +133,12 @@ namespace Umbraco.Cms.Core.Scoping // cannot specify a different mode! // TODO: means that it's OK to go from L2 to None for reading purposes, but writing would be BAD! // this is for XmlStore that wants to bypass caches when rebuilding XML (same for NuCache) - if (repositoryCacheMode != RepositoryCacheMode.Unspecified && parent.RepositoryCacheMode > repositoryCacheMode) + if (repositoryCacheMode != RepositoryCacheMode.Unspecified && + parent.RepositoryCacheMode > repositoryCacheMode) { - throw new ArgumentException($"Value '{repositoryCacheMode}' cannot be lower than parent value '{parent.RepositoryCacheMode}'.", nameof(repositoryCacheMode)); + throw new ArgumentException( + $"Value '{repositoryCacheMode}' cannot be lower than parent value '{parent.RepositoryCacheMode}'.", + nameof(repositoryCacheMode)); } // cannot specify a dispatcher! @@ -155,14 +150,17 @@ namespace Umbraco.Cms.Core.Scoping // Only the outermost scope can specify the notification publisher if (_notificationPublisher != null) { - throw new ArgumentException("Value cannot be specified on nested scope.", nameof(notificationPublisher)); + throw new ArgumentException("Value cannot be specified on nested scope.", + nameof(notificationPublisher)); } // cannot specify a different fs scope! // can be 'true' only on outer scope (and false does not make much sense) if (scopeFileSystems != null && parent._scopeFileSystem != scopeFileSystems) { - throw new ArgumentException($"Value '{scopeFileSystems.Value}' be different from parent value '{parent._scopeFileSystem}'.", nameof(scopeFileSystems)); + throw new ArgumentException( + $"Value '{scopeFileSystems.Value}' be different from parent value '{parent._scopeFileSystem}'.", + nameof(scopeFileSystems)); } } else @@ -194,8 +192,11 @@ namespace Umbraco.Cms.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopedNotificationPublisher, scopeFileSystems, callContext, autoComplete) - { } + : this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, null, + scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, + scopedNotificationPublisher, scopeFileSystems, callContext, autoComplete) + { + } // initializes a new scope in a nested scopes chain, with its parent public Scope( @@ -213,60 +214,14 @@ namespace Umbraco.Cms.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete) - { } - - /// - /// Used for testing. Ensures and gets any queued read locks. - /// - /// - internal Dictionary> GetReadLocks() + : this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, parent, + null, false, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, + scopeFileSystems, callContext, autoComplete) { - EnsureDbLocks(); - // always delegate to root/parent scope. - if (ParentScope is not null) - { - return ParentScope.GetReadLocks(); - } - else - { - return _readLocksDictionary; - } } - /// - /// Used for testing. Ensures and gets and queued write locks. - /// - /// - internal Dictionary> GetWriteLocks() - { - EnsureDbLocks(); - // always delegate to root/parent scope. - if (ParentScope is not null) - { - return ParentScope.GetWriteLocks(); - } - else - { - return _writeLocksDictionary; - } - } - - public Guid InstanceId { get; } = Guid.NewGuid(); - - public int CreatedThreadId { get; } = Thread.CurrentThread.ManagedThreadId; - - public ISqlContext SqlContext - { - get - { - if (_scopeProvider.SqlContext == null) - { - throw new InvalidOperationException($"The {nameof(_scopeProvider.SqlContext)} on the scope provider is null"); - } - return _scopeProvider.SqlContext; - } - } + internal Dictionary> ReadLocks => _readLocksDictionary; + internal Dictionary> WriteLocks => _writeLocksDictionary; // a value indicating whether to force call-context public bool CallContext @@ -301,39 +256,6 @@ namespace Umbraco.Cms.Core.Scoping } } - /// - public RepositoryCacheMode RepositoryCacheMode - { - get - { - if (_repositoryCacheMode != RepositoryCacheMode.Unspecified) - { - return _repositoryCacheMode; - } - - if (ParentScope != null) - { - return ParentScope.RepositoryCacheMode; - } - - return RepositoryCacheMode.Default; - } - } - - /// - public IsolatedCaches IsolatedCaches - { - get - { - if (ParentScope != null) - { - return ParentScope.IsolatedCaches; - } - - return _isolatedCaches ??= new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache())); - } - } - // a value indicating whether the scope is detachable // ie whether it was created by CreateDetachedScope public bool Detachable { get; } @@ -370,6 +292,88 @@ namespace Umbraco.Cms.Core.Scoping } } + public IUmbracoDatabase DatabaseOrNull + { + get + { + EnsureNotDisposed(); + if (ParentScope == null) + { + if (_database != null) + { + EnsureDbLocks(); + } + + return _database; + } + + return ParentScope.DatabaseOrNull; + } + } + + public EventMessages MessagesOrNull + { + get + { + EnsureNotDisposed(); + return ParentScope == null ? _messages : ParentScope.MessagesOrNull; + } + } + + // true if Umbraco.CoreDebugSettings.LogUncompletedScope appSetting is set to "true" + private bool LogUncompletedScopes => _coreDebugSettings.LogIncompletedScopes; + + public Guid InstanceId { get; } = Guid.NewGuid(); + + public int CreatedThreadId { get; } = Thread.CurrentThread.ManagedThreadId; + + public ISqlContext SqlContext + { + get + { + if (_scopeProvider.SqlContext == null) + { + throw new InvalidOperationException( + $"The {nameof(_scopeProvider.SqlContext)} on the scope provider is null"); + } + + return _scopeProvider.SqlContext; + } + } + + /// + public RepositoryCacheMode RepositoryCacheMode + { + get + { + if (_repositoryCacheMode != RepositoryCacheMode.Unspecified) + { + return _repositoryCacheMode; + } + + if (ParentScope != null) + { + return ParentScope.RepositoryCacheMode; + } + + return RepositoryCacheMode.Default; + } + } + + /// + public IsolatedCaches IsolatedCaches + { + get + { + if (ParentScope != null) + { + return ParentScope.IsolatedCaches; + } + + return _isolatedCaches ??= new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache())); + } + } + /// public IUmbracoDatabase Database { @@ -403,7 +407,8 @@ namespace Umbraco.Cms.Core.Scoping IsolationLevel currentLevel = database.GetCurrentTransactionIsolationLevel(); if (_isolationLevel > IsolationLevel.Unspecified && currentLevel < _isolationLevel) { - throw new Exception("Scope requires isolation level " + _isolationLevel + ", but got " + currentLevel + " from parent."); + throw new Exception("Scope requires isolation level " + _isolationLevel + ", but got " + + currentLevel + " from parent."); } return _database = database; @@ -428,24 +433,6 @@ namespace Umbraco.Cms.Core.Scoping } } - public IUmbracoDatabase DatabaseOrNull - { - get - { - EnsureNotDisposed(); - if (ParentScope == null) - { - if (_database != null) - { - EnsureDbLocks(); - } - return _database; - } - - return ParentScope.DatabaseOrNull; - } - } - /// public EventMessages Messages { @@ -468,15 +455,6 @@ namespace Umbraco.Cms.Core.Scoping } } - public EventMessages MessagesOrNull - { - get - { - EnsureNotDisposed(); - return ParentScope == null ? _messages : ParentScope.MessagesOrNull; - } - } - /// public IEventDispatcher Events { @@ -502,7 +480,8 @@ namespace Umbraco.Cms.Core.Scoping return ParentScope.Notifications; } - return _notificationPublisher ?? (_notificationPublisher = new ScopedNotificationPublisher(_eventAggregator)); + return _notificationPublisher ?? + (_notificationPublisher = new ScopedNotificationPublisher(_eventAggregator)); } } @@ -517,125 +496,14 @@ namespace Umbraco.Cms.Core.Scoping return _completed.Value; } - public void Reset() => _completed = null; - - public void ChildCompleted(bool? completed) - { - // if child did not complete we cannot complete - if (completed.HasValue == false || completed.Value == false) - { - if (_coreDebugSettings.LogIncompletedScopes) - { - _logger.LogWarning("Uncompleted Child Scope at\r\n {StackTrace}", Environment.StackTrace); - } - - _completed = false; - } - } - - /// - /// When we require a ReadLock or a WriteLock we don't immediately request these locks from the database, - /// instead we only request them when necessary (lazily). - /// To do this, we queue requests for read/write locks. - /// This is so that if there's a request for either of these - /// locks, but the service/repository returns an item from the cache, we don't end up making a DB call to make the - /// read/write lock. - /// This executes the queue of requested locks in order in an efficient way lazily whenever the database instance is - /// resolved. - /// - private void EnsureDbLocks() - { - // always delegate to the root parent - if (ParentScope is not null) - { - ParentScope.EnsureDbLocks(); - } - else - { - lock (_lockQueueLocker) - { - if (_queuedLocks?.Count > 0) - { - var currentType = LockType.ReadLock; - var currentTimeout = TimeSpan.Zero; - var currentInstanceId = InstanceId; - var collectedIds = new HashSet(); - - var i = 0; - while (_queuedLocks.Count > 0) - { - var (lockType, timeout, instanceId, lockId) = _queuedLocks.Dequeue(); - - if (i == 0) - { - currentType = lockType; - currentTimeout = timeout; - currentInstanceId = instanceId; - } - else if (lockType != currentType || timeout != currentTimeout || instanceId != currentInstanceId) - { - // the lock type, instanceId or timeout switched. - // process the lock ids collected - switch (currentType) - { - case LockType.ReadLock: - EagerReadLockInner(_database, currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); - break; - case LockType.WriteLock: - EagerWriteLockInner(_database, currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); - break; - } - // clear the collected and set new type - collectedIds.Clear(); - currentType = lockType; - currentTimeout = timeout; - currentInstanceId = instanceId; - } - collectedIds.Add(lockId); - i++; - } - - // process the remaining - switch (currentType) - { - case LockType.ReadLock: - EagerReadLockInner(_database, currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); - break; - case LockType.WriteLock: - EagerWriteLockInner(_database, currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); - break; - } - } - } - } - } - - private void EnsureNotDisposed() - { - // We can't be disposed - if (_disposed) - { - throw new ObjectDisposedException($"The {nameof(Scope)} ({this.GetDebugInfo()}) is already disposed"); - } - - // And neither can our ancestors if we're trying to be disposed since - // a child must always be disposed before it's parent. - // This is a safety check, it's actually not entirely possible that a parent can be - // disposed before the child since that will end up with a "not the Ambient" exception. - ParentScope?.EnsureNotDisposed(); - - // TODO: safer? - //if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0) - // throw new ObjectDisposedException(GetType().FullName); - } - public void Dispose() { EnsureNotDisposed(); if (this != _scopeProvider.AmbientScope) { - var failedMessage = $"The {nameof(Scope)} {this.InstanceId} being disposed is not the Ambient {nameof(Scope)} {(_scopeProvider.AmbientScope?.InstanceId.ToString() ?? "NULL")}. This typically indicates that a child {nameof(Scope)} was not disposed, or flowed to a child thread that was not awaited, or concurrent threads are accessing the same {nameof(Scope)} (Ambient context) which is not supported. If using Task.Run (or similar) as a fire and forget tasks or to run threads in parallel you must suppress execution context flow with ExecutionContext.SuppressFlow() and ExecutionContext.RestoreFlow()."; + var failedMessage = + $"The {nameof(Scope)} {InstanceId} being disposed is not the Ambient {nameof(Scope)} {_scopeProvider.AmbientScope?.InstanceId.ToString() ?? "NULL"}. This typically indicates that a child {nameof(Scope)} was not disposed, or flowed to a child thread that was not awaited, or concurrent threads are accessing the same {nameof(Scope)} (Ambient context) which is not supported. If using Task.Run (or similar) as a fire and forget tasks or to run threads in parallel you must suppress execution context flow with ExecutionContext.SuppressFlow() and ExecutionContext.RestoreFlow()."; #if DEBUG_SCOPES Scope ambient = _scopeProvider.AmbientScope; @@ -663,7 +531,8 @@ namespace Umbraco.Cms.Core.Scoping // Since we're only reading we don't have to be in a lock if (_readLocksDictionary?.Count > 0 || _writeLocksDictionary?.Count > 0) { - var exception = new InvalidOperationException($"All scopes has not been disposed from parent scope: {InstanceId}, see log for more details."); + var exception = new InvalidOperationException( + $"All scopes has not been disposed from parent scope: {InstanceId}, see log for more details."); _logger.LogError(exception, GenerateUnclearedScopesLogMessage()); throw exception; } @@ -697,35 +566,213 @@ namespace Umbraco.Cms.Core.Scoping _disposed = true; } + public void EagerReadLock(params int[] lockIds) => EagerReadLockInner(Database, InstanceId, null, lockIds); + + /// + public void ReadLock(params int[] lockIds) => LazyReadLockInner(InstanceId, lockIds); + + public void EagerReadLock(TimeSpan timeout, int lockId) => + EagerReadLockInner(Database, InstanceId, timeout, lockId); + + /// + public void ReadLock(TimeSpan timeout, int lockId) => LazyReadLockInner(InstanceId, timeout, lockId); + + public void EagerWriteLock(params int[] lockIds) => EagerWriteLockInner(Database, InstanceId, null, lockIds); + + /// + public void WriteLock(params int[] lockIds) => LazyWriteLockInner(InstanceId, lockIds); + + public void EagerWriteLock(TimeSpan timeout, int lockId) => + EagerWriteLockInner(Database, InstanceId, timeout, lockId); + + /// + public void WriteLock(TimeSpan timeout, int lockId) => LazyWriteLockInner(InstanceId, timeout, lockId); + /// - /// Generates a log message with all scopes that hasn't cleared their locks, including how many, and what locks they have requested. + /// Used for testing. Ensures and gets any queued read locks. + /// + /// + internal Dictionary> GetReadLocks() + { + EnsureDbLocks(); + // always delegate to root/parent scope. + if (ParentScope is not null) + { + return ParentScope.GetReadLocks(); + } + + return _readLocksDictionary; + } + + /// + /// Used for testing. Ensures and gets and queued write locks. + /// + /// + internal Dictionary> GetWriteLocks() + { + EnsureDbLocks(); + // always delegate to root/parent scope. + if (ParentScope is not null) + { + return ParentScope.GetWriteLocks(); + } + + return _writeLocksDictionary; + } + + public void Reset() => _completed = null; + + public void ChildCompleted(bool? completed) + { + // if child did not complete we cannot complete + if (completed.HasValue == false || completed.Value == false) + { + if (_coreDebugSettings.LogIncompletedScopes) + { + _logger.LogWarning("Uncompleted Child Scope at\r\n {StackTrace}", Environment.StackTrace); + } + + _completed = false; + } + } + + /// + /// When we require a ReadLock or a WriteLock we don't immediately request these locks from the database, + /// instead we only request them when necessary (lazily). + /// To do this, we queue requests for read/write locks. + /// This is so that if there's a request for either of these + /// locks, but the service/repository returns an item from the cache, we don't end up making a DB call to make the + /// read/write lock. + /// This executes the queue of requested locks in order in an efficient way lazily whenever the database instance is + /// resolved. + /// + private void EnsureDbLocks() + { + // always delegate to the root parent + if (ParentScope is not null) + { + ParentScope.EnsureDbLocks(); + } + else + { + lock (_lockQueueLocker) + { + if (_queuedLocks?.Count > 0) + { + LockType currentType = LockType.ReadLock; + TimeSpan currentTimeout = TimeSpan.Zero; + Guid currentInstanceId = InstanceId; + var collectedIds = new HashSet(); + + var i = 0; + while (_queuedLocks.Count > 0) + { + (LockType lockType, TimeSpan timeout, Guid instanceId, var lockId) = _queuedLocks.Dequeue(); + + if (i == 0) + { + currentType = lockType; + currentTimeout = timeout; + currentInstanceId = instanceId; + } + else if (lockType != currentType || timeout != currentTimeout || + instanceId != currentInstanceId) + { + // the lock type, instanceId or timeout switched. + // process the lock ids collected + switch (currentType) + { + case LockType.ReadLock: + EagerReadLockInner(_database, currentInstanceId, + currentTimeout == TimeSpan.Zero ? null : currentTimeout, + collectedIds.ToArray()); + break; + case LockType.WriteLock: + EagerWriteLockInner(_database, currentInstanceId, + currentTimeout == TimeSpan.Zero ? null : currentTimeout, + collectedIds.ToArray()); + break; + } + + // clear the collected and set new type + collectedIds.Clear(); + currentType = lockType; + currentTimeout = timeout; + currentInstanceId = instanceId; + } + + collectedIds.Add(lockId); + i++; + } + + // process the remaining + switch (currentType) + { + case LockType.ReadLock: + EagerReadLockInner(_database, currentInstanceId, + currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); + break; + case LockType.WriteLock: + EagerWriteLockInner(_database, currentInstanceId, + currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); + break; + } + } + } + } + } + + private void EnsureNotDisposed() + { + // We can't be disposed + if (_disposed) + { + throw new ObjectDisposedException($"The {nameof(Scope)} ({this.GetDebugInfo()}) is already disposed"); + } + + // And neither can our ancestors if we're trying to be disposed since + // a child must always be disposed before it's parent. + // This is a safety check, it's actually not entirely possible that a parent can be + // disposed before the child since that will end up with a "not the Ambient" exception. + ParentScope?.EnsureNotDisposed(); + + // TODO: safer? + //if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0) + // throw new ObjectDisposedException(GetType().FullName); + } + + /// + /// Generates a log message with all scopes that hasn't cleared their locks, including how many, and what locks they + /// have requested. /// /// Log message. private string GenerateUnclearedScopesLogMessage() { // Dump the dicts into a message for the locks. - StringBuilder builder = new StringBuilder(); - builder.AppendLine($"Lock counters aren't empty, suggesting a scope hasn't been properly disposed, parent id: {InstanceId}"); + var builder = new StringBuilder(); + builder.AppendLine( + $"Lock counters aren't empty, suggesting a scope hasn't been properly disposed, parent id: {InstanceId}"); WriteLockDictionaryToString(_readLocksDictionary, builder, "read locks"); WriteLockDictionaryToString(_writeLocksDictionary, builder, "write locks"); return builder.ToString(); } /// - /// Writes a locks dictionary to a for logging purposes. + /// Writes a locks dictionary to a for logging purposes. /// /// Lock dictionary to report on. /// String builder to write to. /// The name to report the dictionary as. - private void WriteLockDictionaryToString(Dictionary> dict, StringBuilder builder, string dictName) + private void WriteLockDictionaryToString(Dictionary> dict, StringBuilder builder, + string dictName) { if (dict?.Count > 0) { builder.AppendLine($"Remaining {dictName}:"); - foreach (var instance in dict) + foreach (KeyValuePair> instance in dict) { builder.AppendLine($"Scope {instance.Key}"); - foreach (var lockCounter in instance.Value) + foreach (KeyValuePair lockCounter in instance.Value) { builder.AppendLine($"\tLock ID: {lockCounter.Key} - times requested: {lockCounter.Value}"); } @@ -840,6 +887,7 @@ namespace Umbraco.Cms.Core.Scoping { _scopeProvider.PopAmbientScope(_scopeProvider.AmbientScope); } + if (OrigContext != _scopeProvider.AmbientContext) { _scopeProvider.PopAmbientScopeContext(); @@ -871,12 +919,9 @@ namespace Umbraco.Cms.Core.Scoping } } - // true if Umbraco.CoreDebugSettings.LogUncompletedScope appSetting is set to "true" - private bool LogUncompletedScopes => _coreDebugSettings.LogIncompletedScopes; - /// - /// Increment the counter of a locks dictionary, either ReadLocks or WriteLocks, - /// for a specific scope instance and lock identifier. Must be called within a lock. + /// Increment the counter of a locks dictionary, either ReadLocks or WriteLocks, + /// for a specific scope instance and lock identifier. Must be called within a lock. /// /// Lock ID to increment. /// Instance ID of the scope requesting the lock. @@ -888,7 +933,7 @@ namespace Umbraco.Cms.Core.Scoping locks ??= new Dictionary>(); // Try and get the dict associated with the scope id. - var locksDictFound = locks.TryGetValue(instanceId, out var locksDict); + var locksDictFound = locks.TryGetValue(instanceId, out Dictionary locksDict); if (locksDictFound) { locksDict.TryGetValue(lockId, out var value); @@ -903,7 +948,7 @@ namespace Umbraco.Cms.Core.Scoping } /// - /// Clears all lock counters for a given scope instance, signalling that the scope has been disposed. + /// Clears all lock counters for a given scope instance, signalling that the scope has been disposed. /// /// Instance ID of the scope to clear. private void ClearLocks(Guid instanceId) @@ -924,7 +969,8 @@ namespace Umbraco.Cms.Core.Scoping { // It's safe to assume that the locks on the top of the stack belong to this instance, // since any child scopes that might have added locks to the stack must be disposed before we try and dispose this instance. - var top = _queuedLocks.PeekStack(); + (LockType lockType, TimeSpan timeout, Guid instanceId, int lockId) top = + _queuedLocks.PeekStack(); if (top.instanceId == instanceId) { _queuedLocks.Pop(); @@ -938,26 +984,6 @@ namespace Umbraco.Cms.Core.Scoping } } - public void EagerReadLock(params int[] lockIds) => EagerReadLockInner(Database, InstanceId, null, lockIds); - - /// - public void ReadLock(params int[] lockIds) => LazyReadLockInner(InstanceId, lockIds); - - public void EagerReadLock(TimeSpan timeout, int lockId) => EagerReadLockInner(Database, InstanceId, timeout, lockId); - - /// - public void ReadLock(TimeSpan timeout, int lockId) => LazyReadLockInner(InstanceId, timeout, lockId); - - public void EagerWriteLock(params int[] lockIds) => EagerWriteLockInner(Database, InstanceId, null, lockIds); - - /// - public void WriteLock(params int[] lockIds) => LazyWriteLockInner(InstanceId, lockIds); - - public void EagerWriteLock(TimeSpan timeout, int lockId) => EagerWriteLockInner(Database, InstanceId, timeout, lockId); - - /// - public void WriteLock(TimeSpan timeout, int lockId) => LazyWriteLockInner(InstanceId, timeout, lockId); - public void LazyReadLockInner(Guid instanceId, params int[] lockIds) { if (ParentScope != null) @@ -1014,6 +1040,7 @@ namespace Umbraco.Cms.Core.Scoping { _queuedLocks = new StackQueue<(LockType, TimeSpan, Guid, int)>(); } + foreach (var lockId in lockIds) { _queuedLocks.Enqueue((lockType, TimeSpan.Zero, instanceId, lockId)); @@ -1029,12 +1056,13 @@ namespace Umbraco.Cms.Core.Scoping { _queuedLocks = new StackQueue<(LockType, TimeSpan, Guid, int)>(); } + _queuedLocks.Enqueue((lockType, timeout, instanceId, lockId)); } } /// - /// Handles acquiring a read lock, will delegate it to the parent if there are any. + /// Handles acquiring a read lock, will delegate it to the parent if there are any. /// /// Instance ID of the requesting scope. /// Optional database timeout in milliseconds. @@ -1049,12 +1077,13 @@ namespace Umbraco.Cms.Core.Scoping else { // We are the outermost scope, handle the lock request. - LockInner(db, instanceId, ref _readLocksDictionary, ref _readLocks, ObtainReadLock, ObtainTimeoutReadLock, timeout, lockIds); + LockInner(db, instanceId, ref _readLocksDictionary, ref _readLocks, ObtainReadLock, + ObtainTimeoutReadLock, timeout, lockIds); } } /// - /// Handles acquiring a write lock with a specified timeout, will delegate it to the parent if there are any. + /// Handles acquiring a write lock with a specified timeout, will delegate it to the parent if there are any. /// /// Instance ID of the requesting scope. /// Optional database timeout in milliseconds. @@ -1069,12 +1098,13 @@ namespace Umbraco.Cms.Core.Scoping else { // We are the outermost scope, handle the lock request. - LockInner(db, instanceId, ref _writeLocksDictionary, ref _writeLocks, ObtainWriteLock, ObtainTimeoutWriteLock, timeout, lockIds); + LockInner(db, instanceId, ref _writeLocksDictionary, ref _writeLocks, ObtainWriteLock, + ObtainTimeoutWriteLock, timeout, lockIds); } } /// - /// Handles acquiring a lock, this should only be called from the outermost scope. + /// Handles acquiring a lock, this should only be called from the outermost scope. /// /// Instance ID of the scope requesting the lock. /// Reference to the applicable locks dictionary (ReadLocks or WriteLocks). @@ -1083,8 +1113,10 @@ namespace Umbraco.Cms.Core.Scoping /// Delegate used to request the lock from the database with a timeout. /// Optional timeout parameter to specify a timeout. /// Lock identifiers to lock on. - private void LockInner(IUmbracoDatabase db, Guid instanceId, ref Dictionary> locks, ref HashSet locksSet, - Action obtainLock, Action obtainLockTimeout, TimeSpan? timeout, + private void LockInner(IUmbracoDatabase db, Guid instanceId, ref Dictionary> locks, + ref HashSet locksSet, + Action obtainLock, Action obtainLockTimeout, + TimeSpan? timeout, params int[] lockIds) { lock (_dictionaryLocker) @@ -1130,41 +1162,37 @@ namespace Umbraco.Cms.Core.Scoping } /// - /// Obtains an ordinary read lock. + /// Obtains an ordinary read lock. /// /// Lock object identifier to lock. - private void ObtainReadLock(IUmbracoDatabase db, int lockId) - { - SqlContext.SqlSyntax.ReadLock(db, lockId); - } + private void ObtainReadLock(IUmbracoDatabase db, int lockId) => SqlContext.SqlSyntax.ReadLock(db, lockId); /// - /// Obtains a read lock with a custom timeout. + /// Obtains a read lock with a custom timeout. /// /// Lock object identifier to lock. /// TimeSpan specifying the timout period. - private void ObtainTimeoutReadLock(IUmbracoDatabase db, int lockId, TimeSpan timeout) - { + private void ObtainTimeoutReadLock(IUmbracoDatabase db, int lockId, TimeSpan timeout) => SqlContext.SqlSyntax.ReadLock(db, timeout, lockId); - } /// - /// Obtains an ordinary write lock. + /// Obtains an ordinary write lock. /// /// Lock object identifier to lock. - private void ObtainWriteLock(IUmbracoDatabase db, int lockId) - { - SqlContext.SqlSyntax.WriteLock(db, lockId); - } + private void ObtainWriteLock(IUmbracoDatabase db, int lockId) => SqlContext.SqlSyntax.WriteLock(db, lockId); /// - /// Obtains a write lock with a custom timeout. + /// Obtains a write lock with a custom timeout. /// /// Lock object identifier to lock. /// TimeSpan specifying the timout period. - private void ObtainTimeoutWriteLock(IUmbracoDatabase db, int lockId, TimeSpan timeout) - { + private void ObtainTimeoutWriteLock(IUmbracoDatabase db, int lockId, TimeSpan timeout) => SqlContext.SqlSyntax.WriteLock(db, timeout, lockId); + + private enum LockType + { + ReadLock, + WriteLock } } } diff --git a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs index 26b638a436..f662000cd0 100644 --- a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs +++ b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs @@ -23,28 +23,28 @@ namespace Umbraco.Cms.Infrastructure.Search } /// - public IEnumerable GetBackOfficeFields() => _backOfficeFields; + public virtual IEnumerable GetBackOfficeFields() => _backOfficeFields; /// - public IEnumerable GetBackOfficeMembersFields() => _backOfficeMembersFields; + public virtual IEnumerable GetBackOfficeMembersFields() => _backOfficeMembersFields; /// - public IEnumerable GetBackOfficeMediaFields() => _backOfficeMediaFields; + public virtual IEnumerable GetBackOfficeMediaFields() => _backOfficeMediaFields; /// - public IEnumerable GetBackOfficeDocumentFields() => Enumerable.Empty(); + public virtual IEnumerable GetBackOfficeDocumentFields() => Enumerable.Empty(); /// - public ISet GetBackOfficeFieldsToLoad() => _backOfficeFieldsToLoad; + public virtual ISet GetBackOfficeFieldsToLoad() => _backOfficeFieldsToLoad; /// - public ISet GetBackOfficeMembersFieldsToLoad() => _backOfficeMembersFieldsToLoad; + public virtual ISet GetBackOfficeMembersFieldsToLoad() => _backOfficeMembersFieldsToLoad; /// - public ISet GetBackOfficeMediaFieldsToLoad() => _backOfficeMediaFieldsToLoad; + public virtual ISet GetBackOfficeMediaFieldsToLoad() => _backOfficeMediaFieldsToLoad; /// - public ISet GetBackOfficeDocumentFieldsToLoad() + public virtual ISet GetBackOfficeDocumentFieldsToLoad() { var fields = _backOfficeDocumentFieldsToLoad; diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs index 2182531ff9..020c18b221 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Querying; @@ -19,27 +19,29 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement { /// - /// Implements the content service. + /// Implements the content service. /// public class ContentService : RepositoryService, IContentService { - private readonly IDocumentRepository _documentRepository; - private readonly IEntityRepository _entityRepository; private readonly IAuditRepository _auditRepository; private readonly IContentTypeRepository _contentTypeRepository; private readonly IDocumentBlueprintRepository _documentBlueprintRepository; + private readonly IDocumentRepository _documentRepository; + private readonly IEntityRepository _entityRepository; private readonly ILanguageRepository _languageRepository; + private readonly ILogger _logger; private readonly Lazy _propertyValidationService; private readonly IShortStringHelper _shortStringHelper; - private readonly ILogger _logger; private IQuery _queryNotTrashed; #region Constructors public ContentService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository, - IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository, + IDocumentRepository documentRepository, IEntityRepository entityRepository, + IAuditRepository auditRepository, + IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, + ILanguageRepository languageRepository, Lazy propertyValidationService, IShortStringHelper shortStringHelper) : base(provider, loggerFactory, eventMessagesFactory) { @@ -60,7 +62,73 @@ namespace Umbraco.Cms.Core.Services.Implement // lazy-constructed because when the ctor runs, the query factory may not be ready - private IQuery QueryNotTrashed => _queryNotTrashed ?? (_queryNotTrashed = Query().Where(x => x.Trashed == false)); + private IQuery QueryNotTrashed => + _queryNotTrashed ?? (_queryNotTrashed = Query().Where(x => x.Trashed == false)); + + #endregion + + #region Rollback + + public OperationResult Rollback(int id, int versionId, string culture = "*", + int userId = Constants.Security.SuperUserId) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + + // Get the current copy of the node + IContent content = GetById(id); + + // Get the version + IContent version = GetVersion(versionId); + + // Good old null checks + if (content == null || version == null || content.Trashed) + { + return new OperationResult(OperationResultType.FailedCannot, evtMsgs); + } + + // Store the result of doing the save of content for the rollback + OperationResult rollbackSaveResult; + + using (IScope scope = ScopeProvider.CreateScope()) + { + var rollingBackNotification = new ContentRollingBackNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(rollingBackNotification)) + { + scope.Complete(); + return OperationResult.Cancel(evtMsgs); + } + + // Copy the changes from the version + content.CopyFrom(version, culture); + + // Save the content for the rollback + rollbackSaveResult = Save(content, userId); + + // Depending on the save result - is what we log & audit along with what we return + if (rollbackSaveResult.Success == false) + { + // Log the error/warning + _logger.LogError( + "User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, + id, versionId); + } + else + { + scope.Notifications.Publish( + new ContentRolledBackNotification(content, evtMsgs).WithStateFrom(rollingBackNotification)); + + // Logging & Audit message + _logger.LogInformation("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", + userId, id, versionId); + Audit(AuditType.RollBack, userId, id, + $"Content '{content.Name}' was rolled back to version '{versionId}'"); + } + + scope.Complete(); + } + + return rollbackSaveResult; + } #endregion @@ -68,36 +136,36 @@ namespace Umbraco.Cms.Core.Services.Implement public int CountPublished(string contentTypeAlias = null) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.CountPublished(contentTypeAlias); } } public int Count(string contentTypeAlias = null) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.Count(contentTypeAlias); } } public int CountChildren(int parentId, string contentTypeAlias = null) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.CountChildren(parentId, contentTypeAlias); } } public int CountDescendants(int parentId, string contentTypeAlias = null) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.CountDescendants(parentId, contentTypeAlias); } } @@ -107,46 +175,46 @@ namespace Umbraco.Cms.Core.Services.Implement #region Permissions /// - /// Used to bulk update the permissions set for a content item. This will replace all permissions - /// assigned to an entity with a list of user id & permission pairs. + /// Used to bulk update the permissions set for a content item. This will replace all permissions + /// assigned to an entity with a list of user id & permission pairs. /// /// public void SetPermissions(EntityPermissionSet permissionSet) { - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); _documentRepository.ReplaceContentPermissions(permissionSet); scope.Complete(); } } /// - /// Assigns a single permission to the current content item for the specified group ids + /// Assigns a single permission to the current content item for the specified group ids /// /// /// /// public void SetPermission(IContent entity, char permission, IEnumerable groupIds) { - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); _documentRepository.AssignEntityPermission(entity, permission, groupIds); scope.Complete(); } } /// - /// Returns implicit/inherited permissions assigned to the content item for all user groups + /// Returns implicit/inherited permissions assigned to the content item for all user groups /// /// /// public EntityPermissionCollection GetPermissions(IContent content) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetPermissionsForEntity(content.Id); } } @@ -156,53 +224,59 @@ namespace Umbraco.Cms.Core.Services.Implement #region Create /// - /// Creates an object using the alias of the - /// that this Content should based on. + /// Creates an object using the alias of the + /// that this Content should based on. /// /// - /// Note that using this method will simply return a new IContent without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects - /// that does not invoke a save operation against the database. + /// Note that using this method will simply return a new IContent without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects + /// that does not invoke a save operation against the database. /// /// Name of the Content object /// Id of Parent for the new Content - /// Alias of the + /// Alias of the /// Optional id of the user creating the content - /// - public IContent Create(string name, Guid parentId, string contentTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId) + /// + /// + /// + public IContent Create(string name, Guid parentId, string contentTypeAlias, + int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - var parent = GetById(parentId); + IContent parent = GetById(parentId); return Create(name, parent, contentTypeAlias, userId); } /// - /// Creates an object of a specified content type. + /// Creates an object of a specified content type. /// - /// This method simply returns a new, non-persisted, IContent without any identity. It - /// is intended as a shortcut to creating new content objects that does not invoke a save - /// operation against the database. + /// + /// This method simply returns a new, non-persisted, IContent without any identity. It + /// is intended as a shortcut to creating new content objects that does not invoke a save + /// operation against the database. /// /// The name of the content object. /// The identifier of the parent, or -1. /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent Create(string name, int parentId, string contentTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId) + public IContent Create(string name, int parentId, string contentTypeAlias, + int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - var contentType = GetContentType(contentTypeAlias); + IContentType contentType = GetContentType(contentTypeAlias); return Create(name, parentId, contentType, userId); } /// - /// Creates an object of a specified content type. + /// Creates an object of a specified content type. /// - /// This method simply returns a new, non-persisted, IContent without any identity. It - /// is intended as a shortcut to creating new content objects that does not invoke a save - /// operation against the database. + /// + /// This method simply returns a new, non-persisted, IContent without any identity. It + /// is intended as a shortcut to creating new content objects that does not invoke a save + /// operation against the database. /// /// The name of the content object. /// The identifier of the parent, or -1. @@ -217,7 +291,7 @@ namespace Umbraco.Cms.Core.Services.Implement throw new ArgumentException("Content type must be specified", nameof(contentType)); } - var parent = parentId > 0 ? GetById(parentId) : null; + IContent parent = parentId > 0 ? GetById(parentId) : null; if (parentId > 0 && parent is null) { throw new ArgumentException("No content with that id.", nameof(parentId)); @@ -229,26 +303,34 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Creates an object of a specified content type, under a parent. + /// Creates an object of a specified content type, under a parent. /// - /// This method simply returns a new, non-persisted, IContent without any identity. It - /// is intended as a shortcut to creating new content objects that does not invoke a save - /// operation against the database. + /// + /// This method simply returns a new, non-persisted, IContent without any identity. It + /// is intended as a shortcut to creating new content objects that does not invoke a save + /// operation against the database. /// /// The name of the content object. /// The parent content object. /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent Create(string name, IContent parent, string contentTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId) + public IContent Create(string name, IContent parent, string contentTypeAlias, + int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - if (parent == null) throw new ArgumentNullException(nameof(parent)); + if (parent == null) + { + throw new ArgumentNullException(nameof(parent)); + } - var contentType = GetContentType(contentTypeAlias); + IContentType contentType = GetContentType(contentTypeAlias); if (contentType == null) - throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback + { + throw new ArgumentException("No content type with that alias.", + nameof(contentTypeAlias)); // causes rollback + } var content = new Content(name, parent, contentType, userId); @@ -256,7 +338,7 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Creates an object of a specified content type. + /// Creates an object of a specified content type. /// /// This method returns a new, persisted, IContent with an identity. /// The name of the content object. @@ -264,24 +346,32 @@ namespace Umbraco.Cms.Core.Services.Implement /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId) + public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, + int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - using (var scope = ScopeProvider.CreateScope(autoComplete:true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { // locking the content tree secures content types too - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); - var contentType = GetContentType(contentTypeAlias); // + locks + IContentType contentType = GetContentType(contentTypeAlias); // + locks if (contentType == null) - throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback + { + throw new ArgumentException("No content type with that alias.", + nameof(contentTypeAlias)); // causes rollback + } - var parent = parentId > 0 ? GetById(parentId) : null; // + locks + IContent parent = parentId > 0 ? GetById(parentId) : null; // + locks if (parentId > 0 && parent == null) + { throw new ArgumentException("No content with that id.", nameof(parentId)); // causes rollback + } - var content = parentId > 0 ? new Content(name, parent, contentType, userId) : new Content(name, parentId, contentType, userId); + Content content = parentId > 0 + ? new Content(name, parent, contentType, userId) + : new Content(name, parentId, contentType, userId); Save(content, userId); @@ -290,7 +380,7 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Creates an object of a specified content type, under a parent. + /// Creates an object of a specified content type, under a parent. /// /// This method returns a new, persisted, IContent with an identity. /// The name of the content object. @@ -298,20 +388,27 @@ namespace Umbraco.Cms.Core.Services.Implement /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId) + public IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, + int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - if (parent == null) throw new ArgumentNullException(nameof(parent)); + if (parent == null) + { + throw new ArgumentNullException(nameof(parent)); + } - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { // locking the content tree secures content types too - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); - var contentType = GetContentType(contentTypeAlias); // + locks + IContentType contentType = GetContentType(contentTypeAlias); // + locks if (contentType == null) - throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback + { + throw new ArgumentException("No content type with that alias.", + nameof(contentTypeAlias)); // causes rollback + } var content = new Content(name, parent, contentType, userId); @@ -326,50 +423,59 @@ namespace Umbraco.Cms.Core.Services.Implement #region Get, Has, Is /// - /// Gets an object by Id + /// Gets an object by Id /// /// Id of the Content to retrieve - /// + /// + /// + /// public IContent GetById(int id) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.Get(id); } } /// - /// Gets an object by Id + /// Gets an object by Id /// /// Ids of the Content to retrieve - /// + /// + /// + /// public IEnumerable GetByIds(IEnumerable ids) { var idsA = ids.ToArray(); - if (idsA.Length == 0) return Enumerable.Empty(); - - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + if (idsA.Length == 0) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); - var items = _documentRepository.GetMany(idsA); + return Enumerable.Empty(); + } + + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + IEnumerable items = _documentRepository.GetMany(idsA); var index = items.ToDictionary(x => x.Id, x => x); - return idsA.Select(x => index.TryGetValue(x, out var c) ? c : null).WhereNotNull(); + return idsA.Select(x => index.TryGetValue(x, out IContent c) ? c : null).WhereNotNull(); } } /// - /// Gets an object by its 'UniqueId' + /// Gets an object by its 'UniqueId' /// /// Guid key of the Content to retrieve - /// + /// + /// + /// public IContent GetById(Guid key) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.Get(key); } } @@ -395,47 +501,62 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// /// /// /// /// - Attempt IContentServiceBase.Save(IEnumerable contents, int userId) => Attempt.Succeed(Save(contents, userId)); + Attempt IContentServiceBase.Save(IEnumerable contents, int userId) => + Attempt.Succeed(Save(contents, userId)); /// - /// Gets objects by Ids + /// Gets objects by Ids /// /// Ids of the Content to retrieve - /// + /// + /// + /// public IEnumerable GetByIds(IEnumerable ids) { - var idsA = ids.ToArray(); - if (idsA.Length == 0) return Enumerable.Empty(); - - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + Guid[] idsA = ids.ToArray(); + if (idsA.Length == 0) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); - var items = _documentRepository.GetMany(idsA); + return Enumerable.Empty(); + } + + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + IEnumerable items = _documentRepository.GetMany(idsA); var index = items.ToDictionary(x => x.Key, x => x); - return idsA.Select(x => index.TryGetValue(x, out var c) ? c : null).WhereNotNull(); + return idsA.Select(x => index.TryGetValue(x, out IContent c) ? c : null).WhereNotNull(); } } /// - public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords + public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, + out long totalRecords , IQuery filter = null, Ordering ordering = null) { - if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (pageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex)); + } + + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize)); + } if (ordering == null) - ordering = Ordering.By("sortOrder"); - - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + ordering = Ordering.By("sortOrder"); + } + + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetPage( Query().Where(x => x.ContentTypeId == contentTypeId), pageIndex, pageSize, out totalRecords, filter, ordering); @@ -443,17 +564,27 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering = null) + public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, + out long totalRecords, IQuery filter, Ordering ordering = null) { - if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (pageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex)); + } + + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize)); + } if (ordering == null) - ordering = Ordering.By("sortOrder"); - - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + ordering = Ordering.By("sortOrder"); + } + + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetPage( Query().Where(x => contentTypeIds.Contains(x.ContentTypeId)), pageIndex, pageSize, out totalRecords, filter, ordering); @@ -461,64 +592,64 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Gets a collection of objects by Level + /// Gets a collection of objects by Level /// /// The level to retrieve Content from - /// An Enumerable list of objects + /// An Enumerable list of objects /// Contrary to most methods, this method filters out trashed content items. public IEnumerable GetByLevel(int level) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); - var query = Query().Where(x => x.Level == level && x.Trashed == false); + scope.ReadLock(Constants.Locks.ContentTree); + IQuery query = Query().Where(x => x.Level == level && x.Trashed == false); return _documentRepository.Get(query); } } /// - /// Gets a specific version of an item. + /// Gets a specific version of an item. /// /// Id of the version to retrieve - /// An item + /// An item public IContent GetVersion(int versionId) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetVersion(versionId); } } /// - /// Gets a collection of an objects versions by Id + /// Gets a collection of an objects versions by Id /// /// - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetVersions(int id) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetAllVersions(id); } } /// - /// Gets a collection of an objects versions by Id + /// Gets a collection of an objects versions by Id /// - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetVersionsSlim(int id, int skip, int take) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetAllVersionsSlim(id, skip, take); } } /// - /// Gets a list of all version Ids for the given content item ordered so latest is first + /// Gets a list of all version Ids for the given content item ordered so latest is first /// /// /// The maximum number of rows to return @@ -532,22 +663,22 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Gets a collection of objects, which are ancestors of the current content. + /// Gets a collection of objects, which are ancestors of the current content. /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects public IEnumerable GetAncestors(int id) { // intentionally not locking - var content = GetById(id); + IContent content = GetById(id); return GetAncestors(content); } /// - /// Gets a collection of objects, which are ancestors of the current content. + /// Gets a collection of objects, which are ancestors of the current content. /// - /// to retrieve ancestors for - /// An Enumerable list of objects + /// to retrieve ancestors for + /// An Enumerable list of objects public IEnumerable GetAncestors(IContent content) { //null check otherwise we get exceptions @@ -570,16 +701,16 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Gets a collection of published objects by Parent Id + /// Gets a collection of published objects by Parent Id /// /// Id of the Parent to retrieve Children from - /// An Enumerable list of published objects + /// An Enumerable list of published objects public IEnumerable GetPublishedChildren(int id) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); - var query = Query().Where(x => x.ParentId == id && x.Published); + scope.ReadLock(Constants.Locks.ContentTree); + IQuery query = Query().Where(x => x.ParentId == id && x.Published); return _documentRepository.Get(query).OrderBy(x => x.SortOrder); } } @@ -588,17 +719,26 @@ namespace Umbraco.Cms.Core.Services.Implement public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, IQuery filter = null, Ordering ordering = null) { - if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (pageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex)); + } + + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize)); + } if (ordering == null) - ordering = Ordering.By("sortOrder"); - - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + ordering = Ordering.By("sortOrder"); + } - var query = Query().Where(x => x.ParentId == id); + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + + IQuery query = Query().Where(x => x.ParentId == id); return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } } @@ -608,93 +748,116 @@ namespace Umbraco.Cms.Core.Services.Implement IQuery filter = null, Ordering ordering = null) { if (ordering == null) - ordering = Ordering.By("Path"); - - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + ordering = Ordering.By("Path"); + } + + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); //if the id is System Root, then just get all - if (id != Cms.Core.Constants.System.Root) + if (id != Constants.System.Root) { - var contentPath = _entityRepository.GetAllPaths(Cms.Core.Constants.ObjectTypes.Document, id).ToArray(); + TreeEntityPath[] contentPath = + _entityRepository.GetAllPaths(Constants.ObjectTypes.Document, id).ToArray(); if (contentPath.Length == 0) { totalChildren = 0; return Enumerable.Empty(); } - return GetPagedLocked(GetPagedDescendantQuery(contentPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering); + + return GetPagedLocked(GetPagedDescendantQuery(contentPath[0].Path), pageIndex, pageSize, + out totalChildren, filter, ordering); } + return GetPagedLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering); } } private IQuery GetPagedDescendantQuery(string contentPath) { - var query = Query(); + IQuery query = Query(); if (!contentPath.IsNullOrWhiteSpace()) + { query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar)); + } + return query; } - private IEnumerable GetPagedLocked(IQuery query, long pageIndex, int pageSize, out long totalChildren, + private IEnumerable GetPagedLocked(IQuery query, long pageIndex, int pageSize, + out long totalChildren, IQuery filter, Ordering ordering) { - if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - if (ordering == null) throw new ArgumentNullException(nameof(ordering)); + if (pageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex)); + } + + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize)); + } + + if (ordering == null) + { + throw new ArgumentNullException(nameof(ordering)); + } return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } /// - /// Gets the parent of the current content as an item. + /// Gets the parent of the current content as an item. /// - /// Id of the to retrieve the parent from - /// Parent object + /// Id of the to retrieve the parent from + /// Parent object public IContent GetParent(int id) { // intentionally not locking - var content = GetById(id); + IContent content = GetById(id); return GetParent(content); } /// - /// Gets the parent of the current content as an item. + /// Gets the parent of the current content as an item. /// - /// to retrieve the parent from - /// Parent object + /// to retrieve the parent from + /// Parent object public IContent GetParent(IContent content) { - if (content.ParentId == Cms.Core.Constants.System.Root || content.ParentId == Cms.Core.Constants.System.RecycleBinContent) + if (content.ParentId == Constants.System.Root || content.ParentId == Constants.System.RecycleBinContent) + { return null; + } return GetById(content.ParentId); } /// - /// Gets a collection of objects, which reside at the first level / root + /// Gets a collection of objects, which reside at the first level / root /// - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetRootContent() { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); - var query = Query().Where(x => x.ParentId == Cms.Core.Constants.System.Root); + scope.ReadLock(Constants.Locks.ContentTree); + IQuery query = Query().Where(x => x.ParentId == Constants.System.Root); return _documentRepository.Get(query); } } /// - /// Gets all published content items + /// Gets all published content items /// /// internal IEnumerable GetAllPublished() { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.Get(QueryNotTrashed); } } @@ -702,9 +865,9 @@ namespace Umbraco.Cms.Core.Services.Implement /// public IEnumerable GetContentForExpiration(DateTime date) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetContentForExpiration(date); } } @@ -712,62 +875,69 @@ namespace Umbraco.Cms.Core.Services.Implement /// public IEnumerable GetContentForRelease(DateTime date) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetContentForRelease(date); } } /// - /// Gets a collection of an objects, which resides in the Recycle Bin + /// Gets a collection of an objects, which resides in the Recycle Bin /// - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords, IQuery filter = null, Ordering ordering = null) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { if (ordering == null) + { ordering = Ordering.By("Path"); + } - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); - var query = Query().Where(x => x.Path.StartsWith(Cms.Core.Constants.System.RecycleBinContentPathPrefix)); + scope.ReadLock(Constants.Locks.ContentTree); + IQuery query = Query() + .Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix)); return _documentRepository.GetPage(query, pageIndex, pageSize, out totalRecords, filter, ordering); } } /// - /// Checks whether an item has any children + /// Checks whether an item has any children /// - /// Id of the + /// Id of the /// True if the content has any children otherwise False - public bool HasChildren(int id) - { - return CountChildren(id) > 0; - } + public bool HasChildren(int id) => CountChildren(id) > 0; /// - /// Checks if the passed in can be published based on the ancestors publish state. + /// Checks if the passed in can be published based on the ancestors publish state. /// - /// to check if ancestors are published + /// to check if ancestors are published /// True if the Content can be published, otherwise False public bool IsPathPublishable(IContent content) { // fast - if (content.ParentId == Cms.Core.Constants.System.Root) return true; // root content is always publishable - if (content.Trashed) return false; // trashed content is never publishable + if (content.ParentId == Constants.System.Root) + { + return true; // root content is always publishable + } + + if (content.Trashed) + { + return false; // trashed content is never publishable + } // not trashed and has a parent: publishable if the parent is path-published - var parent = GetById(content.ParentId); + IContent parent = GetById(content.ParentId); return parent == null || IsPathPublished(parent); } public bool IsPathPublished(IContent content) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.IsPathPublished(content); } } @@ -777,17 +947,19 @@ namespace Umbraco.Cms.Core.Services.Implement #region Save, Publish, Unpublish /// - public OperationResult Save(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId, ContentScheduleCollection contentSchedule = null) + public OperationResult Save(IContent content, int userId = Constants.Security.SuperUserId, ContentScheduleCollection contentSchedule = null) { PublishedState publishedState = content.PublishedState; if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) { - throw new InvalidOperationException($"Cannot save (un)publishing content with name: {content.Name} - and state: {content.PublishedState}, use the dedicated SavePublished method."); + throw new InvalidOperationException( + $"Cannot save (un)publishing content with name: {content.Name} - and state: {content.PublishedState}, use the dedicated SavePublished method."); } if (content.Name != null && content.Name.Length > 255) { - throw new InvalidOperationException($"Content with the name {content.Name} cannot be more than 255 characters in length."); + throw new InvalidOperationException( + $"Content with the name {content.Name} cannot be more than 255 characters in length."); } EventMessages eventMessages = EventMessagesFactory.Get(); @@ -826,13 +998,15 @@ namespace Umbraco.Cms.Core.Services.Implement _documentRepository.PersistContentSchedule(content, contentSchedule); } - scope.Notifications.Publish(new ContentSavedNotification(content, eventMessages).WithStateFrom(savingNotification)); + scope.Notifications.Publish( + new ContentSavedNotification(content, eventMessages).WithStateFrom(savingNotification)); // TODO: we had code here to FORCE that this event can never be suppressed. But that just doesn't make a ton of sense?! // I understand that if its suppressed that the caches aren't updated, but that would be expected. If someone // is supressing events then I think it's expected that nothing will happen. They are probably doing it for perf // reasons like bulk import and in those cases we don't want this occuring. - scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshNode, eventMessages)); + scope.Notifications.Publish( + new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshNode, eventMessages)); if (culturesChanging != null) { @@ -842,7 +1016,9 @@ namespace Umbraco.Cms.Core.Services.Implement Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs); } else + { Audit(AuditType.Save, userId, content.Id); + } scope.Complete(); } @@ -851,7 +1027,7 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - public OperationResult Save(IEnumerable contents, int userId = Cms.Core.Constants.Security.SuperUserId) + public OperationResult Save(IEnumerable contents, int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); IContent[] contentsA = contents.ToArray(); @@ -865,7 +1041,7 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Cancel(eventMessages); } - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); foreach (IContent content in contentsA) { if (content.HasIdentity == false) @@ -878,11 +1054,13 @@ namespace Umbraco.Cms.Core.Services.Implement _documentRepository.Save(content); } - scope.Notifications.Publish(new ContentSavedNotification(contentsA, eventMessages).WithStateFrom(savingNotification)); + scope.Notifications.Publish( + new ContentSavedNotification(contentsA, eventMessages).WithStateFrom(savingNotification)); // TODO: See note above about supressing events - scope.Notifications.Publish(new ContentTreeChangeNotification(contentsA, TreeChangeTypes.RefreshNode, eventMessages)); + scope.Notifications.Publish( + new ContentTreeChangeNotification(contentsA, TreeChangeTypes.RefreshNode, eventMessages)); - Audit(AuditType.Save, userId == -1 ? 0 : userId, Cms.Core.Constants.System.Root, "Saved multiple content"); + Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Saved multiple content"); scope.Complete(); } @@ -891,25 +1069,34 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - public PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = Cms.Core.Constants.Security.SuperUserId) + public PublishResult SaveAndPublish(IContent content, string culture = "*", + int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - var publishedState = content.PublishedState; + PublishedState publishedState = content.PublishedState; if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) - throw new InvalidOperationException($"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method."); + { + throw new InvalidOperationException( + $"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method."); + } // cannot accept invariant (null or empty) culture for variant content type // cannot accept a specific culture for invariant content type (but '*' is ok) if (content.ContentType.VariesByCulture()) { if (culture.IsNullOrWhiteSpace()) + { throw new NotSupportedException("Invariant culture is not supported by variant content types."); + } } else { if (!culture.IsNullOrWhiteSpace() && culture != "*") - throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types."); + { + throw new NotSupportedException( + $"Culture \"{culture}\" is not supported by invariant content types."); + } } if (content.Name != null && content.Name.Length > 255) @@ -917,9 +1104,9 @@ namespace Umbraco.Cms.Core.Services.Implement throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); var allLangs = _languageRepository.GetMany().ToList(); @@ -939,30 +1126,39 @@ namespace Umbraco.Cms.Core.Services.Implement // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. content.PublishCulture(impact); - var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, + savingNotification.State, userId); scope.Complete(); return result; } } /// - public PublishResult SaveAndPublish(IContent content, string[] cultures, int userId = Cms.Core.Constants.Security.SuperUserId) + public PublishResult SaveAndPublish(IContent content, string[] cultures, + int userId = Constants.Security.SuperUserId) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (cultures == null) throw new ArgumentNullException(nameof(cultures)); + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + + if (cultures == null) + { + throw new ArgumentNullException(nameof(cultures)); + } if (content.Name != null && content.Name.Length > 255) { throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); var allLangs = _languageRepository.GetMany().ToList(); - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); var savingNotification = new ContentSavingNotification(content, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotification)) @@ -979,56 +1175,75 @@ namespace Umbraco.Cms.Core.Services.Implement } if (cultures.Any(x => x == null || x == "*")) - throw new InvalidOperationException("Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed"); + { + throw new InvalidOperationException( + "Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed"); + } - var impacts = cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x))); + IEnumerable impacts = + cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x))); // publish the culture(s) // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. - foreach (var impact in impacts) + foreach (CultureImpact impact in impacts) { content.PublishCulture(impact); } - var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, + savingNotification.State, userId); scope.Complete(); return result; } } /// - public PublishResult Unpublish(IContent content, string culture = "*", int userId = Cms.Core.Constants.Security.SuperUserId) + public PublishResult Unpublish(IContent content, string culture = "*", + int userId = Constants.Security.SuperUserId) { - if (content == null) throw new ArgumentNullException(nameof(content)); + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); culture = culture.NullOrWhiteSpaceAsNull(); - var publishedState = content.PublishedState; + PublishedState publishedState = content.PublishedState; if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) - throw new InvalidOperationException($"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method."); + { + throw new InvalidOperationException( + $"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method."); + } // cannot accept invariant (null or empty) culture for variant content type // cannot accept a specific culture for invariant content type (but '*' is ok) if (content.ContentType.VariesByCulture()) { if (culture == null) + { throw new NotSupportedException("Invariant culture is not supported by variant content types."); + } } else { if (culture != null && culture != "*") - throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types."); + { + throw new NotSupportedException( + $"Culture \"{culture}\" is not supported by invariant content types."); + } } // if the content is not published, nothing to do if (!content.Published) - return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); - - using (var scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); + } + + using (IScope scope = ScopeProvider.CreateScope()) + { + scope.WriteLock(Constants.Locks.ContentTree); var allLangs = _languageRepository.GetMany().ToList(); @@ -1047,7 +1262,8 @@ namespace Umbraco.Cms.Core.Services.Implement // to be non-routable so that when it's re-published all variants were as they were. content.PublishedState = PublishedState.Unpublishing; - var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, + savingNotification.State, userId); scope.Complete(); return result; } @@ -1061,7 +1277,8 @@ namespace Umbraco.Cms.Core.Services.Implement var removed = content.UnpublishCulture(culture); //save and publish any changes - var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, + savingNotification.State, userId); scope.Complete(); @@ -1069,7 +1286,9 @@ namespace Umbraco.Cms.Core.Services.Implement // were specified to be published which will be the case when removed is false. In that case // we want to swap the result type to PublishResultType.SuccessUnpublishAlready (that was the expectation before). if (result.Result == PublishResultType.FailedPublishNothingToPublish && !removed) + { return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); + } return result; } @@ -1077,32 +1296,39 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Saves a document and publishes/unpublishes any pending publishing changes made to the document. + /// Saves a document and publishes/unpublishes any pending publishing changes made to the document. /// /// - /// - /// This MUST NOT be called from within this service, this used to be a public API and must only be used outside of this service. - /// Internally in this service, calls must be made to CommitDocumentChangesInternal - /// - /// - /// This is the underlying logic for both publishing and unpublishing any document - /// Pending publishing/unpublishing changes on a document are made with calls to and - /// . - /// When publishing or unpublishing a single culture, or all cultures, use - /// and . But if the flexibility to both publish and unpublish in a single operation is required - /// then this method needs to be used in combination with and - /// on the content itself - this prepares the content, but does not commit anything - and then, invoke - /// to actually commit the changes to the database. - /// The document is *always* saved, even when publishing fails. + /// + /// This MUST NOT be called from within this service, this used to be a public API and must only be used outside of + /// this service. + /// Internally in this service, calls must be made to CommitDocumentChangesInternal + /// + /// This is the underlying logic for both publishing and unpublishing any document + /// + /// Pending publishing/unpublishing changes on a document are made with calls to + /// and + /// . + /// + /// + /// When publishing or unpublishing a single culture, or all cultures, use + /// and . But if the flexibility to both publish and unpublish in a single operation is + /// required + /// then this method needs to be used in combination with + /// and + /// on the content itself - this prepares the content, but does not commit anything - and then, invoke + /// to actually commit the changes to the database. + /// + /// The document is *always* saved, even when publishing fails. /// internal PublishResult CommitDocumentChanges(IContent content, - int userId = Cms.Core.Constants.Security.SuperUserId) + int userId = Constants.Security.SuperUserId) { - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); var savingNotification = new ContentSavingNotification(content, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotification)) @@ -1112,14 +1338,15 @@ namespace Umbraco.Cms.Core.Services.Implement var allLangs = _languageRepository.GetMany().ToList(); - var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, + savingNotification.State, userId); scope.Complete(); return result; } } /// - /// Handles a lot of business logic cases for how the document should be persisted + /// Handles a lot of business logic cases for how the document should be persisted /// /// /// @@ -1131,10 +1358,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// /// - /// - /// Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for pending scheduled publishing, etc... is dealt with in this method. - /// There is quite a lot of cases to take into account along with logic that needs to deal with scheduled saving/publishing, branch saving/publishing, etc... - /// + /// + /// Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for + /// pending scheduled publishing, etc... is dealt with in this method. + /// There is quite a lot of cases to take into account along with logic that needs to deal with scheduled + /// saving/publishing, branch saving/publishing, etc... + /// /// private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content, EventMessages eventMessages, IReadOnlyCollection allLangs, @@ -1161,7 +1390,8 @@ namespace Umbraco.Cms.Core.Services.Implement PublishResult unpublishResult = null; // nothing set = republish it all - if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing) + if (content.PublishedState != PublishedState.Publishing && + content.PublishedState != PublishedState.Unpublishing) { content.PublishedState = PublishedState.Publishing; } @@ -1204,18 +1434,20 @@ namespace Umbraco.Cms.Core.Services.Implement //determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo culturesUnpublishing = content.GetCulturesUnpublishing(); culturesPublishing = variesByCulture - ? content.PublishCultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() - : null; + ? content.PublishCultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() + : null; // ensure that the document can be published, and publish handling events, business rules, etc - publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, eventMessages, allLangs, notificationState); + publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ !branchOne || branchRoot, + culturesPublishing, culturesUnpublishing, eventMessages, allLangs, notificationState); if (publishResult.Success) { // note: StrategyPublish flips the PublishedState to Publishing! publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, eventMessages); //check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole - if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos.Count == 0) + if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && + content.PublishCultureInfos.Count == 0) { // This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures // so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that @@ -1260,7 +1492,8 @@ namespace Umbraco.Cms.Core.Services.Implement IContent newest = GetById(content.Id); // ensure we have the newest version - in scope if (content.VersionId != newest.VersionId) { - return new PublishResult(PublishResultType.FailedPublishConcurrencyViolation, eventMessages, content); + return new PublishResult(PublishResultType.FailedPublishConcurrencyViolation, eventMessages, + content); } if (content.Published) @@ -1297,27 +1530,32 @@ namespace Umbraco.Cms.Core.Services.Implement SaveDocument(content); // raise the Saved event, always - scope.Notifications.Publish(new ContentSavedNotification(content, eventMessages).WithState(notificationState)); + scope.Notifications.Publish( + new ContentSavedNotification(content, eventMessages).WithState(notificationState)); if (unpublishing) // we have tried to unpublish - won't happen in a branch { if (unpublishResult.Success) // and succeeded, trigger events { // events and audit - scope.Notifications.Publish(new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState)); - scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); + scope.Notifications.Publish( + new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState)); + scope.Notifications.Publish(new ContentTreeChangeNotification(content, + TreeChangeTypes.RefreshBranch, eventMessages)); if (culturesUnpublishing != null) { // This will mean that that we unpublished a mandatory culture or we unpublished the last culture. var langs = string.Join(", ", allLangs - .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) - .Select(x => x.CultureName)); + .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) + .Select(x => x.CultureName)); Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs); if (publishResult == null) + { throw new PanicException("publishResult == null - should not happen"); + } switch (publishResult.Result) { @@ -1325,15 +1563,18 @@ namespace Umbraco.Cms.Core.Services.Implement //occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture) //log that the whole content item has been unpublished due to mandatory culture unpublished - Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)"); - return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, eventMessages, content); + Audit(AuditType.Unpublish, userId, content.Id, + "Unpublished (mandatory language unpublished)"); + return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, + eventMessages, content); case PublishResultType.SuccessUnpublishCulture: //occurs when the last culture is unpublished - Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)"); - return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, eventMessages, content); + Audit(AuditType.Unpublish, userId, content.Id, + "Unpublished (last language unpublished)"); + return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, eventMessages, + content); } - } Audit(AuditType.Unpublish, userId, content.Id); @@ -1362,8 +1603,10 @@ namespace Umbraco.Cms.Core.Services.Implement // invalidate the node/branch if (!branchOne) // for branches, handled by SaveAndPublishBranch { - scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages)); - scope.Notifications.Publish(new ContentPublishedNotification(content, eventMessages).WithState(notificationState)); + scope.Notifications.Publish( + new ContentTreeChangeNotification(content, changeType, eventMessages)); + scope.Notifications.Publish( + new ContentPublishedNotification(content, eventMessages).WithState(notificationState)); } // it was not published and now is... descendants that were 'published' (but @@ -1372,7 +1615,8 @@ namespace Umbraco.Cms.Core.Services.Implement if (!branchOne && isNew == false && previouslyPublished == false && HasChildren(content.Id)) { IContent[] descendants = GetPublishedDescendantsLocked(content).ToArray(); - scope.Notifications.Publish(new ContentPublishedNotification(descendants, eventMessages).WithState(notificationState)); + scope.Notifications.Publish( + new ContentPublishedNotification(descendants, eventMessages).WithState(notificationState)); } switch (publishResult.Result) @@ -1386,17 +1630,21 @@ namespace Umbraco.Cms.Core.Services.Implement var langs = string.Join(", ", allLangs .Where(x => culturesPublishing.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); - Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs); + Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", + langs); } + break; case PublishResultType.SuccessUnpublishCulture: if (culturesUnpublishing != null) { var langs = string.Join(", ", allLangs - .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) - .Select(x => x.CultureName)); - Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs); + .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) + .Select(x => x.CultureName)); + Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", + langs); } + break; } @@ -1435,7 +1683,7 @@ namespace Umbraco.Cms.Core.Services.Implement public IEnumerable PerformScheduledPublish(DateTime date) { var allLangs = new Lazy>(() => _languageRepository.GetMany().ToList()); - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); var results = new List(); PerformScheduledPublishingRelease(date, results, evtMsgs, allLangs); @@ -1444,17 +1692,18 @@ namespace Umbraco.Cms.Core.Services.Implement return results; } - private void PerformScheduledPublishingExpiration(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs) + private void PerformScheduledPublishingExpiration(DateTime date, List results, + EventMessages evtMsgs, Lazy> allLangs) { - using var scope = ScopeProvider.CreateScope(); + using IScope scope = ScopeProvider.CreateScope(); // do a fast read without any locks since this executes often to see if we even need to proceed if (_documentRepository.HasContentForExpiration(date)) { // now take a write lock since we'll be updating - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); - foreach (var d in _documentRepository.GetContentForExpiration(date)) + foreach (IContent d in _documentRepository.GetContentForExpiration(date)) { ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(d.Id); if (d.ContentType.VariesByCulture()) @@ -1466,7 +1715,9 @@ namespace Umbraco.Cms.Core.Services.Implement .ToList(); if (pendingCultures.Count == 0) + { continue; //shouldn't happen but no point in processing this document if there's nothing there + } var savingNotification = new ContentSavingNotification(d, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotification)) @@ -1484,20 +1735,28 @@ namespace Umbraco.Cms.Core.Services.Implement } _documentRepository.PersistContentSchedule(d, contentSchedule); - var result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId); + PublishResult result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, + savingNotification.State, d.WriterId); if (result.Success == false) - _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - results.Add(result); + { + _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, + result.Result); + } + results.Add(result); } else { //Clear this schedule for this culture contentSchedule.Clear(ContentScheduleAction.Expire, date); _documentRepository.PersistContentSchedule(d, contentSchedule); - var result = Unpublish(d, userId: d.WriterId); + PublishResult result = Unpublish(d, userId: d.WriterId); if (result.Success == false) - _logger.LogError(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); + { + _logger.LogError(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", + d.Id, result.Result); + } + results.Add(result); } } @@ -1508,17 +1767,18 @@ namespace Umbraco.Cms.Core.Services.Implement scope.Complete(); } - private void PerformScheduledPublishingRelease(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs) + private void PerformScheduledPublishingRelease(DateTime date, List results, + EventMessages evtMsgs, Lazy> allLangs) { - using var scope = ScopeProvider.CreateScope(); + using IScope scope = ScopeProvider.CreateScope(); // do a fast read without any locks since this executes often to see if we even need to proceed if (_documentRepository.HasContentForRelease(date)) { // now take a write lock since we'll be updating - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); - foreach (var d in _documentRepository.GetContentForRelease(date)) + foreach (IContent d in _documentRepository.GetContentForRelease(date)) { ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(d.Id); if (d.ContentType.VariesByCulture()) @@ -1530,7 +1790,9 @@ namespace Umbraco.Cms.Core.Services.Implement .ToList(); if (pendingCultures.Count == 0) + { continue; //shouldn't happen but no point in processing this document if there's nothing there + } var savingNotification = new ContentSavingNotification(d, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotification)) @@ -1546,36 +1808,52 @@ namespace Umbraco.Cms.Core.Services.Implement contentSchedule.Clear(culture, ContentScheduleAction.Release, date); if (d.Trashed) + { continue; // won't publish + } //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed IProperty[] invalidProperties = null; var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs.Value, culture)); - var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); + var tryPublish = d.PublishCulture(impact) && + _propertyValidationService.Value.IsPropertyDataValid(d, + out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) - _logger.LogWarning("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", + { + _logger.LogWarning( + "Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias))); + } publishing &= tryPublish; //set the culture to be published if (!publishing) - continue; // move to next document + { + continue; + } } PublishResult result; if (d.Trashed) + { result = new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d); + } else if (!publishing) + { result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d); + } else { _documentRepository.PersistContentSchedule(d, contentSchedule); - result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId); + result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, + savingNotification.State, d.WriterId); } - if (result.Success == false) - _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); + { + _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, + result.Result); + } results.Add(result); } @@ -1597,21 +1875,24 @@ namespace Umbraco.Cms.Core.Services.Implement } if (result.Success == false) - _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); + { + _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, + result.Result); + } results.Add(result); } } _documentRepository.ClearSchedule(date, ContentScheduleAction.Release); - } scope.Complete(); } // utility 'PublishCultures' func used by SaveAndPublishBranch - private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish, IReadOnlyCollection allLangs) + private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish, + IReadOnlyCollection allLangs) { //TODO: This does not support being able to return invalid property details to bubble up to the UI @@ -1622,7 +1903,8 @@ namespace Umbraco.Cms.Core.Services.Implement return culturesToPublish.All(culture => { var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content); - return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact); + return content.PublishCulture(impact) && + _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact); }); } @@ -1631,26 +1913,43 @@ namespace Umbraco.Cms.Core.Services.Implement } // utility 'ShouldPublish' func used by SaveAndPublishBranch - private HashSet SaveAndPublishBranch_ShouldPublish(ref HashSet cultures, string c, bool published, bool edited, bool isRoot, bool force) + private HashSet SaveAndPublishBranch_ShouldPublish(ref HashSet cultures, string c, + bool published, bool edited, bool isRoot, bool force) { // if published, republish if (published) { - if (cultures == null) cultures = new HashSet(); // empty means 'already published' - if (edited) cultures.Add(c); // means 'republish this culture' + if (cultures == null) + { + cultures = new HashSet(); // empty means 'already published' + } + + if (edited) + { + cultures.Add(c); // means 'republish this culture' + } + return cultures; } // if not published, publish if force/root else do nothing - if (!force && !isRoot) return cultures; // null means 'nothing to do' + if (!force && !isRoot) + { + return cultures; // null means 'nothing to do' + } + + if (cultures == null) + { + cultures = new HashSet(); + } - if (cultures == null) cultures = new HashSet(); cultures.Add(c); // means 'publish this culture' return cultures; } /// - public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Cms.Core.Constants.Security.SuperUserId) + public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", + int userId = Constants.Security.SuperUserId) { // note: EditedValue and PublishedValue are objects here, so it is important to .Equals() // and not to == them, else we would be comparing references, and that is a bad thing @@ -1667,10 +1966,16 @@ namespace Umbraco.Cms.Core.Services.Implement HashSet culturesToPublish = null; if (!c.ContentType.VariesByCulture()) // invariant content type - return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force); + { + return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, + force); + } if (culture != "*") // variant content type, specific culture - return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force); + { + return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, + c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force); + } // variant content type, all cultures if (c.Published) @@ -1678,7 +1983,11 @@ namespace Umbraco.Cms.Core.Services.Implement // then some (and maybe all) cultures will be 'already published' (unless forcing), // others will have to 'republish this culture' foreach (var x in c.AvailableCultures) - SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force); + { + SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), + c.IsCultureEdited(x), isRoot, force); + } + return culturesToPublish; } @@ -1692,7 +2001,8 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - public IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Cms.Core.Constants.Security.SuperUserId) + public IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, + int userId = Constants.Security.SuperUserId) { // note: EditedValue and PublishedValue are objects here, so it is important to .Equals() // and not to == them, else we would be comparing references, and that is a bad thing @@ -1707,7 +2017,10 @@ namespace Umbraco.Cms.Core.Services.Implement HashSet culturesToPublish = null; if (!c.ContentType.VariesByCulture()) // invariant content type - return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force); + { + return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, + force); + } // variant content type, specific cultures if (c.Published) @@ -1715,7 +2028,11 @@ namespace Umbraco.Cms.Core.Services.Implement // then some (and maybe all) cultures will be 'already published' (unless forcing), // others will have to 'republish this culture' foreach (var x in cultures) - SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force); + { + SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), + c.IsCultureEdited(x), isRoot, force); + } + return culturesToPublish; } @@ -1731,7 +2048,7 @@ namespace Umbraco.Cms.Core.Services.Implement internal IEnumerable SaveAndPublishBranch(IContent document, bool force, Func> shouldPublish, Func, IReadOnlyCollection, bool> publishCultures, - int userId = Cms.Core.Constants.Security.SuperUserId) + int userId = Constants.Security.SuperUserId) { if (shouldPublish == null) { @@ -1749,7 +2066,7 @@ namespace Umbraco.Cms.Core.Services.Implement using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); var allLangs = _languageRepository.GetMany().ToList(); @@ -1765,7 +2082,8 @@ namespace Umbraco.Cms.Core.Services.Implement } // deal with the branch root - if it fails, abort - PublishResult result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs); + PublishResult result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, + publishedDocuments, eventMessages, userId, allLangs); if (result != null) { results.Add(result); @@ -1787,7 +2105,8 @@ namespace Umbraco.Cms.Core.Services.Implement count = 0; // important to order by Path ASC so make it explicit in case defaults change // ReSharper disable once RedundantArgumentDefaultValue - foreach (IContent d in GetPagedDescendants(document.Id, page, pageSize, out _, ordering: Ordering.By("Path", Direction.Ascending))) + foreach (IContent d in GetPagedDescendants(document.Id, page, pageSize, out _, + ordering: Ordering.By("Path", Direction.Ascending))) { count++; @@ -1799,7 +2118,8 @@ namespace Umbraco.Cms.Core.Services.Implement } // no need to check path here, parent has to be published here - result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs); + result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, + publishedDocuments, eventMessages, userId, allLangs); if (result != null) { results.Add(result); @@ -1820,7 +2140,8 @@ namespace Umbraco.Cms.Core.Services.Implement // trigger events for the entire branch // (SaveAndPublishBranchOne does *not* do it) - scope.Notifications.Publish(new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages)); + scope.Notifications.Publish( + new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages)); scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages)); scope.Complete(); @@ -1839,11 +2160,16 @@ namespace Umbraco.Cms.Core.Services.Implement ICollection publishedDocuments, EventMessages evtMsgs, int userId, IReadOnlyCollection allLangs) { - var culturesToPublish = shouldPublish(document); + HashSet culturesToPublish = shouldPublish(document); if (culturesToPublish == null) // null = do not include + { return null; + } + if (culturesToPublish.Count == 0) // empty = already published + { return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document); + } var savingNotification = new ContentSavingNotification(document, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotification)) @@ -1858,9 +2184,13 @@ namespace Umbraco.Cms.Core.Services.Implement return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document); } - var result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, savingNotification.State, userId, branchOne: true, branchRoot: isRoot); + PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, + savingNotification.State, userId, true, isRoot); if (result.Success) + { publishedDocuments.Add(document); + } + return result; } @@ -1869,7 +2199,7 @@ namespace Umbraco.Cms.Core.Services.Implement #region Delete /// - public OperationResult Delete(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) + public OperationResult Delete(IContent content, int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -1881,7 +2211,7 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Cancel(eventMessages); } - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); // if it's not trashed yet, and published, we should unpublish // but... Unpublishing event makes no sense (not going to cancel?) and no need to save @@ -1893,7 +2223,8 @@ namespace Umbraco.Cms.Core.Services.Implement DeleteLocked(scope, content, eventMessages); - scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.Remove, eventMessages)); + scope.Notifications.Publish( + new ContentTreeChangeNotification(content, TreeChangeTypes.Remove, eventMessages)); Audit(AuditType.Delete, userId, content.Id); scope.Complete(); @@ -1917,10 +2248,14 @@ namespace Umbraco.Cms.Core.Services.Implement while (total > 0) { //get descendants - ordered from deepest to shallowest - var descendants = GetPagedDescendants(content.Id, 0, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); - foreach (var c in descendants) + IEnumerable descendants = GetPagedDescendants(content.Id, 0, pageSize, out total, + ordering: Ordering.By("Path", Direction.Descending)); + foreach (IContent c in descendants) + { DoDelete(c); + } } + DoDelete(content); } @@ -1930,50 +2265,54 @@ namespace Umbraco.Cms.Core.Services.Implement // the version referencing the file will not be there anymore. SO, we can leak files. /// - /// Permanently deletes versions from an object prior to a specific date. - /// This method will never delete the latest version of a content item. + /// Permanently deletes versions from an object prior to a specific date. + /// This method will never delete the latest version of a content item. /// - /// Id of the object to delete versions from + /// Id of the object to delete versions from /// Latest version date /// Optional Id of the User deleting versions of a Content object - public void DeleteVersions(int id, DateTime versionDate, int userId = Cms.Core.Constants.Security.SuperUserId) + public void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - var deletingVersionsNotification = new ContentDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); + var deletingVersionsNotification = + new ContentDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) { scope.Complete(); return; } - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); _documentRepository.DeleteVersions(id, versionDate); - scope.Notifications.Publish(new ContentDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(deletingVersionsNotification)); - Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete (by version date)"); + scope.Notifications.Publish( + new ContentDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom( + deletingVersionsNotification)); + Audit(AuditType.Delete, userId, Constants.System.Root, "Delete (by version date)"); scope.Complete(); } } /// - /// Permanently deletes specific version(s) from an object. - /// This method will never delete the latest version of a content item. + /// Permanently deletes specific version(s) from an object. + /// This method will never delete the latest version of a content item. /// - /// Id of the object to delete a version from + /// Id of the object to delete a version from /// Id of the version to delete /// Boolean indicating whether to delete versions prior to the versionId /// Optional Id of the User deleting versions of a Content object - public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Cms.Core.Constants.Security.SuperUserId) + public void DeleteVersion(int id, int versionId, bool deletePriorVersions, + int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - var deletingVersionsNotification = new ContentDeletingVersionsNotification(id, evtMsgs, specificVersion: versionId); + var deletingVersionsNotification = new ContentDeletingVersionsNotification(id, evtMsgs, versionId); if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) { scope.Complete(); @@ -1982,17 +2321,22 @@ namespace Umbraco.Cms.Core.Services.Implement if (deletePriorVersions) { - var content = GetVersion(versionId); + IContent content = GetVersion(versionId); DeleteVersions(id, content.UpdateDate, userId); } - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); - var c = _documentRepository.Get(id); - if (c.VersionId != versionId && c.PublishedVersionId != versionId) // don't delete the current or published version + scope.WriteLock(Constants.Locks.ContentTree); + IContent c = _documentRepository.Get(id); + if (c.VersionId != versionId && + c.PublishedVersionId != versionId) // don't delete the current or published version + { _documentRepository.DeleteVersion(versionId); + } - scope.Notifications.Publish(new ContentDeletedVersionsNotification(id, evtMsgs, specificVersion: versionId).WithStateFrom(deletingVersionsNotification)); - Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete (by version)"); + scope.Notifications.Publish( + new ContentDeletedVersionsNotification(id, evtMsgs, versionId).WithStateFrom( + deletingVersionsNotification)); + Audit(AuditType.Delete, userId, Constants.System.Root, "Delete (by version)"); scope.Complete(); } @@ -2003,19 +2347,21 @@ namespace Umbraco.Cms.Core.Services.Implement #region Move, RecycleBin /// - public OperationResult MoveToRecycleBin(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) + public OperationResult MoveToRecycleBin(IContent content, int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); var moves = new List<(IContent, string)>(); using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); var originalPath = content.Path; - var moveEventInfo = new MoveEventInfo(content, originalPath, Cms.Core.Constants.System.RecycleBinContent); + var moveEventInfo = + new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent); - var movingToRecycleBinNotification = new ContentMovingToRecycleBinNotification(moveEventInfo, eventMessages); + var movingToRecycleBinNotification = + new ContentMovingToRecycleBinNotification(moveEventInfo, eventMessages); if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification)) { scope.Complete(); @@ -2028,14 +2374,17 @@ namespace Umbraco.Cms.Core.Services.Implement //if (content.HasPublishedVersion) //{ } - PerformMoveLocked(content, Cms.Core.Constants.System.RecycleBinContent, null, userId, moves, true); - scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); + PerformMoveLocked(content, Constants.System.RecycleBinContent, null, userId, moves, true); + scope.Notifications.Publish( + new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); MoveEventInfo[] moveInfo = moves .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); - scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfo, eventMessages).WithStateFrom(movingToRecycleBinNotification)); + scope.Notifications.Publish( + new ContentMovedToRecycleBinNotification(moveInfo, eventMessages).WithStateFrom( + movingToRecycleBinNotification)); Audit(AuditType.Move, userId, content.Id, "Moved to recycle bin"); scope.Complete(); @@ -2045,20 +2394,20 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Moves an object to a new location by changing its parent id. + /// Moves an object to a new location by changing its parent id. /// /// - /// If the object is already published it will be - /// published after being moved to its new location. Otherwise it'll just - /// be saved with a new parent id. + /// If the object is already published it will be + /// published after being moved to its new location. Otherwise it'll just + /// be saved with a new parent id. /// - /// The to move + /// The to move /// Id of the Content's new Parent /// Optional Id of the User moving the Content - public void Move(IContent content, int parentId, int userId = Cms.Core.Constants.Security.SuperUserId) + public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId) { // if moving to the recycle bin then use the proper method - if (parentId == Cms.Core.Constants.System.RecycleBinContent) + if (parentId == Constants.System.RecycleBinContent) { MoveToRecycleBin(content, userId); return; @@ -2070,10 +2419,10 @@ namespace Umbraco.Cms.Core.Services.Implement using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); - IContent parent = parentId == Cms.Core.Constants.System.Root ? null : GetById(parentId); - if (parentId != Cms.Core.Constants.System.Root && (parent == null || parent.Trashed)) + IContent parent = parentId == Constants.System.Root ? null : GetById(parentId); + if (parentId != Constants.System.Root && (parent == null || parent.Trashed)) { throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback } @@ -2104,13 +2453,15 @@ namespace Umbraco.Cms.Core.Services.Implement PerformMoveLocked(content, parentId, parent, userId, moves, trashed); - scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); + scope.Notifications.Publish( + new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); MoveEventInfo[] moveInfo = moves //changes .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); - scope.Notifications.Publish(new ContentMovedNotification(moveInfo, eventMessages).WithStateFrom(movingNotification)); + scope.Notifications.Publish( + new ContentMovedNotification(moveInfo, eventMessages).WithStateFrom(movingNotification)); Audit(AuditType.Move, userId, content.Id); @@ -2147,17 +2498,21 @@ namespace Umbraco.Cms.Core.Services.Implement // if uow is not immediate, content.Path will be updated only when the UOW commits, // and because we want it now, we have to calculate it by ourselves //paths[content.Id] = content.Path; - paths[content.Id] = (parent == null ? (parentId == Cms.Core.Constants.System.RecycleBinContent ? "-1,-20" : Cms.Core.Constants.System.RootString) : parent.Path) + "," + content.Id; + paths[content.Id] = + (parent == null + ? parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString + : parent.Path) + "," + content.Id; const int pageSize = 500; - var query = GetPagedDescendantQuery(originalPath); + IQuery query = GetPagedDescendantQuery(originalPath); long total; do { // We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced - var descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + IEnumerable descendants = + GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path")); - foreach (var descendant in descendants) + foreach (IContent descendant in descendants) { moves.Add((descendant, descendant.Path)); // capture original path @@ -2166,32 +2521,34 @@ namespace Umbraco.Cms.Core.Services.Implement descendant.Level += levelDelta; PerformMoveContentLocked(descendant, userId, trash); } - } while (total > pageSize); - } private void PerformMoveContentLocked(IContent content, int userId, bool? trash) { - if (trash.HasValue) ((ContentBase)content).Trashed = trash.Value; + if (trash.HasValue) + { + ((ContentBase)content).Trashed = trash.Value; + } + content.WriterId = userId; _documentRepository.Save(content); } /// - /// Empties the Recycle Bin by deleting all that resides in the bin + /// Empties the Recycle Bin by deleting all that resides in the bin /// - public OperationResult EmptyRecycleBin(int userId = Cms.Core.Constants.Security.SuperUserId) + public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId) { var deleted = new List(); EventMessages eventMessages = EventMessagesFactory.Get(); using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); // emptying the recycle bin means deleting whatever is in there - do it properly! - IQuery query = Query().Where(x => x.ParentId == Cms.Core.Constants.System.RecycleBinContent); + IQuery query = Query().Where(x => x.ParentId == Constants.System.RecycleBinContent); IContent[] contents = _documentRepository.Get(query).ToArray(); var emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(contents, eventMessages); @@ -2207,9 +2564,12 @@ namespace Umbraco.Cms.Core.Services.Implement deleted.Add(content); } - scope.Notifications.Publish(new ContentEmptiedRecycleBinNotification(deleted, eventMessages).WithStateFrom(emptyingRecycleBinNotification)); - scope.Notifications.Publish(new ContentTreeChangeNotification(deleted, TreeChangeTypes.Remove, eventMessages)); - Audit(AuditType.Delete, userId, Cms.Core.Constants.System.RecycleBinContent, "Recycle bin emptied"); + scope.Notifications.Publish( + new ContentEmptiedRecycleBinNotification(deleted, eventMessages).WithStateFrom( + emptyingRecycleBinNotification)); + scope.Notifications.Publish( + new ContentTreeChangeNotification(deleted, TreeChangeTypes.Remove, eventMessages)); + Audit(AuditType.Delete, userId, Constants.System.RecycleBinContent, "Recycle bin emptied"); scope.Complete(); } @@ -2219,7 +2579,7 @@ namespace Umbraco.Cms.Core.Services.Implement public bool RecycleBinSmells() { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.RecycleBinSmells(); @@ -2231,30 +2591,31 @@ namespace Umbraco.Cms.Core.Services.Implement #region Others /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy which is returned. Recursively copies all children. + /// Copies an object by creating a new Content object of the same type and copies all data from + /// the current + /// to the new copy which is returned. Recursively copies all children. /// - /// The to copy + /// The to copy /// Id of the Content's new Parent /// Boolean indicating whether the copy should be related to the original /// Optional Id of the User copying the Content - /// The newly created object - public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = Cms.Core.Constants.Security.SuperUserId) - { - return Copy(content, parentId, relateToOriginal, true, userId); - } + /// The newly created object + public IContent Copy(IContent content, int parentId, bool relateToOriginal, + int userId = Constants.Security.SuperUserId) => Copy(content, parentId, relateToOriginal, true, userId); /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy which is returned. + /// Copies an object by creating a new Content object of the same type and copies all data from + /// the current + /// to the new copy which is returned. /// - /// The to copy + /// The to copy /// Id of the Content's new Parent /// Boolean indicating whether the copy should be related to the original /// A value indicating whether to recursively copy children. /// Optional Id of the User copying the Content - /// The newly created object - public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = Cms.Core.Constants.Security.SuperUserId) + /// The newly created object + public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, + int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -2263,7 +2624,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (IScope scope = ScopeProvider.CreateScope()) { - if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, eventMessages))) + if (scope.Notifications.PublishCancelable( + new ContentCopyingNotification(content, copy, parentId, eventMessages))) { scope.Complete(); return null; @@ -2275,7 +2637,7 @@ namespace Umbraco.Cms.Core.Services.Implement var copies = new List>(); - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); // a copy is not published (but not really unpublishing either) // update the create author and last edit author @@ -2312,7 +2674,8 @@ namespace Umbraco.Cms.Core.Services.Implement var total = long.MaxValue; while (page * pageSize < total) { - IEnumerable descendants = GetPagedDescendants(content.Id, page++, pageSize, out total); + IEnumerable descendants = + GetPagedDescendants(content.Id, page++, pageSize, out total); foreach (IContent descendant in descendants) { // if parent has not been copied, skip, else gets its copy id @@ -2324,7 +2687,9 @@ namespace Umbraco.Cms.Core.Services.Implement IContent descendantCopy = descendant.DeepCloneWithResetIdentities(); descendantCopy.ParentId = parentId; - if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, eventMessages))) + if (scope.Notifications.PublishCancelable( + new ContentCopyingNotification(descendant, descendantCopy, parentId, + eventMessages))) { continue; } @@ -2352,11 +2717,14 @@ namespace Umbraco.Cms.Core.Services.Implement // - tags should be handled by the content repository // - a copy is unpublished and therefore has no impact on tags in DB - scope.Notifications.Publish(new ContentTreeChangeNotification(copy, TreeChangeTypes.RefreshBranch, eventMessages)); + scope.Notifications.Publish( + new ContentTreeChangeNotification(copy, TreeChangeTypes.RefreshBranch, eventMessages)); foreach (Tuple x in copies) { - scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, relateToOriginal, eventMessages)); + scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, + relateToOriginal, eventMessages)); } + Audit(AuditType.Copy, userId, content.Id); scope.Complete(); @@ -2366,16 +2734,17 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Sends an to Publication, which executes handlers and events for the 'Send to Publication' action. + /// Sends an to Publication, which executes handlers and events for the 'Send to Publication' + /// action. /// - /// The to send to publication + /// The to send to publication /// Optional Id of the User issuing the send to publication /// True if sending publication was successful otherwise false - public bool SendToPublication(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) + public bool SendToPublication(IContent content, int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { var sendingToPublishNotification = new ContentSendingToPublishNotification(content, evtMsgs); if (scope.Notifications.PublishCancelable(sendingToPublishNotification)) @@ -2395,77 +2764,91 @@ namespace Umbraco.Cms.Core.Services.Implement // have always changed if it's been saved in the back office but that's not really fail safe. //Save before raising event - var saveResult = Save(content, userId); + OperationResult saveResult = Save(content, userId); // always complete (but maybe return a failed status) scope.Complete(); if (!saveResult.Success) + { return saveResult.Success; + } - scope.Notifications.Publish(new ContentSentToPublishNotification(content, evtMsgs).WithStateFrom(sendingToPublishNotification)); + scope.Notifications.Publish( + new ContentSentToPublishNotification(content, evtMsgs).WithStateFrom(sendingToPublishNotification)); if (culturesChanging != null) - Audit(AuditType.SendToPublishVariant, userId, content.Id, $"Send To Publish for cultures: {culturesChanging}", culturesChanging); + { + Audit(AuditType.SendToPublishVariant, userId, content.Id, + $"Send To Publish for cultures: {culturesChanging}", culturesChanging); + } else + { Audit(AuditType.SendToPublish, content.WriterId, content.Id); + } return saveResult.Success; } } /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . /// /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. /// /// /// /// Result indicating what action was taken when handling the command. - public OperationResult Sort(IEnumerable items, int userId = Cms.Core.Constants.Security.SuperUserId) + public OperationResult Sort(IEnumerable items, int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - var itemsA = items.ToArray(); - if (itemsA.Length == 0) return new OperationResult(OperationResultType.NoOperation, evtMsgs); - - using (var scope = ScopeProvider.CreateScope()) + IContent[] itemsA = items.ToArray(); + if (itemsA.Length == 0) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + return new OperationResult(OperationResultType.NoOperation, evtMsgs); + } - var ret = Sort(scope, itemsA, userId, evtMsgs); + using (IScope scope = ScopeProvider.CreateScope()) + { + scope.WriteLock(Constants.Locks.ContentTree); + + OperationResult ret = Sort(scope, itemsA, userId, evtMsgs); scope.Complete(); return ret; } } /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items identified by the . + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items identified by the . /// /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. /// /// /// /// Result indicating what action was taken when handling the command. - public OperationResult Sort(IEnumerable ids, int userId = Cms.Core.Constants.Security.SuperUserId) + public OperationResult Sort(IEnumerable ids, int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); var idsA = ids.ToArray(); - if (idsA.Length == 0) return new OperationResult(OperationResultType.NoOperation, evtMsgs); - - using (var scope = ScopeProvider.CreateScope()) + if (idsA.Length == 0) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); - var itemsA = GetByIds(idsA).ToArray(); + return new OperationResult(OperationResultType.NoOperation, evtMsgs); + } - var ret = Sort(scope, itemsA, userId, evtMsgs); + using (IScope scope = ScopeProvider.CreateScope()) + { + scope.WriteLock(Constants.Locks.ContentTree); + IContent[] itemsA = GetByIds(idsA).ToArray(); + + OperationResult ret = Sort(scope, itemsA, userId, evtMsgs); scope.Complete(); return ret; } @@ -2519,10 +2902,13 @@ namespace Umbraco.Cms.Core.Services.Implement } //first saved, then sorted - scope.Notifications.Publish(new ContentSavedNotification(itemsA, eventMessages).WithStateFrom(savingNotification)); - scope.Notifications.Publish(new ContentSortedNotification(itemsA, eventMessages).WithStateFrom(sortingNotification)); + scope.Notifications.Publish( + new ContentSavedNotification(itemsA, eventMessages).WithStateFrom(savingNotification)); + scope.Notifications.Publish( + new ContentSortedNotification(itemsA, eventMessages).WithStateFrom(sortingNotification)); - scope.Notifications.Publish(new ContentTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, eventMessages)); + scope.Notifications.Publish( + new ContentTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, eventMessages)); if (published.Any()) { @@ -2537,15 +2923,19 @@ namespace Umbraco.Cms.Core.Services.Implement { using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); ContentDataIntegrityReport report = _documentRepository.CheckDataIntegrity(options); if (report.FixedIssues.Count > 0) { //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref - var root = new Content("root", -1, new ContentType(_shortStringHelper, -1)) {Id = -1, Key = Guid.Empty}; - scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get())); + var root = new Content("root", -1, new ContentType(_shortStringHelper, -1)) + { + Id = -1, Key = Guid.Empty + }; + scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, + EventMessagesFactory.Get())); } return report; @@ -2557,15 +2947,15 @@ namespace Umbraco.Cms.Core.Services.Implement #region Internal Methods /// - /// Gets a collection of descendants by the first Parent. + /// Gets a collection of descendants by the first Parent. /// - /// item to retrieve Descendants from - /// An Enumerable list of objects + /// item to retrieve Descendants from + /// An Enumerable list of objects internal IEnumerable GetPublishedDescendants(IContent content) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return GetPublishedDescendantsLocked(content).ToArray(); // ToArray important in uow! } } @@ -2573,15 +2963,16 @@ namespace Umbraco.Cms.Core.Services.Implement internal IEnumerable GetPublishedDescendantsLocked(IContent content) { var pathMatch = content.Path + ","; - var query = Query().Where(x => x.Id != content.Id && x.Path.StartsWith(pathMatch) /*&& x.Trashed == false*/); - var contents = _documentRepository.Get(query); + IQuery query = Query() + .Where(x => x.Id != content.Id && x.Path.StartsWith(pathMatch) /*&& x.Trashed == false*/); + IEnumerable contents = _documentRepository.Get(query); // beware! contents contains all published version below content // including those that are not directly published because below an unpublished content // these must be filtered out here var parents = new List { content.Id }; - foreach (var c in contents) + foreach (IContent c in contents) { if (parents.Contains(c.ParentId)) { @@ -2595,20 +2986,22 @@ namespace Umbraco.Cms.Core.Services.Implement #region Private Methods - private void Audit(AuditType type, int userId, int objectId, string message = null, string parameters = null) - { - _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Document), message, parameters)); - } + private void Audit(AuditType type, int userId, int objectId, string message = null, string parameters = null) => + _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Document.GetName(), message, + parameters)); - private bool IsDefaultCulture(IReadOnlyCollection langs, string culture) => langs.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture)); - private bool IsMandatoryCulture(IReadOnlyCollection langs, string culture) => langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture)); + private bool IsDefaultCulture(IReadOnlyCollection langs, string culture) => + langs.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture)); + + private bool IsMandatoryCulture(IReadOnlyCollection langs, string culture) => + langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture)); #endregion #region Publishing Strategies /// - /// Ensures that a document can be published + /// Ensures that a document can be published /// /// /// @@ -2619,40 +3012,54 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// /// - private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing, - IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, IReadOnlyCollection allLangs, IDictionary notificationState) + private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, + IReadOnlyList culturesPublishing, + IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, + IReadOnlyCollection allLangs, IDictionary notificationState) { // raise Publishing notification - if (scope.Notifications.PublishCancelable(new ContentPublishingNotification(content, evtMsgs).WithState(notificationState))) + if (scope.Notifications.PublishCancelable( + new ContentPublishingNotification(content, evtMsgs).WithState(notificationState))) { - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); + _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", + content.Name, content.Id, "publishing was cancelled"); return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); } var variesByCulture = content.ContentType.VariesByCulture(); - var impactsToPublish = culturesPublishing == null + CultureImpact[] impactsToPublish = culturesPublishing == null ? new[] { CultureImpact.Invariant } //if it's null it's invariant - : culturesPublishing.Select(x => CultureImpact.Explicit(x, allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray(); + : culturesPublishing.Select(x => + CultureImpact.Explicit(x, + allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray(); // publish the culture(s) if (!impactsToPublish.All(content.PublishCulture)) + { return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); + } //validate the property values IProperty[] invalidProperties = null; - if (!impactsToPublish.All(x => _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x))) + if (!impactsToPublish.All(x => + _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x))) + { return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) { InvalidProperties = invalidProperties }; + } //Check if mandatory languages fails, if this fails it will mean anything that the published flag on the document will // be changed to Unpublished and any culture currently published will not be visible. if (variesByCulture) { if (culturesPublishing == null) - throw new InvalidOperationException("Internal error, variesByCulture but culturesPublishing is null."); + { + throw new InvalidOperationException( + "Internal error, variesByCulture but culturesPublishing is null."); + } if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) { @@ -2664,26 +3071,32 @@ namespace Umbraco.Cms.Core.Services.Implement // missing mandatory culture = cannot be published - var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode); - var mandatoryMissing = mandatoryCultures.Any(x => !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase)); + IEnumerable mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode); + var mandatoryMissing = mandatoryCultures.Any(x => + !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase)); if (mandatoryMissing) + { return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content); + } if (culturesPublishing.Count == 0 && culturesUnpublishing.Count > 0) + { return new PublishResult(PublishResultType.SuccessUnpublishCulture, evtMsgs, content); + } } // ensure that the document has published values // either because it is 'publishing' or because it already has a published version if (content.PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0) { - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document does not have published values"); + _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", + content.Name, content.Id, "document does not have published values"); return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); } ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(content.Id); //loop over each culture publishing - or string.Empty for invariant - foreach (var culture in culturesPublishing ?? (new[] { string.Empty })) + foreach (var culture in culturesPublishing ?? new[] { string.Empty }) { // ensure that the document status is correct // note: culture will be string.Empty for invariant @@ -2691,20 +3104,45 @@ namespace Umbraco.Cms.Core.Services.Implement { case ContentStatus.Expired: if (!variesByCulture) - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document has expired"); + { + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, + content.Id, "document has expired"); + } else - _logger.LogInformation("Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}", content.Name, content.Id, culture, "document culture has expired"); - return new PublishResult(!variesByCulture ? PublishResultType.FailedPublishHasExpired : PublishResultType.FailedPublishCultureHasExpired, evtMsgs, content); + { + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}", + content.Name, content.Id, culture, "document culture has expired"); + } + + return new PublishResult( + !variesByCulture + ? PublishResultType.FailedPublishHasExpired + : PublishResultType.FailedPublishCultureHasExpired, evtMsgs, content); case ContentStatus.AwaitingRelease: if (!variesByCulture) - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document is awaiting release"); + { + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, + content.Id, "document is awaiting release"); + } else - _logger.LogInformation("Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}", content.Name, content.Id, culture, "document is culture awaiting release"); - return new PublishResult(!variesByCulture ? PublishResultType.FailedPublishAwaitingRelease : PublishResultType.FailedPublishCultureAwaitingRelease, evtMsgs, content); + { + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}", + content.Name, content.Id, culture, "document is culture awaiting release"); + } + + return new PublishResult( + !variesByCulture + ? PublishResultType.FailedPublishAwaitingRelease + : PublishResultType.FailedPublishCultureAwaitingRelease, evtMsgs, content); case ContentStatus.Trashed: - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document is trashed"); + _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", + content.Name, content.Id, "document is trashed"); return new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, content); } } @@ -2714,23 +3152,26 @@ namespace Umbraco.Cms.Core.Services.Implement // check if the content can be path-published // root content can be published // else check ancestors - we know we are not trashed - var pathIsOk = content.ParentId == Cms.Core.Constants.System.Root || IsPathPublished(GetParent(content)); + var pathIsOk = content.ParentId == Constants.System.Root || IsPathPublished(GetParent(content)); if (!pathIsOk) { - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "parent is not published"); + _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", + content.Name, content.Id, "parent is not published"); return new PublishResult(PublishResultType.FailedPublishPathNotPublished, evtMsgs, content); } } //If we are both publishing and unpublishing cultures, then return a mixed status if (variesByCulture && culturesPublishing.Count > 0 && culturesUnpublishing.Count > 0) + { return new PublishResult(PublishResultType.SuccessMixedCulture, evtMsgs, content); + } return new PublishResult(evtMsgs, content); } /// - /// Publishes a document + /// Publishes a document /// /// /// @@ -2738,7 +3179,8 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// /// - /// It is assumed that all publishing checks have passed before calling this method like + /// It is assumed that all publishing checks have passed before calling this method like + /// /// private PublishResult StrategyPublish(IContent content, IReadOnlyCollection culturesPublishing, IReadOnlyCollection culturesUnpublishing, @@ -2751,31 +3193,44 @@ namespace Umbraco.Cms.Core.Services.Implement if (content.ContentType.VariesByCulture()) { if (content.Published && culturesUnpublishing.Count == 0 && culturesPublishing.Count == 0) + { return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); + } if (culturesUnpublishing.Count > 0) - _logger.LogInformation("Document {ContentName} (id={ContentId}) cultures: {Cultures} have been unpublished.", + { + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) cultures: {Cultures} have been unpublished.", content.Name, content.Id, string.Join(",", culturesUnpublishing)); + } if (culturesPublishing.Count > 0) - _logger.LogInformation("Document {ContentName} (id={ContentId}) cultures: {Cultures} have been published.", + { + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) cultures: {Cultures} have been published.", content.Name, content.Id, string.Join(",", culturesPublishing)); + } if (culturesUnpublishing.Count > 0 && culturesPublishing.Count > 0) + { return new PublishResult(PublishResultType.SuccessMixedCulture, evtMsgs, content); + } if (culturesUnpublishing.Count > 0 && culturesPublishing.Count == 0) + { return new PublishResult(PublishResultType.SuccessUnpublishCulture, evtMsgs, content); + } return new PublishResult(PublishResultType.SuccessPublishCulture, evtMsgs, content); } - _logger.LogInformation("Document {ContentName} (id={ContentId}) has been published.", content.Name, content.Id); + _logger.LogInformation("Document {ContentName} (id={ContentId}) has been published.", content.Name, + content.Id); return new PublishResult(evtMsgs, content); } /// - /// Ensures that a document can be unpublished + /// Ensures that a document can be unpublished /// /// /// @@ -2786,7 +3241,9 @@ namespace Umbraco.Cms.Core.Services.Implement // raise Unpublishing notification if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs))) { - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id); + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", + content.Name, content.Id); return new PublishResult(PublishResultType.FailedUnpublishCancelledByEvent, evtMsgs, content); } @@ -2794,18 +3251,25 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - /// Unpublishes a document + /// Unpublishes a document /// /// /// /// /// - /// It is assumed that all unpublishing checks have passed before calling this method like + /// It is assumed that all unpublishing checks have passed before calling this method like + /// /// private PublishResult StrategyUnpublish(IContent content, EventMessages evtMsgs) { var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content); + //TODO: What is this check?? we just created this attempt and of course it is Success?! + if (attempt.Success == false) + { + return attempt; + } + // if the document has any release dates set to before now, // they should be removed so they don't interrupt an unpublish // otherwise it would remain released == published @@ -2814,14 +3278,20 @@ namespace Umbraco.Cms.Core.Services.Implement var pastReleases = contentSchedule.GetPending(ContentScheduleAction.Expire, DateTime.Now); foreach (var p in pastReleases) contentSchedule.Remove(p); + if (pastReleases.Count > 0) - _logger.LogInformation("Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.", content.Name, content.Id); + { + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.", + content.Name, content.Id); + } _documentRepository.PersistContentSchedule(content, contentSchedule); // change state to unpublishing content.PublishedState = PublishedState.Unpublishing; - _logger.LogInformation("Document {ContentName} (id={ContentId}) has been unpublished.", content.Name, content.Id); + _logger.LogInformation("Document {ContentName} (id={ContentId}) has been unpublished.", content.Name, + content.Id); return attempt; } @@ -2830,16 +3300,18 @@ namespace Umbraco.Cms.Core.Services.Implement #region Content Types /// - /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. + /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. /// /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation. - /// Deletes content items of the specified type, and only that type. Does *not* handle content types - /// inheritance and compositions, which need to be managed outside of this method. + /// This needs extra care and attention as its potentially a dangerous and extensive operation. + /// + /// Deletes content items of the specified type, and only that type. Does *not* handle content types + /// inheritance and compositions, which need to be managed outside of this method. + /// /// - /// Id of the + /// Id of the /// Optional Id of the user issuing the delete operation - public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Cms.Core.Constants.Security.SuperUserId) + public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId) { // TODO: This currently this is called from the ContentTypeService but that needs to change, // if we are deleting a content type, we should just delete the data and do this operation slightly differently. @@ -2859,7 +3331,7 @@ namespace Umbraco.Cms.Core.Services.Implement // using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); IQuery query = Query().WhereIn(x => x.ContentTypeId, contentTypeIdsA); IContent[] contents = _documentRepository.Get(query).ToArray(); @@ -2889,7 +3361,7 @@ namespace Umbraco.Cms.Core.Services.Implement foreach (IContent child in children) { // see MoveToRecycleBin - PerformMoveLocked(child, Cms.Core.Constants.System.RecycleBinContent, null, userId, moves, true); + PerformMoveLocked(child, Constants.System.RecycleBinContent, null, userId, moves, true); changes.Add(new TreeChange(content, TreeChangeTypes.RefreshBranch)); } @@ -2906,47 +3378,66 @@ namespace Umbraco.Cms.Core.Services.Implement { scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfos, eventMessages)); } + scope.Notifications.Publish(new ContentTreeChangeNotification(changes, eventMessages)); - Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, $"Delete content of type {string.Join(",", contentTypeIdsA)}"); + Audit(AuditType.Delete, userId, Constants.System.Root, + $"Delete content of type {string.Join(",", contentTypeIdsA)}"); scope.Complete(); } } /// - /// Deletes all content items of specified type. All children of deleted content item is moved to Recycle Bin. + /// Deletes all content items of specified type. All children of deleted content item is moved to Recycle Bin. /// /// This needs extra care and attention as its potentially a dangerous and extensive operation - /// Id of the + /// Id of the /// Optional id of the user deleting the media - public void DeleteOfType(int contentTypeId, int userId = Cms.Core.Constants.Security.SuperUserId) - { + public void DeleteOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) => DeleteOfTypes(new[] { contentTypeId }, userId); - } private IContentType GetContentType(IScope scope, string contentTypeAlias) { - if (contentTypeAlias == null) throw new ArgumentNullException(nameof(contentTypeAlias)); - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); + if (contentTypeAlias == null) + { + throw new ArgumentNullException(nameof(contentTypeAlias)); + } - scope.ReadLock(Cms.Core.Constants.Locks.ContentTypes); + if (string.IsNullOrWhiteSpace(contentTypeAlias)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(contentTypeAlias)); + } - var query = Query().Where(x => x.Alias == contentTypeAlias); - var contentType = _contentTypeRepository.Get(query).FirstOrDefault(); + scope.ReadLock(Constants.Locks.ContentTypes); + + IQuery query = Query().Where(x => x.Alias == contentTypeAlias); + IContentType contentType = _contentTypeRepository.Get(query).FirstOrDefault(); if (contentType == null) - throw new Exception($"No ContentType matching the passed in Alias: '{contentTypeAlias}' was found"); // causes rollback + { + throw new Exception( + $"No ContentType matching the passed in Alias: '{contentTypeAlias}' was found"); // causes rollback + } return contentType; } private IContentType GetContentType(string contentTypeAlias) { - if (contentTypeAlias == null) throw new ArgumentNullException(nameof(contentTypeAlias)); - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); + if (contentTypeAlias == null) + { + throw new ArgumentNullException(nameof(contentTypeAlias)); + } - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + if (string.IsNullOrWhiteSpace(contentTypeAlias)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(contentTypeAlias)); + } + + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { return GetContentType(scope, contentTypeAlias); } @@ -2958,51 +3449,61 @@ namespace Umbraco.Cms.Core.Services.Implement public IContent GetBlueprintById(int id) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); - var blueprint = _documentBlueprintRepository.Get(id); + scope.ReadLock(Constants.Locks.ContentTree); + IContent blueprint = _documentBlueprintRepository.Get(id); if (blueprint != null) + { blueprint.Blueprint = true; + } + return blueprint; } } public IContent GetBlueprintById(Guid id) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); - var blueprint = _documentBlueprintRepository.Get(id); + scope.ReadLock(Constants.Locks.ContentTree); + IContent blueprint = _documentBlueprintRepository.Get(id); if (blueprint != null) + { blueprint.Blueprint = true; + } + return blueprint; } } - public void SaveBlueprint(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) + public void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); //always ensure the blueprint is at the root if (content.ParentId != -1) + { content.ParentId = -1; + } content.Blueprint = true; - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); if (content.HasIdentity == false) { content.CreatorId = userId; } + content.WriterId = userId; _documentBlueprintRepository.Save(content); - Audit(AuditType.Save, Cms.Core.Constants.Security.SuperUserId, content.Id, $"Saved content template: {content.Name}"); + Audit(AuditType.Save, Constants.Security.SuperUserId, content.Id, + $"Saved content template: {content.Name}"); scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs)); @@ -3010,13 +3511,13 @@ namespace Umbraco.Cms.Core.Services.Implement } } - public void DeleteBlueprint(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) + public void DeleteBlueprint(IContent content, int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); _documentBlueprintRepository.Delete(content); scope.Notifications.Publish(new ContentDeletedBlueprintNotification(content, evtMsgs)); scope.Complete(); @@ -3025,11 +3526,15 @@ namespace Umbraco.Cms.Core.Services.Implement private static readonly string[] ArrayOfOneNullString = { null }; - public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Cms.Core.Constants.Security.SuperUserId) + public IContent CreateContentFromBlueprint(IContent blueprint, string name, + int userId = Constants.Security.SuperUserId) { - if (blueprint == null) throw new ArgumentNullException(nameof(blueprint)); + if (blueprint == null) + { + throw new ArgumentNullException(nameof(blueprint)); + } - var contentType = GetContentType(blueprint.ContentType.Alias); + IContentType contentType = GetContentType(blueprint.ContentType.Alias); var content = new Content(name, -1, contentType); content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id); @@ -3040,9 +3545,10 @@ namespace Umbraco.Cms.Core.Services.Implement if (blueprint.CultureInfos.Count > 0) { cultures = blueprint.CultureInfos.Values.Select(x => x.Culture); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - if (blueprint.CultureInfos.TryGetValue(_languageRepository.GetDefaultIsoCode(), out var defaultCulture)) + if (blueprint.CultureInfos.TryGetValue(_languageRepository.GetDefaultIsoCode(), + out ContentCultureInfos defaultCulture)) { defaultCulture.Name = name; } @@ -3051,10 +3557,10 @@ namespace Umbraco.Cms.Core.Services.Implement } } - var now = DateTime.Now; + DateTime now = DateTime.Now; foreach (var culture in cultures) { - foreach (var property in blueprint.Properties) + foreach (IProperty property in blueprint.Properties) { var propertyCulture = property.PropertyType.VariesByCulture() ? culture : null; content.SetValue(property.Alias, property.GetValue(propertyCulture), propertyCulture); @@ -3073,11 +3579,12 @@ namespace Umbraco.Cms.Core.Services.Implement { using (ScopeProvider.CreateScope(autoComplete: true)) { - var query = Query(); + IQuery query = Query(); if (contentTypeId.Length > 0) { query.Where(x => contentTypeId.Contains(x.ContentTypeId)); } + return _documentBlueprintRepository.Get(query).Select(x => { x.Blueprint = true; @@ -3086,26 +3593,29 @@ namespace Umbraco.Cms.Core.Services.Implement } } - public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = Cms.Core.Constants.Security.SuperUserId) + public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, + int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); + scope.WriteLock(Constants.Locks.ContentTree); var contentTypeIdsA = contentTypeIds.ToArray(); - var query = Query(); + IQuery query = Query(); if (contentTypeIdsA.Length > 0) + { query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId)); + } - var blueprints = _documentBlueprintRepository.Get(query).Select(x => + IContent[] blueprints = _documentBlueprintRepository.Get(query).Select(x => { x.Blueprint = true; return x; }).ToArray(); - foreach (var blueprint in blueprints) + foreach (IContent blueprint in blueprints) { _documentBlueprintRepository.Delete(blueprint); } @@ -3115,69 +3625,8 @@ namespace Umbraco.Cms.Core.Services.Implement } } - public void DeleteBlueprintsOfType(int contentTypeId, int userId = Cms.Core.Constants.Security.SuperUserId) - { + public void DeleteBlueprintsOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) => DeleteBlueprintsOfTypes(new[] { contentTypeId }, userId); - } - - #endregion - - #region Rollback - - public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Cms.Core.Constants.Security.SuperUserId) - { - var evtMsgs = EventMessagesFactory.Get(); - - //Get the current copy of the node - var content = GetById(id); - - //Get the version - var version = GetVersion(versionId); - - //Good ole null checks - if (content == null || version == null || content.Trashed) - { - return new OperationResult(OperationResultType.FailedCannot, evtMsgs); - } - - //Store the result of doing the save of content for the rollback - OperationResult rollbackSaveResult; - - using (var scope = ScopeProvider.CreateScope()) - { - var rollingBackNotification = new ContentRollingBackNotification(content, evtMsgs); - if (scope.Notifications.PublishCancelable(rollingBackNotification)) - { - scope.Complete(); - return OperationResult.Cancel(evtMsgs); - } - - //Copy the changes from the version - content.CopyFrom(version, culture); - - //Save the content for the rollback - rollbackSaveResult = Save(content, userId); - - //Depending on the save result - is what we log & audit along with what we return - if (rollbackSaveResult.Success == false) - { - //Log the error/warning - _logger.LogError("User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId); - } - else - { - scope.Notifications.Publish(new ContentRolledBackNotification(content, evtMsgs).WithStateFrom(rollingBackNotification)); - - //Logging & Audit message - _logger.LogInformation("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId); - Audit(AuditType.RollBack, userId, id, $"Content '{content.Name}' was rolled back to version '{versionId}'"); - } - - scope.Complete(); - } - - return rollbackSaveResult; - } #endregion } diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 9dca6ca650..f72738b221 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -134,6 +134,9 @@ namespace Umbraco.Cms.Core.Services.Implement stack.Push(c); } + var duplicatePropertyTypeAliases = new List(); + var invalidPropertyGroupAliases = new List(); + foreach (var dependency in dependencies) { if (dependency.Id == compositionContentType.Id) @@ -143,13 +146,14 @@ namespace Umbraco.Cms.Core.Services.Implement if (contentTypeDependency == null) continue; - var duplicatePropertyTypeAliases = contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase).ToArray(); - var invalidPropertyGroupAliases = contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias).ToArray(); + duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase)); + invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias)); + } - if (duplicatePropertyTypeAliases.Length == 0 && invalidPropertyGroupAliases.Length == 0) - continue; + if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0) - throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases, invalidPropertyGroupAliases); + { + throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray()); } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentVersionService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentVersionService.cs new file mode 100644 index 0000000000..e409f3af47 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentVersionService.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Extensions; + +// ReSharper disable once CheckNamespace +namespace Umbraco.Cms.Core.Services.Implement +{ + internal class ContentVersionService : IContentVersionService + { + private readonly ILogger _logger; + private readonly IDocumentVersionRepository _documentVersionRepository; + private readonly IContentVersionCleanupPolicy _contentVersionCleanupPolicy; + private readonly IScopeProvider _scopeProvider; + private readonly IEventMessagesFactory _eventMessagesFactory; + private readonly IAuditRepository _auditRepository; + private readonly ILanguageRepository _languageRepository; + + public ContentVersionService( + ILogger logger, + IDocumentVersionRepository documentVersionRepository, + IContentVersionCleanupPolicy contentVersionCleanupPolicy, + IScopeProvider scopeProvider, + IEventMessagesFactory eventMessagesFactory, + IAuditRepository auditRepository, + ILanguageRepository languageRepository) + { + _logger = logger; + _documentVersionRepository = documentVersionRepository; + _contentVersionCleanupPolicy = contentVersionCleanupPolicy; + _scopeProvider = scopeProvider; + _eventMessagesFactory = eventMessagesFactory; + _auditRepository = auditRepository; + _languageRepository = languageRepository; + } + + /// + public IReadOnlyCollection PerformContentVersionCleanup(DateTime asAtDate) + { + // Media - ignored + // Members - ignored + return CleanupDocumentVersions(asAtDate); + } + + private IReadOnlyCollection CleanupDocumentVersions(DateTime asAtDate) + { + List versionsToDelete; + + /* Why so many scopes? + * + * We could just work out the set to delete at SQL infra level which was the original plan, however we agreed that really we should fire + * ContentService.DeletingVersions so people can hook & cancel if required. + * + * On first time run of cleanup on a site with a lot of history there may be a lot of historic ContentVersions to remove e.g. 200K for our.umbraco.com. + * If we weren't supporting SQL CE we could do TVP, or use temp tables to bulk delete with joins to our list of version ids to nuke. + * (much nicer, we can kill 100k in sub second time-frames). + * + * However we are supporting SQL CE, so the easiest thing to do is use the Umbraco InGroupsOf helper to create a query with 2K args of version + * ids to delete at a time. + * + * This is already done at the repository level, however if we only had a single scope at service level we're still locking + * the ContentVersions table (and other related tables) for a couple of minutes which makes the back office unusable. + * + * As a quick fix, we can also use InGroupsOf at service level, create a scope per group to give other connections a chance + * to grab the locks and execute their queries. + * + * This makes the back office a tiny bit sluggish during first run but it is usable for loading tree and publishing content. + * + * There are optimizations we can do, we could add a bulk delete for SqlServerSyntaxProvider which differs in implementation + * and fallback to this naive approach only for SQL CE, however we agreed it is not worth the effort as this is a one time pain, + * subsequent runs shouldn't have huge numbers of versions to cleanup. + * + * tl;dr lots of scopes to enable other connections to use the DB whilst we work. + */ + using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) + { + IReadOnlyCollection allHistoricVersions = _documentVersionRepository.GetDocumentVersionsEligibleForCleanup(); + + _logger.LogDebug("Discovered {count} candidate(s) for ContentVersion cleanup", allHistoricVersions.Count); + versionsToDelete = new List(allHistoricVersions.Count); + + IEnumerable filteredContentVersions = _contentVersionCleanupPolicy.Apply(asAtDate, allHistoricVersions); + + foreach (ContentVersionMeta version in filteredContentVersions) + { + EventMessages messages = _eventMessagesFactory.Get(); + + if (scope.Notifications.PublishCancelable(new ContentDeletingVersionsNotification(version.ContentId, messages, version.VersionId))) + { + _logger.LogDebug("Delete cancelled for ContentVersion [{versionId}]", version.VersionId); + continue; + } + + versionsToDelete.Add(version); + } + } + + if (!versionsToDelete.Any()) + { + _logger.LogDebug("No remaining ContentVersions for cleanup"); + return Array.Empty(); + } + + _logger.LogDebug("Removing {count} ContentVersion(s)", versionsToDelete.Count); + + foreach (IEnumerable group in versionsToDelete.InGroupsOf(Constants.Sql.MaxParameterCount)) + { + using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) + { + scope.WriteLock(Constants.Locks.ContentTree); + var groupEnumerated = group.ToList(); + _documentVersionRepository.DeleteVersions(groupEnumerated.Select(x => x.VersionId)); + + foreach (ContentVersionMeta version in groupEnumerated) + { + EventMessages messages = _eventMessagesFactory.Get(); + + scope.Notifications.Publish(new ContentDeletedVersionsNotification(version.ContentId, messages, version.VersionId)); + } + } + } + + using (_scopeProvider.CreateScope(autoComplete: true)) + { + Audit(AuditType.Delete, Constants.Security.SuperUserId, -1, $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy"); + } + + return versionsToDelete; + } + + /// + public IEnumerable GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string culture = null) + { + if (pageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex)); + } + + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize)); + } + + using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) + { + var languageId = _languageRepository.GetIdByIsoCode(culture, throwOnNotFound: true); + scope.ReadLock(Constants.Locks.ContentTree); + return _documentVersionRepository.GetPagedItemsByContentId(contentId, pageIndex, pageSize, out totalRecords, languageId); + } + } + + /// + public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1) + { + using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) + { + scope.WriteLock(Constants.Locks.ContentTree); + _documentVersionRepository.SetPreventCleanup(versionId, preventCleanup); + + ContentVersionMeta version = _documentVersionRepository.Get(versionId); + + AuditType auditType = preventCleanup + ? AuditType.ContentVersionPreventCleanup + : AuditType.ContentVersionEnableCleanup; + + var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'"; + + Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}"); + } + } + + private void Audit(AuditType type, int userId, int objectId, string message = null, string parameters = null) + { + var entry = new AuditItem( + objectId, + type, + userId, + UmbracoObjectTypes.Document.GetName(), + message, + parameters); + + _auditRepository.Save(entry); + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Implement/DefaultContentVersionCleanupPolicy.cs b/src/Umbraco.Infrastructure/Services/Implement/DefaultContentVersionCleanupPolicy.cs new file mode 100644 index 0000000000..30193396ee --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Implement/DefaultContentVersionCleanupPolicy.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using ContentVersionCleanupPolicySettings = Umbraco.Cms.Core.Models.ContentVersionCleanupPolicySettings; + +namespace Umbraco.Cms.Infrastructure.Services.Implement +{ + public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy + { + private readonly IOptions _contentSettings; + private readonly IScopeProvider _scopeProvider; + private readonly IDocumentVersionRepository _documentVersionRepository; + + public DefaultContentVersionCleanupPolicy(IOptions contentSettings, IScopeProvider scopeProvider, IDocumentVersionRepository documentVersionRepository) + { + _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); + _scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); + _documentVersionRepository = documentVersionRepository ?? throw new ArgumentNullException(nameof(documentVersionRepository)); + } + + public IEnumerable Apply(DateTime asAtDate, IEnumerable items) + { + // Note: Not checking global enable flag, that's handled in the scheduled job. + // If this method is called and policy is globally disabled someone has chosen to run in code. + + var globalPolicy = _contentSettings.Value.ContentVersionCleanupPolicy; + + var theRest = new List(); + + using(_scopeProvider.CreateScope(autoComplete: true)) + { + var policyOverrides = _documentVersionRepository.GetCleanupPolicies() + .ToDictionary(x => x.ContentTypeId); + + foreach (var version in items) + { + var age = asAtDate - version.VersionDate; + + var overrides = GetOverridePolicy(version, policyOverrides); + + var keepAll = overrides?.KeepAllVersionsNewerThanDays ?? globalPolicy.KeepAllVersionsNewerThanDays!; + var keepLatest = overrides?.KeepLatestVersionPerDayForDays ?? globalPolicy.KeepLatestVersionPerDayForDays; + var preventCleanup = overrides?.PreventCleanup ?? false; + + if (preventCleanup) + { + continue; + } + + if (age.TotalDays <= keepAll) + { + continue; + } + + if (age.TotalDays > keepLatest) + { + + yield return version; + continue; + } + + theRest.Add(version); + } + + var grouped = theRest.GroupBy(x => new + { + x.ContentId, + x.VersionDate.Date + }); + + foreach (var group in grouped) + { + foreach (var version in group.OrderByDescending(x => x.VersionId).Skip(1)) + { + yield return version; + } + } + } + } + + private ContentVersionCleanupPolicySettings GetOverridePolicy( + ContentVersionMeta version, + IDictionary overrides) + { + _ = overrides.TryGetValue(version.ContentTypeId, out var value); + + return value; + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs b/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs index 68fa4c37a5..d09abb02b0 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs @@ -8,63 +8,85 @@ using Microsoft.Extensions.Logging; namespace Umbraco.Cms.Core.Services.Implement { + /// public class LocalizedTextService : ILocalizedTextService { private readonly ILogger _logger; private readonly Lazy _fileSources; - private IDictionary>> _dictionarySource => _dictionarySourceLazy.Value; - private IDictionary> _noAreaDictionarySource => _noAreaDictionarySourceLazy.Value; - private readonly Lazy>>> _dictionarySourceLazy; - private readonly Lazy>> _noAreaDictionarySourceLazy; - private readonly char[] _splitter = new[] { '/' }; + + private IDictionary>>> _dictionarySource => + _dictionarySourceLazy.Value; + + private IDictionary>> _noAreaDictionarySource => + _noAreaDictionarySourceLazy.Value; + + private readonly Lazy>>>> + _dictionarySourceLazy; + + private readonly Lazy>>> _noAreaDictionarySourceLazy; + /// /// Initializes with a file sources instance /// /// /// - public LocalizedTextService(Lazy fileSources, ILogger logger) + public LocalizedTextService(Lazy fileSources, + ILogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger; if (fileSources == null) throw new ArgumentNullException(nameof(fileSources)); - _dictionarySourceLazy = new Lazy>>>(() => FileSourcesToAreaDictionarySources(fileSources.Value)); - _noAreaDictionarySourceLazy = new Lazy>>(() => FileSourcesToNoAreaDictionarySources(fileSources.Value)); + _dictionarySourceLazy = + new Lazy>>>>(() => + FileSourcesToAreaDictionarySources(fileSources.Value)); + _noAreaDictionarySourceLazy = + new Lazy>>>(() => + FileSourcesToNoAreaDictionarySources(fileSources.Value)); _fileSources = fileSources; } - private IDictionary> FileSourcesToNoAreaDictionarySources(LocalizedTextServiceFileSources fileSources) + private IDictionary>> FileSourcesToNoAreaDictionarySources( + LocalizedTextServiceFileSources fileSources) { var xmlSources = fileSources.GetXmlSources(); return XmlSourceToNoAreaDictionary(xmlSources); } - private IDictionary> XmlSourceToNoAreaDictionary(IDictionary> xmlSources) + private IDictionary>> XmlSourceToNoAreaDictionary( + IDictionary> xmlSources) { - var cultureNoAreaDictionary = new Dictionary>(); + var cultureNoAreaDictionary = new Dictionary>>(); foreach (var xmlSource in xmlSources) { - var noAreaAliasValue = GetNoAreaStoredTranslations(xmlSources, xmlSource.Key); + var noAreaAliasValue = + new Lazy>(() => GetNoAreaStoredTranslations(xmlSources, xmlSource.Key)); cultureNoAreaDictionary.Add(xmlSource.Key, noAreaAliasValue); } + return cultureNoAreaDictionary; } - private IDictionary>> FileSourcesToAreaDictionarySources(LocalizedTextServiceFileSources fileSources) + private IDictionary>>> + FileSourcesToAreaDictionarySources(LocalizedTextServiceFileSources fileSources) { var xmlSources = fileSources.GetXmlSources(); return XmlSourcesToAreaDictionary(xmlSources); } - private IDictionary>> XmlSourcesToAreaDictionary(IDictionary> xmlSources) + private IDictionary>>> + XmlSourcesToAreaDictionary(IDictionary> xmlSources) { - var cultureDictionary = new Dictionary>>(); + var cultureDictionary = + new Dictionary>>>(); foreach (var xmlSource in xmlSources) { - var areaAliaValue = GetAreaStoredTranslations(xmlSources, xmlSource.Key); + var areaAliaValue = + new Lazy>>(() => + GetAreaStoredTranslations(xmlSources, xmlSource.Key)); cultureDictionary.Add(xmlSource.Key, areaAliaValue); - } + return cultureDictionary; } @@ -73,45 +95,69 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// /// - public LocalizedTextService(IDictionary> source, ILogger logger) + public LocalizedTextService(IDictionary> source, + ILogger logger) { if (source == null) throw new ArgumentNullException(nameof(source)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _dictionarySourceLazy = new Lazy>>>(() => XmlSourcesToAreaDictionary(source)); - _noAreaDictionarySourceLazy = new Lazy>>(() => XmlSourceToNoAreaDictionary(source)); - + _dictionarySourceLazy = + new Lazy>>>>(() => + XmlSourcesToAreaDictionary(source)); + _noAreaDictionarySourceLazy = + new Lazy>>>(() => + XmlSourceToNoAreaDictionary(source)); } + [Obsolete("Use other ctor with IDictionary>>> as input parameter.")] + public LocalizedTextService(IDictionary>> source, + ILogger logger) : this(source.ToDictionary(x=>x.Key, x=> new Lazy>>(() => x.Value)), logger) + { + } /// /// Initializes with a source of a dictionary of culture -> areas -> sub dictionary of keys/values /// /// /// - public LocalizedTextService(IDictionary>> source, ILogger logger) + public LocalizedTextService( + IDictionary>>> source, + ILogger logger) { var dictionarySource = source ?? throw new ArgumentNullException(nameof(source)); - _dictionarySourceLazy = new Lazy>>>(() => dictionarySource); + _dictionarySourceLazy = + new Lazy>>>>(() => + dictionarySource); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - var cultureNoAreaDictionary = new Dictionary>(); + var cultureNoAreaDictionary = new Dictionary>>(); foreach (var cultureDictionary in dictionarySource) { var areaAliaValue = GetAreaStoredTranslations(source, cultureDictionary.Key); - var aliasValue = new Dictionary(); - foreach (var area in areaAliaValue) + + cultureNoAreaDictionary.Add(cultureDictionary.Key, + new Lazy>(() => GetAliasValues(areaAliaValue))); + } + + _noAreaDictionarySourceLazy = + new Lazy>>>(() => cultureNoAreaDictionary); + } + + private static Dictionary GetAliasValues( + Dictionary> areaAliaValue) + { + var aliasValue = new Dictionary(); + foreach (var area in areaAliaValue) + { + foreach (var alias in area.Value) { - foreach (var alias in area.Value) + if (!aliasValue.ContainsKey(alias.Key)) { - if (!aliasValue.ContainsKey(alias.Key)) - { - aliasValue.Add(alias.Key, alias.Value); - } + aliasValue.Add(alias.Key, alias.Value); } } - cultureNoAreaDictionary.Add(cultureDictionary.Key, aliasValue); } - _noAreaDictionarySourceLazy = new Lazy>>(() => cultureNoAreaDictionary); + + return aliasValue; } public string Localize(string key, CultureInfo culture, IDictionary tokens = null) @@ -122,12 +168,14 @@ namespace Umbraco.Cms.Core.Services.Implement if (string.IsNullOrEmpty(key)) return string.Empty; - var keyParts = key.Split(_splitter, StringSplitOptions.RemoveEmptyEntries); + var keyParts = key.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries); var area = keyParts.Length > 1 ? keyParts[0] : null; var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0]; return Localize(area, alias, culture, tokens); } - public string Localize(string area, string alias, CultureInfo culture, IDictionary tokens = null) + + public string Localize(string area, string alias, CultureInfo culture, + IDictionary tokens = null) { if (culture == null) throw new ArgumentNullException(nameof(culture)); @@ -153,12 +201,15 @@ namespace Umbraco.Cms.Core.Services.Implement if (_dictionarySource.ContainsKey(culture) == false) { - _logger.LogWarning("The culture specified {Culture} was not found in any configured sources for this service", culture); + _logger.LogWarning( + "The culture specified {Culture} was not found in any configured sources for this service", + culture); return new Dictionary(0); } + IDictionary result = new Dictionary(); //convert all areas + keys to a single key with a '/' - foreach (var area in _dictionarySource[culture]) + foreach (var area in _dictionarySource[culture].Value) { foreach (var key in area.Value) { @@ -170,10 +221,12 @@ namespace Umbraco.Cms.Core.Services.Implement } } } + return result; } - private Dictionary> GetAreaStoredTranslations(IDictionary> xmlSource, CultureInfo cult) + private IDictionary> GetAreaStoredTranslations( + IDictionary> xmlSource, CultureInfo cult) { var overallResult = new Dictionary>(StringComparer.InvariantCulture); var areas = xmlSource[cult].Value.XPathSelectElements("//area"); @@ -189,6 +242,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (result.ContainsKey(dictionaryKey) == false) result.Add(dictionaryKey, key.Value); } + overallResult.Add(area.Attribute("alias").Value, result); } @@ -199,11 +253,13 @@ namespace Umbraco.Cms.Core.Services.Implement var enUS = xmlSource[englishCulture].Value.XPathSelectElements("//area"); foreach (var area in enUS) { - IDictionary result = new Dictionary(StringComparer.InvariantCulture); + IDictionary + result = new Dictionary(StringComparer.InvariantCulture); if (overallResult.ContainsKey(area.Attribute("alias").Value)) { result = overallResult[area.Attribute("alias").Value]; } + var keys = area.XPathSelectElements("./key"); foreach (var key in keys) { @@ -213,15 +269,19 @@ namespace Umbraco.Cms.Core.Services.Implement if (result.ContainsKey(dictionaryKey) == false) result.Add(dictionaryKey, key.Value); } + if (!overallResult.ContainsKey(area.Attribute("alias").Value)) { overallResult.Add(area.Attribute("alias").Value, result); } } } + return overallResult; } - private Dictionary GetNoAreaStoredTranslations(IDictionary> xmlSource, CultureInfo cult) + + private Dictionary GetNoAreaStoredTranslations( + IDictionary> xmlSource, CultureInfo cult) { var result = new Dictionary(StringComparer.InvariantCulture); var keys = xmlSource[cult].Value.XPathSelectElements("//key"); @@ -250,14 +310,18 @@ namespace Umbraco.Cms.Core.Services.Implement result.Add(dictionaryKey, key.Value); } } + return result; } - private Dictionary> GetAreaStoredTranslations(IDictionary>> dictionarySource, CultureInfo cult) + + private Dictionary> GetAreaStoredTranslations( + IDictionary>>> dictionarySource, + CultureInfo cult) { var overallResult = new Dictionary>(StringComparer.InvariantCulture); var areaDict = dictionarySource[cult]; - foreach (var area in areaDict) + foreach (var area in areaDict.Value) { var result = new Dictionary(StringComparer.InvariantCulture); var keys = area.Value.Keys; @@ -267,8 +331,10 @@ namespace Umbraco.Cms.Core.Services.Implement if (result.ContainsKey(key) == false) result.Add(key, area.Value[key]); } + overallResult.Add(area.Key, result); } + return overallResult; } @@ -306,11 +372,14 @@ namespace Umbraco.Cms.Core.Services.Implement return attempt ? attempt.Result : currentCulture; } - private string GetFromDictionarySource(CultureInfo culture, string area, string key, IDictionary tokens) + private string GetFromDictionarySource(CultureInfo culture, string area, string key, + IDictionary tokens) { if (_dictionarySource.ContainsKey(culture) == false) { - _logger.LogWarning("The culture specified {Culture} was not found in any configured sources for this service", culture); + _logger.LogWarning( + "The culture specified {Culture} was not found in any configured sources for this service", + culture); return "[" + key + "]"; } @@ -318,17 +387,18 @@ namespace Umbraco.Cms.Core.Services.Implement string found = null; if (string.IsNullOrWhiteSpace(area)) { - _noAreaDictionarySource[culture].TryGetValue(key, out found); + _noAreaDictionarySource[culture].Value.TryGetValue(key, out found); } else { - if (_dictionarySource[culture].TryGetValue(area, out var areaDictionary)) + if (_dictionarySource[culture].Value.TryGetValue(area, out var areaDictionary)) { areaDictionary.TryGetValue(key, out found); } + if (found == null) { - _noAreaDictionarySource[culture].TryGetValue(key, out found); + _noAreaDictionarySource[culture].Value.TryGetValue(key, out found); } } @@ -341,6 +411,7 @@ namespace Umbraco.Cms.Core.Services.Implement //NOTE: Based on how legacy works, the default text does not contain the area, just the key return "[" + key + "]"; } + /// /// Parses the tokens in the value /// @@ -370,20 +441,29 @@ namespace Umbraco.Cms.Core.Services.Implement return value; } + /// + /// Returns all key/values in storage for the given culture + /// + /// public IDictionary> GetAllStoredValuesByAreaAndAlias(CultureInfo culture) { - if (culture == null) throw new ArgumentNullException("culture"); + if (culture == null) + { + throw new ArgumentNullException("culture"); + } // TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode culture = ConvertToSupportedCultureWithRegionCode(culture); if (_dictionarySource.ContainsKey(culture) == false) { - _logger.LogWarning("The culture specified {Culture} was not found in any configured sources for this service", culture); + _logger.LogWarning( + "The culture specified {Culture} was not found in any configured sources for this service", + culture); return new Dictionary>(0); } - return _dictionarySource[culture]; + return _dictionarySource[culture].Value; } } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs index b9fceeffec..c64a91a598 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs @@ -713,7 +713,7 @@ namespace Umbraco.Cms.Core.Services.Implement _userGroupRepository.ReplaceGroupPermissions(groupId, permissions, entityIds); scope.Complete(); - var assigned = permissions.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray(); + var assigned = permissions?.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray(); var entityPermissions = entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); scope.Notifications.Publish(new AssignedUserGroupPermissionsNotification(entityPermissions, evtMsgs)); } diff --git a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs index 875e6d2ffc..455c31d00f 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs @@ -13,19 +13,24 @@ using Umbraco.Cms.Core.Xml; using Umbraco.Cms.Core.Xml.XPath; using Umbraco.Cms.Infrastructure.PublishedCache.Navigable; using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Infrastructure.PublishedCache { public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigableData, IDisposable { + private readonly IDomainCache _domainCache; + private readonly IAppCache _elementsCache; + private readonly GlobalSettings _globalSettings; private readonly ContentStore.Snapshot _snapshot; private readonly IAppCache _snapshotCache; - private readonly IAppCache _elementsCache; - private readonly IDomainCache _domainCache; - private readonly GlobalSettings _globalSettings; private readonly IVariationContextAccessor _variationContextAccessor; + #region IDisposable + + public void Dispose() => _snapshot.Dispose(); + + #endregion + #region Constructor // TODO: figure this out @@ -33,7 +38,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // it's too late for UmbracoContext which has captured previewDefault and stuff into these ctor vars // but, no, UmbracoContext returns snapshot.Content which comes from elements SO a resync should create a new cache - public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache, IAppCache elementsCache, IDomainCache domainCache, IOptions globalSettings, IVariationContextAccessor variationContextAccessor) + public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache, + IAppCache elementsCache, IDomainCache domainCache, IOptions globalSettings, + IVariationContextAccessor variationContextAccessor) : base(previewDefault) { _snapshot = snapshot; @@ -59,18 +66,23 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // at the moment we try our best to be backward compatible, but really, // should get rid of hideTopLevelNode and other oddities entirely, eventually - public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null) - { - return GetByRoute(PreviewDefault, route, hideTopLevelNode, culture); - } + public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null) => + GetByRoute(PreviewDefault, route, hideTopLevelNode, culture); - public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string culture = null) + public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, + string culture = null) { - if (route == null) throw new ArgumentNullException(nameof(route)); + if (route == null) + { + throw new ArgumentNullException(nameof(route)); + } - var cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing ? _elementsCache : _snapshotCache; + + IAppCache cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing + ? _elementsCache + : _snapshotCache; var key = CacheKeys.ContentCacheContentByRoute(route, preview, culture); - return cache.GetCacheItem(key, () => GetByRouteInternal(preview, route, hideTopLevelNode, culture)); + return cache.GetCacheItem(key, () => GetByRouteInternal(preview, route, hideTopLevelNode, culture)); } private IPublishedContent GetByRouteInternal(bool preview, string route, bool? hideTopLevelNode, string culture) @@ -108,8 +120,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // hideTopLevelNode = support legacy stuff, look for /*/path/to/node // else normal, look for /path/to/node content = hideTopLevelNode.Value - ? GetAtRoot(preview).SelectMany(x => x.Children(_variationContextAccessor, culture)).FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]) - : GetAtRoot(preview).FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]); + ? GetAtRoot(preview).SelectMany(x => x.Children(_variationContextAccessor, culture)) + .FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]) + : GetAtRoot(preview) + .FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]); content = FollowRoute(content, parts, 1, culture); } @@ -118,59 +132,72 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // have to look for /foo (see note in ApplyHideTopLevelNodeFromPath). if (content == null && hideTopLevelNode.Value && parts.Length == 1) { - content = GetAtRoot(preview).FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]); + content = GetAtRoot(preview) + .FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]); } return content; } - public string GetRouteById(int contentId, string culture = null) - { - return GetRouteById(PreviewDefault, contentId, culture); - } + public string GetRouteById(int contentId, string culture = null) => + GetRouteById(PreviewDefault, contentId, culture); public string GetRouteById(bool preview, int contentId, string culture = null) { - var cache = (preview == false || PublishedSnapshotService.FullCacheWhenPreviewing) ? _elementsCache : _snapshotCache; + IAppCache cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing + ? _elementsCache + : _snapshotCache; var key = CacheKeys.ContentCacheRouteByContent(contentId, preview, culture); - return cache.GetCacheItem(key, () => GetRouteByIdInternal(preview, contentId, null, culture)); + return cache.GetCacheItem(key, () => GetRouteByIdInternal(preview, contentId, null, culture)); } private string GetRouteByIdInternal(bool preview, int contentId, bool? hideTopLevelNode, string culture) { - var node = GetById(preview, contentId); + IPublishedContent node = GetById(preview, contentId); if (node == null) + { return null; + } hideTopLevelNode = hideTopLevelNode ?? HideTopLevelNodeFromPath; // default = settings // walk up from that node until we hit a node with a domain, // or we reach the content root, collecting URLs in the way var pathParts = new List(); - var n = node; + IPublishedContent n = node; var urlSegment = n.UrlSegment(_variationContextAccessor, culture); - var hasDomains = _domainCache.HasAssigned(n.Id); + var hasDomains = _domainCache.GetAssignedWithCulture(culture, n.Id); while (hasDomains == false && n != null) // n is null at root { // no segment indicates this is not published when this is a variant - if (urlSegment.IsNullOrWhiteSpace()) return null; + if (urlSegment.IsNullOrWhiteSpace()) + { + return null; + } pathParts.Add(urlSegment); // move to parent node n = n.Parent; if (n != null) + { urlSegment = n.UrlSegment(_variationContextAccessor, culture); + } - hasDomains = n != null && _domainCache.HasAssigned(n.Id); + hasDomains = n != null && _domainCache.GetAssignedWithCulture(culture, n.Id); } // at this point this will be the urlSegment of the root, no segment indicates this is not published when this is a variant - if (urlSegment.IsNullOrWhiteSpace()) return null; + if (urlSegment.IsNullOrWhiteSpace()) + { + return null; + } // no domain, respect HideTopLevelNodeFromPath for legacy purposes if (hasDomains == false && hideTopLevelNode.Value) + { ApplyHideTopLevelNodeFromPath(node, pathParts, preview); + } // assemble the route pathParts.Reverse(); @@ -182,7 +209,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache return route; } - private IPublishedContent FollowRoute(IPublishedContent content, IReadOnlyList parts, int start, string culture) + private IPublishedContent FollowRoute(IPublishedContent content, IReadOnlyList parts, int start, + string culture) { var i = start; while (content != null && i < parts.Count) @@ -194,6 +222,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache return urlSegment == part; }); } + return content; } @@ -209,11 +238,16 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // that's the way it works pre-4.10 and we try to be backward compat for the time being if (content.Parent == null) { - var rootNode = GetByRoute(preview, "/", true); + IPublishedContent rootNode = GetByRoute(preview, "/", true); if (rootNode == null) + { throw new Exception("Failed to get node at /."); + } + if (rootNode.Id == content.Id) // remove only if we're the default node + { segments.RemoveAt(segments.Count - 1); + } } else { @@ -227,13 +261,13 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache public override IPublishedContent GetById(bool preview, int contentId) { - var node = _snapshot.Get(contentId); + ContentNode node = _snapshot.Get(contentId); return GetNodePublishedContent(node, preview); } public override IPublishedContent GetById(bool preview, Guid contentId) { - var node = _snapshot.Get(contentId); + ContentNode node = _snapshot.Get(contentId); return GetNodePublishedContent(node, preview); } @@ -241,18 +275,26 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache { var guidUdi = contentId as GuidUdi; if (guidUdi == null) + { throw new ArgumentException($"Udi must be of type {typeof(GuidUdi).Name}.", nameof(contentId)); + } if (guidUdi.EntityType != Constants.UdiEntityType.Document) - throw new ArgumentException($"Udi entity type must be \"{Constants.UdiEntityType.Document}\".", nameof(contentId)); + { + throw new ArgumentException($"Udi entity type must be \"{Constants.UdiEntityType.Document}\".", + nameof(contentId)); + } return GetById(preview, guidUdi.Guid); } public override bool HasById(bool preview, int contentId) { - var n = _snapshot.Get(contentId); - if (n == null) return false; + ContentNode n = _snapshot.Get(contentId); + if (n == null) + { + return false; + } return preview || n.PublishedModel != null; } @@ -263,7 +305,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache { // handle context culture for variant if (culture == null) + { culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; + } // _snapshot.GetAtRoot() returns all ContentNode at root // both .Draft and .Published cannot be null at the same time @@ -272,13 +316,15 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // GetNodePublishedContent may return null if !preview and there is no // published model, so we need to filter these nulls out - var atRoot = _snapshot.GetAtRoot() + IEnumerable atRoot = _snapshot.GetAtRoot() .Select(n => GetNodePublishedContent(n, preview)) .WhereNotNull(); // if a culture is specified, we must ensure that it is avail/published if (culture != "*") + { atRoot = atRoot.Where(x => x.IsInvariantOrHasCulture(culture)); + } return atRoot; } @@ -286,7 +332,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache private static IPublishedContent GetNodePublishedContent(ContentNode node, bool preview) { if (node == null) + { return null; + } // both .Draft and .Published cannot be null at the same time @@ -299,7 +347,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // this is for published content when previewing private static IPublishedContent GetPublishedContentAsDraft(IPublishedContent content /*, bool preview*/) { - if (content == null /*|| preview == false*/) return null; //content; + if (content == null /*|| preview == false*/) + { + return null; //content; + } // an object in the cache is either an IPublishedContentOrMedia, // or a model inheriting from PublishedContentExtended - in which @@ -309,12 +360,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache return inner.AsDraft(); } - public override bool HasContent(bool preview) - { - return preview + public override bool HasContent(bool preview) => + preview ? _snapshot.IsEmpty == false : _snapshot.GetAtRoot().Any(x => x.PublishedModel != null); - } #endregion @@ -322,21 +371,24 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) { - var navigator = CreateNavigator(preview); - var iterator = navigator.Select(xpath, vars); + XPathNavigator navigator = CreateNavigator(preview); + XPathNodeIterator iterator = navigator.Select(xpath, vars); return GetSingleByXPath(iterator); } public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { - var navigator = CreateNavigator(preview); - var iterator = navigator.Select(xpath, vars); + XPathNavigator navigator = CreateNavigator(preview); + XPathNodeIterator iterator = navigator.Select(xpath, vars); return GetSingleByXPath(iterator); } private static IPublishedContent GetSingleByXPath(XPathNodeIterator iterator) { - if (iterator.MoveNext() == false) return null; + if (iterator.MoveNext() == false) + { + return null; + } var xnav = iterator.Current as NavigableNavigator; var xcontent = xnav?.UnderlyingObject as NavigableContent; @@ -345,15 +397,16 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) { - var navigator = CreateNavigator(preview); - var iterator = navigator.Select(xpath, vars); + XPathNavigator navigator = CreateNavigator(preview); + XPathNodeIterator iterator = navigator.Select(xpath, vars); return GetByXPath(iterator); } - public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) + public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, + XPathVariable[] vars) { - var navigator = CreateNavigator(preview); - var iterator = navigator.Select(xpath, vars); + XPathNavigator navigator = CreateNavigator(preview); + XPathNodeIterator iterator = navigator.Select(xpath, vars); return GetByXPath(iterator); } @@ -364,7 +417,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache { var xnav = iterator.Current as NavigableNavigator; var xcontent = xnav?.UnderlyingObject as NavigableContent; - if (xcontent == null) continue; + if (xcontent == null) + { + continue; + } yield return xcontent.InnerContent; } @@ -395,14 +451,5 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache public override IPublishedContentType GetContentType(Guid key) => _snapshot.GetContentType(key); #endregion - - #region IDisposable - - public void Dispose() - { - _snapshot.Dispose(); - } - - #endregion } } diff --git a/src/Umbraco.PublishedCache.NuCache/ContentNode.cs b/src/Umbraco.PublishedCache.NuCache/ContentNode.cs index 23088df3ad..beb5986861 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentNode.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentNode.cs @@ -175,13 +175,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache public IPublishedContent PublishedModel => GetModel(ref _publishedModel, _publishedData); public ContentNodeKit ToKit() - => new ContentNodeKit - { - Node = this, - ContentTypeId = ContentType.Id, - - DraftData = _draftData, - PublishedData = _publishedData - }; + => new ContentNodeKit(this, ContentType.Id, _draftData, _publishedData); } } diff --git a/src/Umbraco.PublishedCache.NuCache/ContentNodeKit.cs b/src/Umbraco.PublishedCache.NuCache/ContentNodeKit.cs index bb05e14706..3f230925fe 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentNodeKit.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentNodeKit.cs @@ -1,23 +1,39 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using System; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; namespace Umbraco.Cms.Infrastructure.PublishedCache { - // what's needed to actually build a content node public struct ContentNodeKit { + [Obsolete("This will be changed to a property in future versions")] public ContentNode Node; + + [Obsolete("This will be changed to a property in future versions")] public int ContentTypeId; + + [Obsolete("This will be changed to a property in future versions")] public ContentData DraftData; + + [Obsolete("This will be changed to a property in future versions")] public ContentData PublishedData; + public ContentNodeKit(ContentNode node, int contentTypeId, ContentData draftData, ContentData publishedData) + { + Node = node; + ContentTypeId = contentTypeId; + DraftData = draftData; + PublishedData = publishedData; + } + + public bool IsEmpty => Node == null; public bool IsNull => ContentTypeId < 0; public static ContentNodeKit Empty { get; } = new ContentNodeKit(); - public static ContentNodeKit Null { get; } = new ContentNodeKit { ContentTypeId = -1 }; + public static ContentNodeKit Null { get; } = new ContentNodeKit(null, -1, null, null); public void Build( IPublishedContentType contentType, @@ -41,12 +57,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache } public ContentNodeKit Clone(IPublishedModelFactory publishedModelFactory) - => new ContentNodeKit - { - ContentTypeId = ContentTypeId, - DraftData = DraftData, - PublishedData = PublishedData, - Node = new ContentNode(Node, publishedModelFactory) - }; + => new ContentNodeKit(new ContentNode(Node, publishedModelFactory), ContentTypeId, DraftData, PublishedData); + + public ContentNodeKit Clone(IPublishedModelFactory publishedModelFactory, ContentData draftData, ContentData publishedData) + => new ContentNodeKit(new ContentNode(Node, publishedModelFactory), ContentTypeId, draftData, publishedData); } } diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index d83d91c0db..240e6c8861 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -104,7 +104,11 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache private class WriteLockInfo { +#pragma warning disable IDE1006 // Naming Styles + + // This is a field that is used for ref operations public bool Taken; +#pragma warning restore IDE1006 // Naming Styles } // a scope contextual that represents a locked writer to the dictionary @@ -524,7 +528,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache parent = GetParentLink(kit.Node, null); if (parent == null) { - _logger.LogWarning("Skip item id={kit.Node.Id}, could not find parent id={kit.Node.ParentContentId}.", kit.Node.Id, kit.Node.ParentContentId); + _logger.LogWarning("Skip item id={kitNodeId}, could not find parent id={kitNodeParentContentId}.", kit.Node.Id, kit.Node.ParentContentId); return false; } @@ -533,21 +537,21 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // because the data sort operation is by path. if (parent.Value == null) { - _logger.LogWarning("Skip item id={kit.Node.Id}, no Data assigned for linked node with path {kit.Node.Path} and parent id {kit.Node.ParentContentId}. This can indicate data corruption for the Path value for node {kit.Node.Id}. See the Health Check dashboard in Settings to resolve data integrity issues.", kit.Node.Id, kit.Node.ParentContentId); + _logger.LogWarning("Skip item id={kitNodeId}, no Data assigned for linked node with path {kitNodePath} and parent id {kitNodeParentContentId}. This can indicate data corruption for the Path value for node {kitNodeId}. See the Health Check dashboard in Settings to resolve data integrity issues.", kit.Node.Id, kit.Node.Path, kit.Node.ParentContentId, kit.Node.Id); return false; } // make sure the kit is valid if (kit.DraftData == null && kit.PublishedData == null) { - _logger.LogWarning("Skip item id={kit.Node.Id}, both draft and published data are null.", kit.Node.Id); + _logger.LogWarning("Skip item id={kitNodeId}, both draft and published data are null.", kit.Node.Id); return false; } // unknown = bad if (_contentTypesById.TryGetValue(kit.ContentTypeId, out var link) == false || link.Value == null) { - _logger.LogWarning("Skip item id={kit.Node.Id}, could not find content type id={kit.ContentTypeId}.", kit.Node.Id, kit.ContentTypeId); + _logger.LogWarning("Skip item id={kitNodeId}, could not find content type id={kitContentTypeId}.", kit.Node.Id, kit.ContentTypeId); return false; } @@ -723,7 +727,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache previousNode = null; // there is no previous sibling } - _logger.LogDebug("Set {thisNode.Id} with parent {thisNode.ParentContentId}", thisNode.Id, thisNode.ParentContentId); + _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 @@ -780,7 +784,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache ok = false; continue; // skip that one } - _logger.LogDebug("Set {kit.Node.Id} with parent {kit.Node.ParentContentId}", kit.Node.Id, kit.Node.ParentContentId); + _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); diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentDataSerializer.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentDataSerializer.cs index 0f2d9f1db3..261e884fca 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentDataSerializer.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentDataSerializer.cs @@ -13,11 +13,11 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource _dictionaryOfPropertyDataSerializer = dictionaryOfPropertyDataSerializer; if(_dictionaryOfPropertyDataSerializer == null) { - _dictionaryOfPropertyDataSerializer = DefaultPropertiesSerializer; + _dictionaryOfPropertyDataSerializer = s_defaultPropertiesSerializer; } } - private static readonly DictionaryOfPropertyDataSerializer DefaultPropertiesSerializer = new DictionaryOfPropertyDataSerializer(); - private static readonly DictionaryOfCultureVariationSerializer DefaultCultureVariationsSerializer = new DictionaryOfCultureVariationSerializer(); + private static readonly DictionaryOfPropertyDataSerializer s_defaultPropertiesSerializer = new DictionaryOfPropertyDataSerializer(); + private static readonly DictionaryOfCultureVariationSerializer s_defaultCultureVariationsSerializer = new DictionaryOfCultureVariationSerializer(); private readonly IDictionaryOfPropertyDataSerializer _dictionaryOfPropertyDataSerializer; public ContentData ReadFrom(Stream stream) @@ -29,18 +29,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource var versionDate = PrimitiveSerializer.DateTime.ReadFrom(stream); var writerId = PrimitiveSerializer.Int32.ReadFrom(stream); var templateId = PrimitiveSerializer.Int32.ReadFrom(stream); - return new ContentData - { - Published = published, - Name = name, - UrlSegment = urlSegment, - VersionId = versionId, - VersionDate = versionDate, - WriterId = writerId, - TemplateId = templateId == 0 ? (int?)null : templateId, - Properties = _dictionaryOfPropertyDataSerializer.ReadFrom(stream), // TODO: We don't want to allocate empty arrays - CultureInfos = DefaultCultureVariationsSerializer.ReadFrom(stream) // TODO: We don't want to allocate empty arrays - }; + var properties = _dictionaryOfPropertyDataSerializer.ReadFrom(stream); // TODO: We don't want to allocate empty arrays + var cultureInfos = s_defaultCultureVariationsSerializer.ReadFrom(stream); // TODO: We don't want to allocate empty arrays + return new ContentData(name, urlSegment, versionId, versionDate, writerId, templateId, published, properties, cultureInfos); } public void WriteTo(ContentData value, Stream stream) @@ -53,7 +44,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource PrimitiveSerializer.Int32.WriteTo(value.WriterId, stream); PrimitiveSerializer.Int32.WriteTo(value.TemplateId ?? 0, stream); _dictionaryOfPropertyDataSerializer.WriteTo(value.Properties, stream); - DefaultCultureVariationsSerializer.WriteTo(value.CultureInfos, stream); + s_defaultCultureVariationsSerializer.WriteTo(value.CultureInfos, stream); } } } diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentNodeKitSerializer.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentNodeKitSerializer.cs index db0886ce79..42da3e601b 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentNodeKitSerializer.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.ContentNodeKitSerializer.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using CSharpTest.Net.Serialization; namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource @@ -10,19 +10,17 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource _contentDataSerializer = contentDataSerializer; if(_contentDataSerializer == null) { - _contentDataSerializer = DefaultDataSerializer; + _contentDataSerializer = s_defaultDataSerializer; } } - static readonly ContentDataSerializer DefaultDataSerializer = new ContentDataSerializer(); + static readonly ContentDataSerializer s_defaultDataSerializer = new ContentDataSerializer(); private readonly ContentDataSerializer _contentDataSerializer; //static readonly ListOfIntSerializer ChildContentIdsSerializer = new ListOfIntSerializer(); public ContentNodeKit ReadFrom(Stream stream) { - var kit = new ContentNodeKit - { - Node = new ContentNode( + var contentNode = new ContentNode( PrimitiveSerializer.Int32.ReadFrom(stream), // id PrimitiveSerializer.Guid.ReadFrom(stream), // uid PrimitiveSerializer.Int32.ReadFrom(stream), // level @@ -31,15 +29,27 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource PrimitiveSerializer.Int32.ReadFrom(stream), // parent id PrimitiveSerializer.DateTime.ReadFrom(stream), // date created PrimitiveSerializer.Int32.ReadFrom(stream) // creator id - ), - ContentTypeId = PrimitiveSerializer.Int32.ReadFrom(stream) - }; + ); + + int contentTypeId = PrimitiveSerializer.Int32.ReadFrom(stream); var hasDraft = PrimitiveSerializer.Boolean.ReadFrom(stream); + ContentData draftData = null; + ContentData publishedData = null; if (hasDraft) - kit.DraftData = _contentDataSerializer.ReadFrom(stream); + { + draftData = _contentDataSerializer.ReadFrom(stream); + } var hasPublished = PrimitiveSerializer.Boolean.ReadFrom(stream); if (hasPublished) - kit.PublishedData = _contentDataSerializer.ReadFrom(stream); + { + publishedData = _contentDataSerializer.ReadFrom(stream); + } + var kit = new ContentNodeKit( + contentNode, + contentTypeId, + draftData, + publishedData); + return kit; } diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs index a4fd455468..a461cda437 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs @@ -8,19 +8,38 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource ///
public class ContentData { - public string Name { get; set; } - public string UrlSegment { get; set; } - public int VersionId { get; set; } - public DateTime VersionDate { get; set; } - public int WriterId { get; set; } - public int? TemplateId { get; set; } - public bool Published { get; set; } + [Obsolete("Use ctor with all params, as the pros should be immutable")] + public ContentData() + { - public IDictionary Properties { get; set; } + } + + public ContentData(string name, string urlSegment, int versionId, DateTime versionDate, int writerId, int? templateId, bool published, IDictionary properties, IReadOnlyDictionary cultureInfos) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + UrlSegment = urlSegment; + VersionId = versionId; + VersionDate = versionDate; + WriterId = writerId; + TemplateId = templateId; + Published = published; + Properties = properties ?? throw new ArgumentNullException(nameof(properties)); + CultureInfos = cultureInfos; + } + + public string Name { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; } + public string UrlSegment { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; } + public int VersionId { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; } + public DateTime VersionDate { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; } + public int WriterId { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; } + public int? TemplateId { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; } + public bool Published { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; } + + public IDictionary Properties { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; } /// /// The collection of language Id to name for the content item /// - public IReadOnlyDictionary CultureInfos { get; set; } + public IReadOnlyDictionary CultureInfos { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; } } } diff --git a/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs b/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs new file mode 100644 index 0000000000..61f10917fd --- /dev/null +++ b/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs @@ -0,0 +1,15 @@ +using System.Linq; +using Umbraco.Cms.Core.PublishedCache; + +namespace Umbraco.Cms.Infrastructure.PublishedCache +{ + public static class DomainCacheExtensions + { + public static bool GetAssignedWithCulture(this IDomainCache domainCache, string culture, int documentId, bool includeWildcards = false) + { + var assigned = domainCache.GetAssigned(documentId, includeWildcards); + + return culture is null ? assigned.Any() : assigned.Any(x => x.Culture == culture); + } + } +} diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs index 4973e54380..649bc0eebb 100644 --- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs @@ -820,18 +820,16 @@ AND cmsContentNu.nodeId IS NULL bool published = false; var deserializedContent = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published); - d = new ContentData - { - Name = dto.EditName, - Published = published, - TemplateId = dto.EditTemplateId == 0 ? (int?)null : dto.EditTemplateId, - VersionId = dto.VersionId, - VersionDate = dto.EditVersionDate, - WriterId = dto.EditWriterId, - Properties = deserializedContent.PropertyData, // TODO: We don't want to allocate empty arrays - CultureInfos = deserializedContent.CultureData, - UrlSegment = deserializedContent.UrlSegment - }; + d = new ContentData( + dto.EditName, + deserializedContent.UrlSegment, + dto.VersionId, + dto.EditVersionDate, + dto.EditWriterId, + dto.EditTemplateId, + published, + deserializedContent.PropertyData, + deserializedContent.CultureData); } } @@ -851,31 +849,23 @@ AND cmsContentNu.nodeId IS NULL bool published = true; var deserializedContent = serializer.Deserialize(dto, dto.PubData, dto.PubDataRaw, published); - p = new ContentData - { - Name = dto.PubName, - UrlSegment = deserializedContent.UrlSegment, - Published = published, - TemplateId = dto.PubTemplateId == 0 ? (int?)null : dto.PubTemplateId, - VersionId = dto.VersionId, - VersionDate = dto.PubVersionDate, - WriterId = dto.PubWriterId, - Properties = deserializedContent.PropertyData, // TODO: We don't want to allocate empty arrays - CultureInfos = deserializedContent.CultureData - }; + p = new ContentData( + dto.PubName, + deserializedContent.UrlSegment, + dto.VersionId, + dto.PubVersionDate, + dto.PubWriterId, + dto.PubTemplateId, + published, + deserializedContent.PropertyData, + deserializedContent.CultureData); } } var n = new ContentNode(dto.Id, dto.Key, dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId); - var s = new ContentNodeKit - { - Node = n, - ContentTypeId = dto.ContentTypeId, - DraftData = d, - PublishedData = p - }; + var s = new ContentNodeKit(n, dto.ContentTypeId, d, p); return s; } @@ -888,27 +878,21 @@ AND cmsContentNu.nodeId IS NULL bool published = true; var deserializedMedia = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published); - var p = new ContentData - { - Name = dto.EditName, - Published = published, - TemplateId = -1, - VersionId = dto.VersionId, - VersionDate = dto.EditVersionDate, - WriterId = dto.CreatorId, // what-else? - Properties = deserializedMedia.PropertyData, // TODO: We don't want to allocate empty arrays - CultureInfos = deserializedMedia.CultureData - }; + var p = new ContentData( + dto.EditName, + null, + dto.VersionId, + dto.EditVersionDate, + dto.CreatorId, + -1, + published, + deserializedMedia.PropertyData, + deserializedMedia.CultureData); var n = new ContentNode(dto.Id, dto.Key, dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId); - var s = new ContentNodeKit - { - Node = n, - ContentTypeId = dto.ContentTypeId, - PublishedData = p - }; + var s = new ContentNodeKit(n, dto.ContentTypeId, null, p); return s; } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs b/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs index 4796f32295..53cc597cf5 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs @@ -34,15 +34,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache IVariationContextAccessor variationContextAccessor, IPublishedModelFactory publishedModelFactory) { - var d = new ContentData - { - Name = member.Name, - Published = previewing, - TemplateId = -1, - VersionDate = member.UpdateDate, - WriterId = member.CreatorId, // what else? - Properties = GetPropertyValues(contentType, member) - }; + var d = new ContentData(member.Name, null, 0, member.UpdateDate, member.CreatorId, -1, previewing, GetPropertyValues(contentType, member), null); + var n = new ContentNode( member.Id, member.Key, diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 83d2bd5ccb..20df726080 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -46,7 +46,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache private readonly IPublishedModelFactory _publishedModelFactory; private readonly IDefaultCultureAccessor _defaultCultureAccessor; private readonly IHostingEnvironment _hostingEnvironment; - private readonly IContentCacheDataSerializerFactory _contentCacheDataSerializerFactory; private readonly ContentDataSerializer _contentDataSerializer; private readonly NuCacheSettings _config; @@ -93,7 +92,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache IPublishedModelFactory publishedModelFactory, IHostingEnvironment hostingEnvironment, IOptions config, - IContentCacheDataSerializerFactory contentCacheDataSerializerFactory, ContentDataSerializer contentDataSerializer) { _options = options; @@ -111,7 +109,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache _defaultCultureAccessor = defaultCultureAccessor; _globalSettings = globalSettings.Value; _hostingEnvironment = hostingEnvironment; - _contentCacheDataSerializerFactory = contentCacheDataSerializerFactory; _contentDataSerializer = contentDataSerializer; _config = config.Value; _publishedModelFactory = publishedModelFactory; diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index c5ebe0446d..0221181cb3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -517,6 +517,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers // Failed only occurs when the user does not exist errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office."); } + else if (result == ExternalLoginSignInResult.NotAllowed) + { + // This occurs when the external provider has approved the login but custom logic in OnExternalLogin has denined it. + errors.Add($"The user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not been accepted and cannot sign in."); + } else if (result == AutoLinkSignInResult.FailedNotLinked) { errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office."); diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index 0dfea020d8..2e81a0905b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -419,6 +419,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers {"showAllowSegmentationForDocumentTypes", false}, {"minimumPasswordLength", _memberPasswordConfigurationSettings.RequiredLength}, {"minimumPasswordNonAlphaNum", _memberPasswordConfigurationSettings.GetMinNonAlphaNumericChars()}, + {"sanitizeTinyMce", _globalSettings.SanitizeTinyMce} } }, { diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index c051bde3c9..a2e50a8185 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -62,6 +62,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly ActionCollection _actionCollection; private readonly ISqlContext _sqlContext; private readonly IAuthorizationService _authorizationService; + private readonly IContentVersionService _contentVersionService; private readonly Lazy> _allLangs; private readonly ILogger _logger; private readonly IScopeProvider _scopeProvider; @@ -90,7 +91,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers ISqlContext sqlContext, IJsonSerializer serializer, IScopeProvider scopeProvider, - IAuthorizationService authorizationService) + IAuthorizationService authorizationService, + IContentVersionService contentVersionService) : base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, serializer) { _propertyEditors = propertyEditors; @@ -109,6 +111,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _actionCollection = actionCollection; _sqlContext = sqlContext; _authorizationService = authorizationService; + _contentVersionService = contentVersionService; _logger = loggerFactory.CreateLogger(); _scopeProvider = scopeProvider; _allLangs = new Lazy>(() => _localizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase)); @@ -164,27 +167,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IEnumerable groupPermissions; if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions)) { - //create a string collection of the assigned letters - var groupPermissionCodes = groupPermissions.ToArray(); - - //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions - //for this group/node which will go back to the defaults - if (groupPermissionCodes.Length == 0) + if (groupPermissions is null) { _userService.RemoveUserGroupPermissions(userGroup.Id, content.Id); + continue; } - //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored - else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes)) - { - //only remove them if they are actually currently assigned - if (contentPermissions.ContainsKey(userGroup.Id)) - { - //remove these permissions from this node for this group since the ones being assigned are the same as the defaults - _userService.RemoveUserGroupPermissions(userGroup.Id, content.Id); - } - } - //if they are different we need to update, otherwise there's nothing to update - else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) + + // Create a string collection of the assigned letters + var groupPermissionCodes = groupPermissions.ToArray(); + // Check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored + if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) { _userService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id); @@ -2510,6 +2502,53 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return NoContent(); } + [HttpGet] + [JsonCamelCaseFormatter] + public IActionResult GetPagedContentVersions( + int contentId, + int pageNumber = 1, + int pageSize = 10, + string culture = null) + { + if (!string.IsNullOrEmpty(culture)) + { + if (!_allLangs.Value.TryGetValue(culture, out _)) + { + return NotFound(); + } + } + + IEnumerable results = _contentVersionService.GetPagedContentVersions( + contentId, + pageNumber - 1, + pageSize, + out var totalRecords, + culture); + + var model = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = results + }; + + return Ok(model); + } + + [HttpPost] + [Authorize(Policy = AuthorizationPolicies.ContentPermissionAdministrationById)] + public IActionResult PostSetContentVersionPreventCleanup(int contentId, int versionId, bool preventCleanup) + { + IContent content = _contentService.GetVersion(versionId); + + if (content == null || content.Id != contentId) + { + return NotFound(); + } + + _contentVersionService.SetPreventCleanup(versionId, preventCleanup, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); + + return NoContent(); + } + [HttpGet] public IEnumerable GetRollbackVersions(int contentId, string culture = null) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index b78ac1fdfd..8282969013 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -590,36 +590,44 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var root = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads); var tempPath = Path.Combine(root,fileName); - - using (var stream = System.IO.File.Create(tempPath)) + if (Path.GetFullPath(tempPath).StartsWith(Path.GetFullPath(root))) { - formFile.CopyToAsync(stream).GetAwaiter().GetResult(); - } - - if (ext.InvariantEquals("udt")) - { - model.TempFileName = Path.Combine(root, fileName); - - var xd = new XmlDocument + using (var stream = System.IO.File.Create(tempPath)) { - XmlResolver = null - }; - xd.Load(model.TempFileName); + formFile.CopyToAsync(stream).GetAwaiter().GetResult(); + } - model.Alias = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Alias")?.FirstChild.Value; - model.Name = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Name")?.FirstChild.Value; + if (ext.InvariantEquals("udt")) + { + model.TempFileName = Path.Combine(root, fileName); + + var xd = new XmlDocument + { + XmlResolver = null + }; + xd.Load(model.TempFileName); + + model.Alias = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Alias")?.FirstChild.Value; + model.Name = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Name")?.FirstChild.Value; + } + else + { + model.Notifications.Add(new BackOfficeNotification( + _localizedTextService.Localize("speechBubbles","operationFailedHeader"), + _localizedTextService.Localize("media","disallowedFileType"), + NotificationStyle.Warning)); + } } else { model.Notifications.Add(new BackOfficeNotification( - _localizedTextService.Localize("speechBubbles","operationFailedHeader"), - _localizedTextService.Localize("media","disallowedFileType"), + _localizedTextService.Localize("speechBubbles", "operationFailedHeader"), + _localizedTextService.Localize("media", "invalidFileName"), NotificationStyle.Warning)); } + } - - return model; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs index acfac62741..0190d5d400 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net.Mime; using System.Text; @@ -16,12 +17,11 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.BackOffice.Filters; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.Controllers { /// - /// Am abstract API controller providing functionality used for dealing with content and media types + /// Am abstract API controller providing functionality used for dealing with content and media types /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [PrefixlessBodyModelValidator] @@ -39,13 +39,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IUmbracoMapper umbracoMapper, ILocalizedTextService localizedTextService) { - _editorValidatorCollection = editorValidatorCollection ?? throw new ArgumentNullException(nameof(editorValidatorCollection)); + _editorValidatorCollection = editorValidatorCollection ?? + throw new ArgumentNullException(nameof(editorValidatorCollection)); CultureDictionary = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary)); ContentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService)); MediaTypeService = mediaTypeService ?? throw new ArgumentNullException(nameof(mediaTypeService)); MemberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService)); UmbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); - LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); + LocalizedTextService = + localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); } protected ICultureDictionary CultureDictionary { get; } @@ -56,22 +58,26 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public ILocalizedTextService LocalizedTextService { get; } /// - /// Returns the available composite content types for a given content type + /// Returns the available composite content types for a given content type /// /// /// - /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out - /// along with any content types that have matching property types that are included in the filtered content types + /// This is normally an empty list but if additional content type aliases are passed in, any content types containing + /// those aliases will be filtered out + /// along with any content types that have matching property types that are included in the filtered content types /// /// - /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out. - /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot - /// be looked up via the db, they need to be passed in. + /// This is normally an empty list but if additional property type aliases are passed in, any content types that have + /// these aliases will be filtered out. + /// This is required because in the case of creating/modifying a content type because new property types being added to + /// it are not yet persisted so cannot + /// be looked up via the db, they need to be passed in. /// /// /// Whether the composite content types should be applicable for an element type /// - protected ActionResult>> PerformGetAvailableCompositeContentTypes(int contentTypeId, + protected ActionResult>> PerformGetAvailableCompositeContentTypes( + int contentTypeId, UmbracoObjectTypes type, string[] filterContentTypes, string[] filterPropertyTypes, @@ -89,26 +95,38 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (contentTypeId > 0) { source = ContentTypeService.Get(contentTypeId); - if (source == null) return NotFound(); + if (source == null) + { + return NotFound(); + } } + allContentTypes = ContentTypeService.GetAll().Cast().ToArray(); break; case UmbracoObjectTypes.MediaType: if (contentTypeId > 0) { - source =MediaTypeService.Get(contentTypeId); - if (source == null) return NotFound(); + source = MediaTypeService.Get(contentTypeId); + if (source == null) + { + return NotFound(); + } } - allContentTypes =MediaTypeService.GetAll().Cast().ToArray(); + + allContentTypes = MediaTypeService.GetAll().Cast().ToArray(); break; case UmbracoObjectTypes.MemberType: if (contentTypeId > 0) { source = MemberTypeService.Get(contentTypeId); - if (source == null) return NotFound(); + if (source == null) + { + return NotFound(); + } } + allContentTypes = MemberTypeService.GetAll().Cast().ToArray(); break; @@ -116,16 +134,20 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers throw new ArgumentOutOfRangeException("The entity type was not a content type"); } - var availableCompositions = ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes, isElement); + ContentTypeAvailableCompositionsResults availableCompositions = + ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, + filterPropertyTypes, isElement); - - var currCompositions = source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray(); + IContentTypeComposition[] currCompositions = + source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray(); var compAliases = currCompositions.Select(x => x.Alias).ToArray(); - var ancestors = availableCompositions.Ancestors.Select(x => x.Alias); + IEnumerable ancestors = availableCompositions.Ancestors.Select(x => x.Alias); return availableCompositions.Results - .Select(x => new Tuple(UmbracoMapper.Map(x.Composition), x.Allowed)) + .Select(x => + new Tuple(UmbracoMapper.Map(x.Composition), + x.Allowed)) .Select(x => { //we need to ensure that the item is enabled if it is already selected @@ -139,9 +161,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers //translate the name x.Item1.Name = TranslateItem(x.Item1.Name); - var contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key); - var containers = GetEntityContainers(contentType, type)?.ToArray(); - var containerPath = $"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}"; + IContentTypeComposition contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key); + EntityContainer[] containers = GetEntityContainers(contentType, type)?.ToArray(); + var containerPath = + $"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}"; x.Item1.AdditionalData["containerPath"] = containerPath; return x; @@ -149,7 +172,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers .ToList(); } - private IEnumerable GetEntityContainers(IContentTypeComposition contentType, UmbracoObjectTypes type) + private IEnumerable GetEntityContainers(IContentTypeComposition contentType, + UmbracoObjectTypes type) { if (contentType == null) { @@ -170,12 +194,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } /// - /// Returns a list of content types where a particular composition content type is used + /// Returns a list of content types where a particular composition content type is used /// /// Type of content Type, eg documentType or mediaType /// Id of composition content type /// - protected ActionResult> PerformGetWhereCompositionIsUsedInContentTypes(int contentTypeId, UmbracoObjectTypes type) + protected ActionResult> PerformGetWhereCompositionIsUsedInContentTypes( + int contentTypeId, UmbracoObjectTypes type) { var id = 0; @@ -190,7 +215,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers break; case UmbracoObjectTypes.MediaType: - source =MediaTypeService.Get(contentTypeId); + source = MediaTypeService.Get(contentTypeId); break; case UmbracoObjectTypes.MemberType: @@ -202,7 +227,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } if (source == null) + { return NotFound(); + } id = source.Id; } @@ -216,7 +243,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers break; case UmbracoObjectTypes.MediaType: - composedOf =MediaTypeService.GetComposedOf(id); + composedOf = MediaTypeService.GetComposedOf(id); break; case UmbracoObjectTypes.MemberType: @@ -242,10 +269,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers protected string TranslateItem(string text) { if (text == null) + { return null; + } if (text.StartsWith("#") == false) + { return text; + } text = text.Substring(1); return CultureDictionary[text].IfNullOrWhiteSpace(text); @@ -261,18 +292,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers where TPropertyType : PropertyTypeBasic { var ctId = Convert.ToInt32(contentTypeSave.Id); - var ct = ctId > 0 ? getContentType(ctId) : null; - if (ctId > 0 && ct == null) return NotFound(); + TContentType ct = ctId > 0 ? getContentType(ctId) : null; + if (ctId > 0 && ct == null) + { + return NotFound(); + } //Validate that there's no other ct with the same alias // it in fact cannot be the same as any content type alias (member, content or media) because // this would interfere with how ModelsBuilder works and also how many of the published caches // works since that is based on aliases. - var allAliases = ContentTypeService.GetAllContentTypeAliases(); + IEnumerable allAliases = ContentTypeService.GetAllContentTypeAliases(); var exists = allAliases.InvariantContains(contentTypeSave.Alias); if (exists && (ctId == 0 || !ct.Alias.InvariantEquals(contentTypeSave.Alias))) { - ModelState.AddModelError("Alias", LocalizedTextService.Localize("editcontenttype", "aliasAlreadyExists")); + ModelState.AddModelError("Alias", + LocalizedTextService.Localize("editcontenttype", "aliasAlreadyExists")); } // execute the external validators @@ -280,13 +315,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (ModelState.IsValid == false) { - var err = CreateModelStateValidationEror(ctId, contentTypeSave, ct); + TContentTypeDisplay err = + CreateModelStateValidationEror(ctId, contentTypeSave, ct); return ValidationProblem(err); } //filter out empty properties contentTypeSave.Groups = contentTypeSave.Groups.Where(x => x.Name.IsNullOrWhiteSpace() == false).ToList(); - foreach (var group in contentTypeSave.Groups) + foreach (PropertyGroupBasic group in contentTypeSave.Groups) { group.Properties = group.Properties.Where(x => x.Alias.IsNullOrWhiteSpace() == false).ToList(); } @@ -302,12 +338,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } catch (Exception ex) { - var responseEx = CreateInvalidCompositionResponseException(ex, contentTypeSave, ct, ctId); - if (responseEx != null) return ValidationProblem(responseEx); + TContentTypeDisplay responseEx = + CreateInvalidCompositionResponseException( + ex, contentTypeSave, ct, ctId); + if (responseEx != null) + { + return ValidationProblem(responseEx); + } } - var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, ct); - if (exResult != null) return ValidationProblem(exResult); + TContentTypeDisplay exResult = + CreateCompositionValidationExceptionIfInvalid( + contentTypeSave, ct); + if (exResult != null) + { + return ValidationProblem(exResult); + } saveContentType(ct); @@ -329,7 +375,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers allowIfselfAsChildSortOrder = contentTypeSave.AllowedContentTypes.IndexOf(0); allowItselfAsChild = contentTypeSave.AllowedContentTypes.Any(x => x == 0); - contentTypeSave.AllowedContentTypes = contentTypeSave.AllowedContentTypes.Where(x => x > 0).ToList(); + contentTypeSave.AllowedContentTypes = + contentTypeSave.AllowedContentTypes.Where(x => x > 0).ToList(); } //save as new @@ -342,15 +389,24 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } catch (Exception ex) { - var responseEx = CreateInvalidCompositionResponseException(ex, contentTypeSave, ct, ctId); + TContentTypeDisplay responseEx = + CreateInvalidCompositionResponseException( + ex, contentTypeSave, ct, ctId); if (responseEx is null) + { throw ex; + } return ValidationProblem(responseEx); } - var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, newCt); - if (exResult != null) return ValidationProblem(exResult); + TContentTypeDisplay exResult = + CreateCompositionValidationExceptionIfInvalid( + contentTypeSave, newCt); + if (exResult != null) + { + return ValidationProblem(exResult); + } //set id to null to ensure its handled as a new type contentTypeSave.Id = null; @@ -364,30 +420,33 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { newCt.AllowedContentTypes = newCt.AllowedContentTypes.Union( - new []{ new ContentTypeSort(newCt.Id, allowIfselfAsChildSortOrder) } + new[] { new ContentTypeSort(newCt.Id, allowIfselfAsChildSortOrder) } ); saveContentType(newCt); } + return newCt; } } private void ValidateExternalValidators(ModelStateDictionary modelState, object model) { - var modelType = model.GetType(); + Type modelType = model.GetType(); - var validationResults = _editorValidatorCollection - .Where(x => x.ModelType == modelType) - .SelectMany(x => x.Validate(model)) - .Where(x => !string.IsNullOrWhiteSpace(x.ErrorMessage) && x.MemberNames.Any()); + IEnumerable validationResults = _editorValidatorCollection + .Where(x => x.ModelType == modelType) + .SelectMany(x => x.Validate(model)) + .Where(x => !string.IsNullOrWhiteSpace(x.ErrorMessage) && x.MemberNames.Any()); - foreach (var r in validationResults) - foreach (var m in r.MemberNames) - modelState.AddModelError(m, r.ErrorMessage); + foreach (ValidationResult r in validationResults) + foreach (var m in r.MemberNames) + { + modelState.AddModelError(m, r.ErrorMessage); + } } /// - /// Move + /// Move /// /// /// @@ -398,13 +457,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers Func getContentType, Func>> doMove) { - var toMove = getContentType(move.Id); + TContentType toMove = getContentType(move.Id); if (toMove == null) { return NotFound(); } - var result = doMove(toMove, move.ParentId); + Attempt> result = doMove(toMove, move.ParentId); if (result.Success) { return Content(toMove.Path, MediaTypeNames.Text.Plain, Encoding.UTF8); @@ -424,7 +483,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } /// - /// Move + /// Move /// /// /// @@ -435,13 +494,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers Func getContentType, Func>> doCopy) { - var toMove = getContentType(move.Id); + TContentType toMove = getContentType(move.Id); if (toMove == null) { return NotFound(); } - var result = doCopy(toMove, move.ParentId); + Attempt> result = doCopy(toMove, move.ParentId); if (result.Success) { return Content(toMove.Path, MediaTypeNames.Text.Plain, Encoding.UTF8); @@ -461,31 +520,39 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } /// - /// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors + /// Validates the composition and adds errors to the model state if any are found then throws an error response if + /// there are errors /// /// /// /// - private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition) + private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition) where TContentTypeSave : ContentTypeSave where TPropertyType : PropertyTypeBasic where TContentTypeDisplay : ContentTypeCompositionDisplay { - var service = GetContentTypeService(); - var validateAttempt = service.ValidateComposition(composition); + IContentTypeBaseService service = GetContentTypeService(); + Attempt validateAttempt = service.ValidateComposition(composition); if (validateAttempt == false) { - //if it's not successful then we need to return some model state for the property aliases that - // are duplicated - var invalidPropertyAliases = validateAttempt.Result.Distinct(); - AddCompositionValidationErrors(contentTypeSave, invalidPropertyAliases); + // if it's not successful then we need to return some model state for the property type and property group + // aliases that are duplicated + IEnumerable duplicatePropertyTypeAliases = validateAttempt.Result.Distinct(); + var invalidPropertyGroupAliases = + (validateAttempt.Exception as InvalidCompositionException)?.PropertyGroupAliases ?? + Array.Empty(); - var display = UmbracoMapper.Map(composition); + AddCompositionValidationErrors(contentTypeSave, + duplicatePropertyTypeAliases, invalidPropertyGroupAliases); + + TContentTypeDisplay display = UmbracoMapper.Map(composition); //map the 'save' data on top display = UmbracoMapper.Map(contentTypeSave, display); display.Errors = ModelState.ToErrorDictionary(); return display; } + return null; } @@ -493,39 +560,62 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers where T : IContentTypeComposition { if (typeof(T).Implements()) + { return ContentTypeService as IContentTypeBaseService; + } + if (typeof(T).Implements()) + { return MediaTypeService as IContentTypeBaseService; + } + if (typeof(T).Implements()) + { return MemberTypeService as IContentTypeBaseService; + } + throw new ArgumentException("Type " + typeof(T).FullName + " does not have a service."); } /// - /// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors + /// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors /// /// - /// + /// + /// /// - private void AddCompositionValidationErrors(TContentTypeSave contentTypeSave, IEnumerable invalidPropertyAliases) + private void AddCompositionValidationErrors(TContentTypeSave contentTypeSave, + IEnumerable duplicatePropertyTypeAliases, IEnumerable invalidPropertyGroupAliases) where TContentTypeSave : ContentTypeSave where TPropertyType : PropertyTypeBasic { - foreach (var propertyAlias in invalidPropertyAliases) + foreach (var propertyTypeAlias in duplicatePropertyTypeAliases) { - // Find the property relating to these - var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias); - var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(property)); + // Find the property type relating to these + TPropertyType property = contentTypeSave.Groups.SelectMany(x => x.Properties) + .Single(x => x.Alias == propertyTypeAlias); + PropertyGroupBasic group = + contentTypeSave.Groups.Single(x => x.Properties.Contains(property)); var propertyIndex = group.Properties.IndexOf(property); var groupIndex = contentTypeSave.Groups.IndexOf(group); var key = $"Groups[{groupIndex}].Properties[{propertyIndex}].Alias"; - ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions"); + ModelState.AddModelError(key, "Duplicate property aliases aren't allowed between compositions"); + } + + foreach (var propertyGroupAlias in invalidPropertyGroupAliases) + { + // Find the property group relating to these + PropertyGroupBasic group = + contentTypeSave.Groups.Single(x => x.Alias == propertyGroupAlias); + var groupIndex = contentTypeSave.Groups.IndexOf(group); + var key = $"Groups[{groupIndex}].Name"; + ModelState.AddModelError(key, "Different group types aren't allowed between compositions"); } } /// - /// If the exception is an InvalidCompositionException create a response exception to be thrown for validation errors + /// If the exception is an InvalidCompositionException create a response exception to be thrown for validation errors /// /// /// @@ -535,7 +625,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - private TContentTypeDisplay CreateInvalidCompositionResponseException( + private TContentTypeDisplay CreateInvalidCompositionResponseException( Exception ex, TContentTypeSave contentTypeSave, TContentType ct, int ctId) where TContentTypeDisplay : ContentTypeCompositionDisplay where TContentTypeSave : ContentTypeSave @@ -550,23 +641,27 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { invalidCompositionException = (InvalidCompositionException)ex.InnerException; } + if (invalidCompositionException != null) { - AddCompositionValidationErrors(contentTypeSave, invalidCompositionException.PropertyTypeAliases); + AddCompositionValidationErrors(contentTypeSave, + invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases); return CreateModelStateValidationEror(ctId, contentTypeSave, ct); } + return null; } /// - /// Used to throw the ModelState validation results when the ModelState is invalid + /// Used to throw the ModelState validation results when the ModelState is invalid /// /// /// /// /// /// - private TContentTypeDisplay CreateModelStateValidationEror(int ctId, TContentTypeSave contentTypeSave, TContentType ct) + private TContentTypeDisplay CreateModelStateValidationEror(int ctId, + TContentTypeSave contentTypeSave, TContentType ct) where TContentTypeDisplay : ContentTypeCompositionDisplay where TContentTypeSave : ContentTypeSave { diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 2eaeee3d12..c3f36e92cb 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Dynamic; using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -13,6 +15,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Models.TemplateQuery; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; @@ -26,20 +29,22 @@ using Umbraco.Cms.Web.BackOffice.ModelBinders; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.ModelBinders; using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.Controllers { /// - /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode + /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are + /// based on CMSNode /// /// - /// - /// This controller allows resolving basic entity data for various entities without placing the hard restrictions on users that may not have access - /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all users. In some cases such as accessing - /// Members, more explicit security checks are done. - /// - /// Some objects such as macros are not based on CMSNode + /// + /// This controller allows resolving basic entity data for various entities without placing the hard restrictions + /// on users that may not have access + /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all + /// users. In some cases such as accessing + /// Members, more explicit security checks are done. + /// + /// Some objects such as macros are not based on CMSNode /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [ParameterSwapControllerActionSelector(nameof(GetAncestors), "id", typeof(int), typeof(Guid))] @@ -51,26 +56,28 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [ParameterSwapControllerActionSelector(nameof(GetUrl), "id", typeof(int), typeof(Udi))] public class EntityController : UmbracoAuthorizedJsonController { - private readonly ITreeService _treeService; - private readonly UmbracoTreeSearcher _treeSearcher; - private readonly SearchableTreeCollection _searchableTreeCollection; - private readonly IPublishedContentQuery _publishedContentQuery; - private readonly IShortStringHelper _shortStringHelper; - private readonly IEntityService _entityService; - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; - private readonly IPublishedUrlProvider _publishedUrlProvider; - private readonly IContentService _contentService; - private readonly IUmbracoMapper _umbracoMapper; - private readonly IDataTypeService _dataTypeService; - private readonly ISqlContext _sqlContext; - private readonly ILocalizedTextService _localizedTextService; - private readonly IFileService _fileService; - private readonly IContentTypeService _contentTypeService; - private readonly IMediaTypeService _mediaTypeService; - private readonly IMacroService _macroService; - private readonly IUserService _userService; - private readonly ILocalizationService _localizationService; + private static readonly string[] _postFilterSplitStrings = { "=", "==", "!=", "<>", ">", "<", ">=", "<=" }; + private readonly AppCaches _appCaches; + private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IContentService _contentService; + private readonly IContentTypeService _contentTypeService; + private readonly IDataTypeService _dataTypeService; + private readonly IEntityService _entityService; + private readonly IFileService _fileService; + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IMacroService _macroService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IPublishedContentQuery _publishedContentQuery; + private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly SearchableTreeCollection _searchableTreeCollection; + private readonly IShortStringHelper _shortStringHelper; + private readonly ISqlContext _sqlContext; + private readonly UmbracoTreeSearcher _treeSearcher; + private readonly ITreeService _treeService; + private readonly IUmbracoMapper _umbracoMapper; + private readonly IUserService _userService; public EntityController( ITreeService treeService, @@ -102,7 +109,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers publishedContentQuery ?? throw new ArgumentNullException(nameof(publishedContentQuery)); _shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper)); _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); - _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); + _backofficeSecurityAccessor = backofficeSecurityAccessor ?? + throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); _publishedUrlProvider = publishedUrlProvider ?? throw new ArgumentNullException(nameof(publishedUrlProvider)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); @@ -120,16 +128,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _appCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches)); } + /// - /// Returns an Umbraco alias given a string + /// Returns an Umbraco alias given a string /// /// /// /// public dynamic GetSafeAlias(string value, bool camelCase = true) { - var returnValue = string.IsNullOrWhiteSpace(value) ? string.Empty : value.ToSafeAlias(_shortStringHelper, camelCase); - dynamic returnObj = new System.Dynamic.ExpandoObject(); + var returnValue = string.IsNullOrWhiteSpace(value) + ? string.Empty + : value.ToSafeAlias(_shortStringHelper, camelCase); + dynamic returnObj = new ExpandoObject(); returnObj.alias = returnValue; returnObj.original = value; returnObj.camelCase = camelCase; @@ -138,41 +149,47 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } /// - /// Searches for results based on the entity type + /// Searches for results based on the entity type /// /// /// /// - /// A starting point for the search, generally a node id, but for members this is a member type alias + /// A starting point for the search, generally a node id, but for members this is a member type alias /// /// If set used to look up whether user and group start node permissions will be ignored. /// [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeKey = null) + public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, + Guid? dataTypeKey = null) { // NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that if (string.IsNullOrEmpty(query)) + { return Enumerable.Empty(); + } //TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type - var ignoreUserStartNodes = dataTypeKey.HasValue && _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value); + var ignoreUserStartNodes = dataTypeKey.HasValue && + _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value); return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes); } /// - /// Searches for all content that the user is allowed to see (based on their allowed sections) + /// Searches for all content that the user is allowed to see (based on their allowed sections) /// /// /// /// - /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need - /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search - /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. - /// - /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search - /// methods might be used in things like pickers in the content editor. + /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to + /// edit, we need + /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the + /// global search + /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. + /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those + /// search + /// methods might be used in things like pickers in the content editor. /// [HttpGet] public IDictionary SearchAll(string query) @@ -180,16 +197,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var result = new Dictionary(); if (string.IsNullOrEmpty(query)) + { return result; + } var allowedSections = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.AllowedSections.ToArray(); - foreach (var searchableTree in _searchableTreeCollection.SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder)) + foreach (KeyValuePair searchableTree in _searchableTreeCollection + .SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder)) { if (allowedSections.Contains(searchableTree.Value.AppAlias)) { - var tree = _treeService.GetByAlias(searchableTree.Key); - if (tree == null) continue; //shouldn't occur + Tree tree = _treeService.GetByAlias(searchableTree.Key); + if (tree == null) + { + continue; //shouldn't occur + } result[Tree.GetRootNodeDisplayName(tree, _localizedTextService)] = new TreeSearchResult { @@ -201,49 +224,52 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers }; } } + return result; } /// - /// Gets the path for a given node ID + /// Gets the path for a given node ID /// /// /// /// public IConvertToActionResult GetPath(int id, UmbracoEntityTypes type) { - var foundContentResult = GetResultForId(id, type); - var foundContent = foundContentResult.Value; + ActionResult foundContentResult = GetResultForId(id, type); + EntityBasic foundContent = foundContentResult.Value; if (foundContent is null) { return foundContentResult; } - return new ActionResult>(foundContent.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select( - s => int.Parse(s, CultureInfo.InvariantCulture))); + return new ActionResult>(foundContent.Path + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select( + s => int.Parse(s, CultureInfo.InvariantCulture))); } /// - /// Gets the path for a given node ID + /// Gets the path for a given node ID /// /// /// /// public IConvertToActionResult GetPath(Guid id, UmbracoEntityTypes type) { - var foundContentResult = GetResultForKey(id, type); - var foundContent = foundContentResult.Value; + ActionResult foundContentResult = GetResultForKey(id, type); + EntityBasic foundContent = foundContentResult.Value; if (foundContent is null) { return foundContentResult; } - return new ActionResult>(foundContent.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select( - s => int.Parse(s, CultureInfo.InvariantCulture))); + return new ActionResult>(foundContent.Path + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select( + s => int.Parse(s, CultureInfo.InvariantCulture))); } /// - /// Gets the path for a given node ID + /// Gets the path for a given node ID /// /// /// @@ -260,16 +286,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } /// - /// Gets the URL of an entity + /// Gets the URL of an entity /// /// UDI of the entity to fetch URL for /// The culture to fetch the URL for /// The URL or path to the item public IActionResult GetUrl(Udi id, string culture = "*") { - var intId = _entityService.GetId(id); + Attempt intId = _entityService.GetId(id); if (!intId.Success) + { return NotFound(); + } + UmbracoEntityTypes entityType; switch (id.EntityType) { @@ -285,18 +314,61 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers default: return NotFound(); } + return GetUrl(intId.Result, entityType, culture); } /// - /// Gets the URL of an entity + /// Get entity URLs by UDIs + /// + /// + /// A list of UDIs to lookup items by + /// + /// The culture to fetch the URL for + /// Dictionary mapping Udi -> Url + /// + /// We allow for POST because there could be quite a lot of Ids. + /// + [HttpGet] + [HttpPost] + public IDictionary GetUrlsByUdis([FromJsonPath] Udi[] udis, string culture = null) + { + if (udis == null || udis.Length == 0) + { + return new Dictionary(); + } + + // TODO: PMJ 2021-09-27 - Should GetUrl(Udi) exist as an extension method on UrlProvider/IUrlProvider (in v9) + string MediaOrDocumentUrl(Udi udi) + { + if (udi is not GuidUdi guidUdi) + { + return null; + } + + return guidUdi.EntityType switch + { + Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(guidUdi.Guid, + culture: culture ?? ClientCulture()), + // NOTE: If culture is passed here we get an empty string rather than a media item URL WAT + Constants.UdiEntityType.Media => _publishedUrlProvider.GetMediaUrl(guidUdi.Guid, culture: null), + _ => null + }; + } + + return udis + .Select(udi => new { Udi = udi, Url = MediaOrDocumentUrl(udi) }).ToDictionary(x => x.Udi, x => x.Url); + } + + /// + /// Gets the URL of an entity /// /// Int id of the entity to fetch URL for /// The type of entity such as Document, Media, Member /// The culture to fetch the URL for /// The URL or path to the item /// - /// We are not restricting this with security because there is no sensitive data + /// We are not restricting this with security because there is no sensitive data /// public IActionResult GetUrl(int id, UmbracoEntityTypes type, string culture = null) { @@ -315,7 +387,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } - var ancestors = GetResultForAncestors(id, type); + IEnumerable ancestors = GetResultForAncestors(id, type); //if content, skip the first node for replicating NiceUrl defaults if (type == UmbracoEntityTypes.Document) @@ -330,7 +402,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// - /// Gets an entity by a xpath query + /// Gets an entity by a xpath query /// /// /// @@ -342,48 +414,53 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (type != UmbracoEntityTypes.Document) + { throw new ArgumentException("Get by query is only compatible with entities of type Document"); + } var q = ParseXPathQuery(query, nodeContextId); - var node = _publishedContentQuery.ContentSingleAtXPath(q); + IPublishedContent node = _publishedContentQuery.ContentSingleAtXPath(q); if (node == null) + { return null; + } return GetById(node.Id, type); } // PP: Work in progress on the query parser - private string ParseXPathQuery(string query, int id) - { - return UmbracoXPathPathSyntaxParser.ParseXPathQuery( - xpathExpression: query, - nodeContextId: id, - getPath: nodeid => + private string ParseXPathQuery(string query, int id) => + UmbracoXPathPathSyntaxParser.ParseXPathQuery( + query, + id, + nodeid => { - var ent = _entityService.Get(nodeid); + IEntitySlim ent = _entityService.Get(nodeid); return ent.Path.Split(Constants.CharArrays.Comma).Reverse(); }, - publishedContentExists: i => _publishedContentQuery.Content(i) != null); - } + i => _publishedContentQuery.Content(i) != null); [HttpGet] public ActionResult GetUrlAndAnchors(Udi id, string culture = "*") { - var intId = _entityService.GetId(id); + Attempt intId = _entityService.GetId(id); if (!intId.Success) + { return NotFound(); + } return GetUrlAndAnchors(intId.Result, culture); } + [HttpGet] public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*") { culture = culture ?? ClientCulture(); var url = _publishedUrlProvider.GetUrl(id, culture: culture); - var anchorValues = _contentService.GetAnchorValuesFromRTEs(id, culture); + IEnumerable anchorValues = _contentService.GetAnchorValuesFromRTEs(id, culture); return new UrlAndAnchors(url, anchorValues); } @@ -391,137 +468,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [HttpPost] public IEnumerable GetAnchors(AnchorsModel model) { - var anchorValues = _contentService.GetAnchorValuesFromRTEContent(model.RteContent); + IEnumerable anchorValues = _contentService.GetAnchorValuesFromRTEContent(model.RteContent); return anchorValues; } - - #region GetById - - /// - /// Gets an entity by it's id - /// - /// - /// - /// - public ActionResult GetById(int id, UmbracoEntityTypes type) - { - return GetResultForId(id, type); - } - - /// - /// Gets an entity by it's key - /// - /// - /// - /// - public ActionResult GetById(Guid id, UmbracoEntityTypes type) - { - return GetResultForKey(id, type); - } - - /// - /// Gets an entity by it's UDI - /// - /// - /// - /// - public ActionResult GetById(Udi id, UmbracoEntityTypes type) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetResultForKey(guidUdi.Guid, type); - } - - return NotFound(); - } - #endregion - - #region GetByIds - /// - /// Get entities by integer ids - /// - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids - /// - [HttpGet] - [HttpPost] - public ActionResult> GetByIds([FromJsonPath]int[] ids, [FromQuery]UmbracoEntityTypes type) - { - if (ids == null) - { - return NotFound(); - } - - return new ActionResult>(GetResultForIds(ids, type)); - } - - /// - /// Get entities by GUID ids - /// - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids - /// - [HttpGet] - [HttpPost] - public ActionResult> GetByIds([FromJsonPath]Guid[] ids, [FromQuery]UmbracoEntityTypes type) - { - if (ids == null) - { - return NotFound(); - } - - return new ActionResult>(GetResultForKeys(ids, type)); - } - - /// - /// Get entities by UDIs - /// - /// - /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids. - /// - [HttpGet] - [HttpPost] - public ActionResult> GetByIds([FromJsonPath]Udi[] ids, [FromQuery]UmbracoEntityTypes type) - { - if (ids == null) - { - return NotFound(); - } - - if (ids.Length == 0) - { - return Enumerable.Empty().ToList(); - } - - //all udi types will need to be the same in this list so we'll determine by the first - //currently we only support GuidUdi for this method - - var guidUdi = ids[0] as GuidUdi; - if (guidUdi != null) - { - return new ActionResult>(GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type)); - } - - return NotFound(); - } - #endregion - public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeKey = null) { - var objectType = ConvertToObjectType(type); + UmbracoObjectTypes? objectType = ConvertToObjectType(type); if (objectType.HasValue) { //TODO: Need to check for Object types that support hierarchy here, some might not. @@ -531,11 +484,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes - if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + if (id == Constants.System.Root && startNodes.Length > 0 && + startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) { - var nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray(); + IEntitySlim[] nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray(); if (nodes.Length == 0) + { return Enumerable.Empty(); + } + var pr = new List(nodes.Select(_umbracoMapper.Map)); return pr; } @@ -546,6 +503,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers .WhereNotNull() .Select(_umbracoMapper.Map); } + //now we need to convert the unknown ones switch (type) { @@ -553,12 +511,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case UmbracoEntityTypes.User: case UmbracoEntityTypes.Macro: default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + type); } } /// - /// Get paged child entities by id + /// Get paged child entities by id /// /// /// @@ -602,7 +561,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers //the EntityService can search paged members from the root intId = -1; - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeKey); + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, + dataTypeKey); } //the EntityService cannot search members of a certain type, this is currently not supported and would require @@ -611,16 +571,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers //TODO: We should really fix this in the EntityService but if we don't we should allow the ISearchableTree for the members controller // to be used for this search instead of the built in/internal searcher - var searchResult = _treeSearcher.ExamineSearch(filter ?? "", type, pageSize, pageNumber - 1, out long total, null, id); + IEnumerable searchResult = _treeSearcher.ExamineSearch(filter ?? "", type, pageSize, + pageNumber - 1, out var total, null, id); - return new PagedResult(total, pageNumber, pageSize) - { - Items = searchResult - }; + return new PagedResult(total, pageNumber, pageSize) { Items = searchResult }; } /// - /// Get paged child entities by id + /// Get paged child entities by id /// /// /// @@ -641,11 +599,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers Guid? dataTypeKey = null) { if (pageNumber <= 0) + { return NotFound(); - if (pageSize <= 0) - return NotFound(); + } - var objectType = ConvertToObjectType(type); + if (pageSize <= 0) + { + return NotFound(); + } + + UmbracoObjectTypes? objectType = ConvertToObjectType(type); if (objectType.HasValue) { IEnumerable entities; @@ -656,14 +619,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes - if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + if (id == Constants.System.Root && startNodes.Length > 0 && + startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) { if (pageNumber > 0) + { return new PagedResult(0, 0, 0); - var nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray(); + } + + IEntitySlim[] nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray(); if (nodes.Length == 0) + { return new PagedResult(0, 0, 0); - if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + } + + if (pageSize < nodes.Length) + { + pageSize = nodes.Length; // bah + } + var pr = new PagedResult(nodes.Length, pageNumber, pageSize) { Items = nodes.Select(_umbracoMapper.Map) @@ -672,7 +646,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } // else proceed as usual - entities = _entityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, + entities = _entityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, + out totalRecords, filter.IsNullOrWhiteSpace() ? null : _sqlContext.Query().Where(x => x.Name.Contains(filter)), @@ -689,7 +664,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { Items = entities.Select(source => { - var target = _umbracoMapper.Map(source, context => + EntityBasic target = _umbracoMapper.Map(source, context => { context.SetCulture(culture); context.SetCulture(culture); @@ -712,7 +687,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case UmbracoEntityTypes.User: case UmbracoEntityTypes.Macro: default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + type); } } @@ -721,9 +697,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers switch (type) { case UmbracoEntityTypes.Document: - return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches); + return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds( + _entityService, _appCaches); case UmbracoEntityTypes.Media: - return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches); + return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds( + _entityService, _appCaches); default: return Array.Empty(); } @@ -740,14 +718,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers Guid? dataTypeKey = null) { if (pageNumber <= 0) + { return NotFound(); + } + if (pageSize <= 0) + { return NotFound(); + } // re-normalize since NULL can be passed in filter = filter ?? string.Empty; - var objectType = ConvertToObjectType(type); + UmbracoObjectTypes? objectType = ConvertToObjectType(type); if (objectType.HasValue) { IEnumerable entities; @@ -757,20 +740,23 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { // root is special: we reduce it to start nodes - int[] aids = GetStartNodes(type); + var aids = GetStartNodes(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes - ? _entityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords, + ? _entityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, + out totalRecords, _sqlContext.Query().Where(x => x.Name.Contains(filter)), - Ordering.By(orderBy, orderDirection), includeTrashed: false) - : _entityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, + Ordering.By(orderBy, orderDirection), false) + : _entityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, + out totalRecords, _sqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection)); } else { - entities = _entityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, + entities = _entityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, + out totalRecords, _sqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection)); } @@ -797,20 +783,26 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case UmbracoEntityTypes.User: case UmbracoEntityTypes.Macro: default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + type); } } - private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue && _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value); + private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue && + _dataTypeService + .IsDataTypeIgnoringUserStartNodes( + dataTypeKey.Value); - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) - { - return GetResultForAncestors(id, type, queryStrings); - } + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, + [ModelBinder(typeof(HttpQueryStringModelBinder))] + FormCollection queryStrings) => + GetResultForAncestors(id, type, queryStrings); - public ActionResult> GetAncestors(Guid id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) + public ActionResult> GetAncestors(Guid id, UmbracoEntityTypes type, + [ModelBinder(typeof(HttpQueryStringModelBinder))] + FormCollection queryStrings) { - var entity = _entityService.Get(id); + IEntitySlim entity = _entityService.Get(id); if (entity is null) { return NotFound(); @@ -820,22 +812,24 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } /// - /// Searches for results based on the entity type + /// Searches for results based on the entity type /// /// /// /// /// If set to true, user and group start node permissions will be ignored. /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, + string searchFrom = null, bool ignoreUserStartNodes = false) { var culture = ClientCulture(); - return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, culture, searchFrom, ignoreUserStartNodes); + return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, culture, searchFrom, + ignoreUserStartNodes); } private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) { - var objectType = ConvertToObjectType(entityType); + UmbracoObjectTypes? objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { // TODO: Need to check for Object types that support hierarchic here, some might not. @@ -844,6 +838,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers .WhereNotNull() .Select(MapEntities()); } + //now we need to convert the unknown ones switch (entityType) { @@ -851,30 +846,37 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case UmbracoEntityTypes.User: case UmbracoEntityTypes.Macro: default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + entityType); } } - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormCollection queryStrings = null) + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, + FormCollection queryStrings = null) { - var objectType = ConvertToObjectType(entityType); + UmbracoObjectTypes? objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { // TODO: Need to check for Object types that support hierarchic here, some might not. - var ids = _entityService.Get(id).Path.Split(Constants.CharArrays.Comma).Select(s => int.Parse(s, CultureInfo.InvariantCulture)).Distinct().ToArray(); + var ids = _entityService.Get(id).Path.Split(Constants.CharArrays.Comma) + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)).Distinct().ToArray(); - var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(queryStrings?.GetValue("dataTypeId")); + var ignoreUserStartNodes = + IsDataTypeIgnoringUserStartNodes(queryStrings?.GetValue("dataTypeId")); if (ignoreUserStartNodes == false) { int[] aids = null; switch (entityType) { case UmbracoEntityTypes.Document: - aids = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches); + aids = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser + .CalculateContentStartNodeIds(_entityService, _appCaches); break; case UmbracoEntityTypes.Media: - aids = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches); + aids = + _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds( + _entityService, _appCaches); break; } @@ -889,12 +891,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers lids.Add(i); continue; } + if (aids.Contains(i)) { lids.Add(i); ok = true; } } + ids = lids.ToArray(); } } @@ -908,6 +912,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers .OrderBy(x => x.Level) .Select(MapEntities(culture)); } + //now we need to convert the unknown ones switch (entityType) { @@ -917,28 +922,33 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case UmbracoEntityTypes.User: case UmbracoEntityTypes.Macro: default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + entityType); } } private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) { if (keys.Length == 0) + { return Enumerable.Empty(); + } - var objectType = ConvertToObjectType(entityType); + UmbracoObjectTypes? objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - var entities = _entityService.GetAll(objectType.Value, keys) + IEnumerable entities = _entityService.GetAll(objectType.Value, keys) .WhereNotNull() .Select(MapEntities()); // entities are in "some" order, put them back in order var xref = entities.ToDictionary(x => x.Key); - var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + IEnumerable result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null) + .Where(x => x != null); return result; } + //now we need to convert the unknown ones switch (entityType) { @@ -948,28 +958,33 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case UmbracoEntityTypes.User: case UmbracoEntityTypes.Macro: default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + entityType); } } private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType) { if (ids.Length == 0) + { return Enumerable.Empty(); + } - var objectType = ConvertToObjectType(entityType); + UmbracoObjectTypes? objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - var entities = _entityService.GetAll(objectType.Value, ids) + IEnumerable entities = _entityService.GetAll(objectType.Value, ids) .WhereNotNull() .Select(MapEntities()); // entities are in "some" order, put them back in order var xref = entities.ToDictionary(x => x.Id); - var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + IEnumerable result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null) + .Where(x => x != null); return result; } + //now we need to convert the unknown ones switch (entityType) { @@ -979,22 +994,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case UmbracoEntityTypes.User: case UmbracoEntityTypes.Macro: default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + entityType); } } private ActionResult GetResultForKey(Guid key, UmbracoEntityTypes entityType) { - var objectType = ConvertToObjectType(entityType); + UmbracoObjectTypes? objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - var found = _entityService.Get(key, objectType.Value); + IEntitySlim found = _entityService.Get(key, objectType.Value); if (found == null) { return NotFound(); } + return _umbracoMapper.Map(found); } + //now we need to convert the unknown ones switch (entityType) { @@ -1008,23 +1026,35 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case UmbracoEntityTypes.Macro: + case UmbracoEntityTypes.Template: + ITemplate template = _fileService.GetTemplate(key); + if (template is null) + { + return NotFound(); + } + + return _umbracoMapper.Map(template); + default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + entityType); } } private ActionResult GetResultForId(int id, UmbracoEntityTypes entityType) { - var objectType = ConvertToObjectType(entityType); + UmbracoObjectTypes? objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - var found = _entityService.Get(id, objectType.Value); + IEntitySlim found = _entityService.Get(id, objectType.Value); if (found == null) { return NotFound(); } + return MapEntity(found); } + //now we need to convert the unknown ones switch (entityType) { @@ -1038,8 +1068,18 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case UmbracoEntityTypes.Macro: + case UmbracoEntityTypes.Template: + ITemplate template = _fileService.GetTemplate(id); + if (template is null) + { + return NotFound(); + } + + return _umbracoMapper.Map(template); + default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + entityType); } } @@ -1070,145 +1110,151 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } /// - /// /// /// The type of entity. - /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are ignored. + /// + /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are + /// ignored. + /// /// - public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter) - { - return GetResultForAll(type, postFilter); - } + public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter) => + GetResultForAll(type, postFilter); /// - /// Gets the result for the entity list based on the type + /// Gets the result for the entity list based on the type /// /// /// A string where filter that will filter the results dynamically with linq - optional /// private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null) { - var objectType = ConvertToObjectType(entityType); + UmbracoObjectTypes? objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { // TODO: Should we order this by something ? - var entities = _entityService.GetAll(objectType.Value).WhereNotNull().Select(MapEntities()); + IEnumerable entities = + _entityService.GetAll(objectType.Value).WhereNotNull().Select(MapEntities()); return ExecutePostFilter(entities, postFilter); } + //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.Template: - var templates = _fileService.GetTemplates(); - var filteredTemplates = ExecutePostFilter(templates, postFilter); + IEnumerable templates = _fileService.GetTemplates(); + IEnumerable filteredTemplates = ExecutePostFilter(templates, postFilter); return filteredTemplates.Select(MapEntities()); case UmbracoEntityTypes.Macro: //Get all macros from the macro service - var macros = _macroService.GetAll().WhereNotNull().OrderBy(x => x.Name); - var filteredMacros = ExecutePostFilter(macros, postFilter); + IOrderedEnumerable macros = _macroService.GetAll().WhereNotNull().OrderBy(x => x.Name); + IEnumerable filteredMacros = ExecutePostFilter(macros, postFilter); return filteredMacros.Select(MapEntities()); case UmbracoEntityTypes.PropertyType: //get all document types, then combine all property types into one list - var propertyTypes = _contentTypeService.GetAll().Cast() - .Concat(_mediaTypeService.GetAll()) - .ToArray() - .SelectMany(x => x.PropertyTypes) - .DistinctBy(composition => composition.Alias); - var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter); + IEnumerable propertyTypes = _contentTypeService.GetAll() + .Cast() + .Concat(_mediaTypeService.GetAll()) + .ToArray() + .SelectMany(x => x.PropertyTypes) + .DistinctBy(composition => composition.Alias); + IEnumerable filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter); return _umbracoMapper.MapEnumerable(filteredPropertyTypes); case UmbracoEntityTypes.PropertyGroup: //get all document types, then combine all property types into one list - var propertyGroups = _contentTypeService.GetAll().Cast() - .Concat(_mediaTypeService.GetAll()) - .ToArray() - .SelectMany(x => x.PropertyGroups) - .DistinctBy(composition => composition.Name); - var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter); + IEnumerable propertyGroups = _contentTypeService.GetAll() + .Cast() + .Concat(_mediaTypeService.GetAll()) + .ToArray() + .SelectMany(x => x.PropertyGroups) + .DistinctBy(composition => composition.Name); + IEnumerable filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter); return _umbracoMapper.MapEnumerable(filteredpropertyGroups); case UmbracoEntityTypes.User: - var users = _userService.GetAll(0, int.MaxValue, out _); - var filteredUsers = ExecutePostFilter(users, postFilter); + IEnumerable users = _userService.GetAll(0, int.MaxValue, out _); + IEnumerable filteredUsers = ExecutePostFilter(users, postFilter); return _umbracoMapper.MapEnumerable(filteredUsers); case UmbracoEntityTypes.Stylesheet: if (!postFilter.IsNullOrWhiteSpace()) + { throw new NotSupportedException("Filtering on stylesheets is not currently supported"); + } return _fileService.GetStylesheets().Select(MapEntities()); case UmbracoEntityTypes.Script: if (!postFilter.IsNullOrWhiteSpace()) + { throw new NotSupportedException("Filtering on scripts is not currently supported"); + } return _fileService.GetScripts().Select(MapEntities()); case UmbracoEntityTypes.PartialView: if (!postFilter.IsNullOrWhiteSpace()) + { throw new NotSupportedException("Filtering on partial views is not currently supported"); + } return _fileService.GetPartialViews().Select(MapEntities()); case UmbracoEntityTypes.Language: if (!postFilter.IsNullOrWhiteSpace()) + { throw new NotSupportedException("Filtering on languages is not currently supported"); + } return _localizationService.GetAllLanguages().Select(MapEntities()); case UmbracoEntityTypes.DictionaryItem: if (!postFilter.IsNullOrWhiteSpace()) + { throw new NotSupportedException("Filtering on dictionary items is not currently supported"); + } return GetAllDictionaryItems(); default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + throw new NotSupportedException("The " + typeof(EntityController) + + " does not currently support data for the type " + entityType); } } private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter) { - if (postFilter.IsNullOrWhiteSpace()) return entities; + if (postFilter.IsNullOrWhiteSpace()) + { + return entities; + } var postFilterConditions = postFilter.Split(Constants.CharArrays.Ampersand); foreach (var postFilterCondition in postFilterConditions) { - var queryCondition = BuildQueryCondition(postFilterCondition); + QueryCondition queryCondition = BuildQueryCondition(postFilterCondition); if (queryCondition != null) { - var whereClauseExpression = queryCondition.BuildCondition("x"); + Expression> whereClauseExpression = queryCondition.BuildCondition("x"); entities = entities.Where(whereClauseExpression.Compile()); } - } + return entities; } - private static readonly string[] _postFilterSplitStrings = new[] - { - "=", - "==", - "!=", - "<>", - ">", - "<", - ">=", - "<=" - }; - private static QueryCondition BuildQueryCondition(string postFilter) { var postFilterParts = postFilter.Split(_postFilterSplitStrings, 2, StringSplitOptions.RemoveEmptyEntries); @@ -1234,26 +1280,21 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return null; } - var type = typeof(T); - var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); + Type type = typeof(T); + PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); if (property == null) { return null; } - var queryCondition = new QueryCondition() + var queryCondition = new QueryCondition { - Term = new OperatorTerm() - { - Operator = binaryOperator - }, + Term = new OperatorTerm { Operator = binaryOperator }, ConstraintValue = constraintValue, - Property = new PropertyModel() + Property = new PropertyModel { - Alias = propertyName, - Name = propertyName, - Type = property.PropertyType.Name + Alias = propertyName, Name = propertyName, Type = property.PropertyType.Name } }; @@ -1274,14 +1315,141 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private string ClientCulture() => Request.ClientCulture(); + + #region GetById + + /// + /// Gets an entity by it's id + /// + /// + /// + /// + public ActionResult GetById(int id, UmbracoEntityTypes type) => GetResultForId(id, type); + + /// + /// Gets an entity by it's key + /// + /// + /// + /// + public ActionResult GetById(Guid id, UmbracoEntityTypes type) => GetResultForKey(id, type); + + /// + /// Gets an entity by it's UDI + /// + /// + /// + /// + public ActionResult GetById(Udi id, UmbracoEntityTypes type) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetResultForKey(guidUdi.Guid, type); + } + + return NotFound(); + } + + #endregion + + #region GetByIds + + /// + /// Get entities by integer ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public ActionResult> GetByIds([FromJsonPath] int[] ids, + [FromQuery] UmbracoEntityTypes type) + { + if (ids == null) + { + return NotFound(); + } + + return new ActionResult>(GetResultForIds(ids, type)); + } + + /// + /// Get entities by GUID ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public ActionResult> GetByIds([FromJsonPath] Guid[] ids, + [FromQuery] UmbracoEntityTypes type) + { + if (ids == null) + { + return NotFound(); + } + + return new ActionResult>(GetResultForKeys(ids, type)); + } + + /// + /// Get entities by UDIs + /// + /// + /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids. + /// + [HttpGet] + [HttpPost] + public ActionResult> GetByIds([FromJsonPath] Udi[] ids, + [FromQuery] UmbracoEntityTypes type) + { + if (ids == null) + { + return NotFound(); + } + + if (ids.Length == 0) + { + return Enumerable.Empty().ToList(); + } + + //all udi types will need to be the same in this list so we'll determine by the first + //currently we only support GuidUdi for this method + + var guidUdi = ids[0] as GuidUdi; + if (guidUdi != null) + { + return new ActionResult>( + GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type)); + } + + return NotFound(); + } + + #endregion + #region Methods to get all dictionary items + private IEnumerable GetAllDictionaryItems() { var list = new List(); - foreach (var dictionaryItem in _localizationService.GetRootDictionaryItems().OrderBy(DictionaryItemSort())) + foreach (IDictionaryItem dictionaryItem in _localizationService.GetRootDictionaryItems() + .OrderBy(DictionaryItemSort())) { - var item = _umbracoMapper.Map(dictionaryItem); + EntityBasic item = _umbracoMapper.Map(dictionaryItem); list.Add(item); GetChildItemsForList(dictionaryItem, list); } @@ -1293,15 +1461,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private void GetChildItemsForList(IDictionaryItem dictionaryItem, ICollection list) { - foreach (var childItem in _localizationService.GetDictionaryItemChildren(dictionaryItem.Key).OrderBy(DictionaryItemSort())) + foreach (IDictionaryItem childItem in _localizationService.GetDictionaryItemChildren(dictionaryItem.Key) + .OrderBy(DictionaryItemSort())) { - var item = _umbracoMapper.Map(childItem); + EntityBasic item = _umbracoMapper.Map(childItem); list.Add(item); GetChildItemsForList(childItem, list); } } - #endregion + #endregion } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs index de3125ad64..008582b6b3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs @@ -91,12 +91,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return SearchResults.Empty(); } - var pagedResults = results.Skip(pageIndex * pageSize); - return new SearchResults { TotalRecords = results.TotalItemCount, - Results = pagedResults.Select(x => new SearchResult + Results = results.Select(x => new SearchResult { Id = x.Id, Score = x.Score, diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index 4e84bac0fe..2cbf21fc7c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -9,8 +9,6 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Web.BackOffice.Extensions; -using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Extensions; @@ -23,7 +21,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// Backoffice controller supporting the dashboard for language administration. ///
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - //[PrefixlessBodyModelValidator] public class LanguageController : UmbracoAuthorizedJsonController { private readonly ILocalizationService _localizationService; @@ -51,10 +48,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers // (see notes in Language class about culture info names) // TODO: Fix this requirement, see https://github.com/umbraco/Umbraco-CMS/issues/3623 return CultureInfo.GetCultures(CultureTypes.AllCultures) - .Where(x => !x.Name.IsNullOrWhiteSpace()) - .Select(x => new CultureInfo(x.Name)) // important! - .OrderBy(x => x.EnglishName) - .ToDictionary(x => x.Name, x => x.EnglishName); + .Select(x=>x.Name) + .Distinct() + .Where(x => !x.IsNullOrWhiteSpace()) + .Select(x => new CultureInfo(x)) // important! + .OrderBy(x => x.EnglishName) + .ToDictionary(x => x.Name, x => x.EnglishName); } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index f3c2b7d245..a6485768f1 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -858,7 +858,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } - return Ok(); + return Ok(tempFiles); } private IMedia FindInChildren(int mediaId, string nameToFind, string contentTypeAlias) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs index fb5286505e..24f128e410 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs @@ -12,21 +12,20 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.Controllers { /// - /// An API controller used for dealing with member groups + /// An API controller used for dealing with member groups /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberGroups)] [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] public class MemberGroupController : UmbracoAuthorizedJsonController { + private readonly ILocalizedTextService _localizedTextService; private readonly IMemberGroupService _memberGroupService; private readonly IUmbracoMapper _umbracoMapper; - private readonly ILocalizedTextService _localizedTextService; public MemberGroupController( IMemberGroupService memberGroupService, @@ -35,11 +34,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { _memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService)); _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); - _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); + _localizedTextService = + localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); } /// - /// Gets the member group json for the member group id + /// Gets the member group json for the member group id /// /// /// @@ -56,7 +56,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } /// - /// Gets the member group json for the member group guid + /// Gets the member group json for the member group guid /// /// /// @@ -72,7 +72,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } /// - /// Gets the member group json for the member group udi + /// Gets the member group json for the member group udi /// /// /// @@ -100,7 +100,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [HttpPost] public IActionResult DeleteById(int id) { - var memberGroup = _memberGroupService.GetById(id); + IMemberGroup memberGroup = _memberGroupService.GetById(id); if (memberGroup == null) { return NotFound(); @@ -112,7 +112,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public IEnumerable GetAllGroups() => _memberGroupService.GetAll() - .Select(_umbracoMapper.Map); + .Select(_umbracoMapper.Map); public MemberGroupDisplay GetEmpty() { @@ -120,9 +120,24 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return _umbracoMapper.Map(item); } + public bool IsMemberGroupNameUnique(int id, string oldName, string newName) + { + if (newName == oldName) + { + return true; // name hasn't changed + } + + IMemberGroup memberGroup = _memberGroupService.GetByName(newName); + if (memberGroup == null) + { + return true; // no member group found + } + + return memberGroup.Id == id; + } + public ActionResult PostSave(MemberGroupSave saveModel) { - var id = int.Parse(saveModel.Id.ToString(), CultureInfo.InvariantCulture); IMemberGroup memberGroup = id > 0 ? _memberGroupService.GetById(id) : new MemberGroup(); if (memberGroup == null) @@ -130,16 +145,28 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return NotFound(); } - memberGroup.Name = saveModel.Name; - _memberGroupService.Save(memberGroup); + if (IsMemberGroupNameUnique(memberGroup.Id, memberGroup.Name, saveModel.Name)) + { + memberGroup.Name = saveModel.Name; + _memberGroupService.Save(memberGroup); - MemberGroupDisplay display = _umbracoMapper.Map(memberGroup); + MemberGroupDisplay display = _umbracoMapper.Map(memberGroup); - display.AddSuccessNotification( - _localizedTextService.Localize("speechBubbles","memberGroupSavedHeader"), - string.Empty); + display.AddSuccessNotification( + _localizedTextService.Localize("speechBubbles", "memberGroupSavedHeader"), + string.Empty); - return display; + return display; + } + else + { + MemberGroupDisplay display = _umbracoMapper.Map(memberGroup); + display.AddErrorNotification( + _localizedTextService.Localize("speechBubbles", "memberGroupNameDuplicate"), + string.Empty); + + return display; + } } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs index 0a800693f8..23c955219a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Mapping; @@ -12,6 +13,7 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; 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.Controllers @@ -24,15 +26,28 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly IFileService _fileService; private readonly IUmbracoMapper _umbracoMapper; private readonly IShortStringHelper _shortStringHelper; + private readonly IDefaultViewContentProvider _defaultViewContentProvider; + [ActivatorUtilitiesConstructor] public TemplateController( IFileService fileService, IUmbracoMapper umbracoMapper, - IShortStringHelper shortStringHelper) + IShortStringHelper shortStringHelper, + IDefaultViewContentProvider defaultViewContentProvider) { _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService)); _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); _shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper)); + _defaultViewContentProvider = defaultViewContentProvider ?? throw new ArgumentNullException(nameof(defaultViewContentProvider)); + } + + [Obsolete("Use ctor will all params")] + public TemplateController( + IFileService fileService, + IUmbracoMapper umbracoMapper, + IShortStringHelper shortStringHelper) + : this(fileService, umbracoMapper, shortStringHelper, StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -136,10 +151,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } - var content = ViewHelper.GetDefaultFileContent( layoutPageAlias: dt.MasterTemplateAlias ); + var content = _defaultViewContentProvider.GetDefaultFileContent( layoutPageAlias: dt.MasterTemplateAlias ); var scaffold = _umbracoMapper.Map(dt); - scaffold.Content = content + "\r\n\r\n@* the fun starts here *@\r\n\r\n"; + scaffold.Content = content; return scaffold; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 4d8da0641e..db23e3b04c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -525,6 +525,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var user = await _userManager.FindByIdAsync(((int) userDisplay.Id).ToString()); var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); + // Use info from SMTP Settings if configured, otherwise set fromEmail as fallback + var senderEmail = !string.IsNullOrEmpty(_globalSettings.Smtp?.From) ? _globalSettings.Smtp.From : fromEmail; + var inviteToken = string.Format("{0}{1}{2}", (int)userDisplay.Id, WebUtility.UrlEncode("|"), @@ -550,14 +553,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var emailBody = _localizedTextService.Localize("user","inviteEmailCopyFormat", //Ensure the culture of the found user is used for the email! UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings), - new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail }); + new[] { userDisplay.Name, from, message, inviteUri.ToString(), senderEmail }); // This needs to be in the correct mailto format including the name, else // the name cannot be captured in the email sending notification. // i.e. "Some Person" var toMailBoxAddress = new MailboxAddress(to.Name, to.Email); - var mailMessage = new EmailMessage(null /*use info from smtp settings*/, toMailBoxAddress.ToString(), emailSubject, emailBody, true); + var mailMessage = new EmailMessage(senderEmail, toMailBoxAddress.ToString(), emailSubject, emailBody, true); await _emailSender.SendAsync(mailMessage, Constants.Web.EmailTypes.UserInvite, true); } diff --git a/src/Umbraco.Web.BackOffice/PropertyEditors/TagsDataController.cs b/src/Umbraco.Web.BackOffice/PropertyEditors/TagsDataController.cs index 17d015abc8..7f02de4794 100644 --- a/src/Umbraco.Web.BackOffice/PropertyEditors/TagsDataController.cs +++ b/src/Umbraco.Web.BackOffice/PropertyEditors/TagsDataController.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Filters; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -33,6 +34,8 @@ namespace Umbraco.Cms.Web.BackOffice.PropertyEditors /// /// /// + /// + [AllowHttpJsonConfigration] public IEnumerable GetTags(string tagGroup, string culture, string query = null) { if (culture == string.Empty) culture = null; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index 4b970e4b72..ec0d273b56 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -77,7 +77,8 @@ namespace Umbraco.Cms.Web.BackOffice.Security var shouldSignIn = autoLinkOptions.OnExternalLogin(user, loginInfo); if (shouldSignIn == false) { - 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); + LogFailedExternalLogin(loginInfo, user); + return ExternalLoginSignInResult.NotAllowed; } } @@ -192,7 +193,16 @@ namespace Umbraco.Cms.Web.BackOffice.Security return AutoLinkSignInResult.FailedException(ex.Message); } - return await LinkUser(autoLinkUser, loginInfo); + var shouldLinkUser = autoLinkOptions.OnExternalLogin == null || autoLinkOptions.OnExternalLogin(autoLinkUser, loginInfo); + if (shouldLinkUser) + { + return await LinkUser(autoLinkUser, loginInfo); + } + else + { + LogFailedExternalLogin(loginInfo, autoLinkUser); + return ExternalLoginSignInResult.NotAllowed; + } } else { @@ -225,7 +235,16 @@ namespace Umbraco.Cms.Web.BackOffice.Security } else { - return await LinkUser(autoLinkUser, loginInfo); + var shouldLinkUser = autoLinkOptions.OnExternalLogin == null || autoLinkOptions.OnExternalLogin(autoLinkUser, loginInfo); + if (shouldLinkUser) + { + return await LinkUser(autoLinkUser, loginInfo); + } + else + { + LogFailedExternalLogin(loginInfo, autoLinkUser); + return ExternalLoginSignInResult.NotAllowed; + } } } } @@ -264,5 +283,8 @@ namespace Umbraco.Cms.Web.BackOffice.Security return AutoLinkSignInResult.FailedLinkingUser(errors); } } + + 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); } } diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index 1767386088..58a6862300 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -36,7 +36,6 @@ namespace Umbraco.Cms.Web.BackOffice.Security private readonly ISystemClock _systemClock; private readonly UmbracoRequestPaths _umbracoRequestPaths; private readonly IBasicAuthService _basicAuthService; - private readonly IOptionsMonitor _optionsSnapshot; /// /// Initializes a new instance of the class. diff --git a/src/Umbraco.Web.BackOffice/Security/ExternalLoginSignInResult.cs b/src/Umbraco.Web.BackOffice/Security/ExternalLoginSignInResult.cs new file mode 100644 index 0000000000..188961b2ac --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/ExternalLoginSignInResult.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Identity; + +namespace Umbraco.Cms.Web.BackOffice.Security +{ + /// + /// Result returned from signing in when external logins are used. + /// + public class ExternalLoginSignInResult : SignInResult + { + public static ExternalLoginSignInResult NotAllowed { get; } = new ExternalLoginSignInResult() + { + Succeeded = false + }; + } +} diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs index 1bddb12cde..8bf8435703 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs @@ -68,7 +68,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - public ActionResult GetTreeNode([FromRoute] string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) + public ActionResult GetTreeNode([FromRoute] string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) { int asInt; Guid asGuid = Guid.Empty; @@ -325,7 +325,8 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// protected bool HasPathAccess(IUmbracoEntity entity, FormCollection queryStrings) { - if (entity == null) return false; + if (entity == null) + return false; return RecycleBinId == Constants.System.RecycleBinContent ? _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasContentPathAccess(entity, _entityService, _appCaches) : _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasMediaPathAccess(entity, _entityService, _appCaches); @@ -469,13 +470,13 @@ namespace Umbraco.Cms.Web.BackOffice.Trees // only add empty recycle bin if the current user is allowed to delete by default if (deleteAllowed) { - menu.Items.Add(new MenuItem("emptyrecyclebin", LocalizedTextService) - { - Icon = "trash", - OpensDialog = true - }); - menu.Items.Add(new RefreshNode(LocalizedTextService, true)); - } + menu.Items.Add(new MenuItem("emptyRecycleBin", LocalizedTextService) + { + Icon = "trash", + OpensDialog = true + }); + menu.Items.Add(new RefreshNode(LocalizedTextService, true)); + } return menu; } @@ -608,7 +609,8 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// internal bool IgnoreUserStartNodes(FormCollection queryStrings) { - if (_ignoreUserStartNodes.HasValue) return _ignoreUserStartNodes.Value; + if (_ignoreUserStartNodes.HasValue) + return _ignoreUserStartNodes.Value; var dataTypeKey = queryStrings.GetValue(TreeQueryStringParameters.DataTypeKey); _ignoreUserStartNodes = dataTypeKey.HasValue && _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value); diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs index c4112cc77e..ecc5b78a51 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs @@ -76,7 +76,8 @@ namespace Umbraco.Cms.Web.BackOffice.Trees })); //if the request is for folders only then just return - if (queryStrings["foldersonly"].ToString().IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; + if (queryStrings["foldersonly"].ToString().IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") + return nodes; var children = _entityService.GetChildren(intId, UmbracoObjectTypes.DocumentType).ToArray(); var contentTypes = _contentTypeService.GetAll(children.Select(c => c.Id).ToArray()).ToDictionary(c => c.Id); @@ -117,7 +118,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees // root actions menu.Items.Add(LocalizedTextService, opensDialog: true); - menu.Items.Add(new MenuItem("importdocumenttype", LocalizedTextService) + menu.Items.Add(new MenuItem("importDocumentType", LocalizedTextService) { Icon = "page-up", SeparatorBefore = true, diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs b/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs index 37a857f78d..d8d8afe13a 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Trees; @@ -56,5 +57,16 @@ namespace Umbraco.Cms.Web.BackOffice.Trees foreach (var controllerType in controllerTypes) AddTreeController(controllerType); } + + public void RemoveTreeController() => RemoveTreeController(typeof(T)); + + public void RemoveTreeController(Type type) + { + var tree = _trees.FirstOrDefault(it => it.TreeControllerType == type); + if (tree != null) + { + _trees.Remove(tree); + } + } } } diff --git a/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs b/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs index 2ef9a5e4b3..0eb30bcd03 100644 --- a/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs +++ b/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs @@ -44,18 +44,14 @@ namespace Umbraco.Cms.Web.Common.ActionsResults reason = "No template exists to render the document at URL '{0}'."; } - await response.WriteAsync("

Page not found

"); - await response.WriteAsync("

"); - await response.WriteAsync(string.Format(reason, WebUtility.HtmlEncode(_umbracoContext.OriginalRequestUrl.PathAndQuery))); - await response.WriteAsync("

"); - if (string.IsNullOrWhiteSpace(_message) == false) + var viewResult = new ViewResult { - await response.WriteAsync("

" + _message + "

"); - } + ViewName = "~/umbraco/UmbracoWebsite/NotFound.cshtml" + }; + context.HttpContext.Items.Add("reason", string.Format(reason, WebUtility.HtmlEncode(_umbracoContext.OriginalRequestUrl.PathAndQuery))); + context.HttpContext.Items.Add("message", _message); - await response.WriteAsync("

This page can be replaced with a custom 404. Check the documentation for Custom 404 Error Pages.

"); - await response.WriteAsync("

This page is intentionally left ugly ;-)

"); - await response.WriteAsync(""); + await viewResult.ExecuteResultAsync(context); } } } diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs index b80104a7bf..df0584386b 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs @@ -18,6 +18,7 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels /// /// This is nearly a copy of aspnetcore's ApiBehaviorApplicationModelProvider which supplies a convention for the /// [ApiController] attribute, however that convention is too strict for our purposes so we will have our own. + /// Uses UmbracoJsonModelBinder for complex parameters and those with BindingSource of Body, but leaves the rest alone see GH #11554 /// /// /// See https://shazwazza.com/post/custom-body-model-binding-per-controller-in-asp-net-core/ @@ -41,14 +42,12 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels { new ClientErrorResultFilterConvention(), // Ensures the responses without any body is converted into a simple json object with info instead of a string like "Status Code: 404; Not Found" new ConsumesConstraintForFormFileParameterConvention(), // If an controller accepts files, it must accept multipart/form-data. - new InferParameterBindingInfoConvention(modelMetadataProvider), // no need for [FromBody] everywhere, A complex type parameter is assigned to FromBody - // This ensures that all parameters of type BindingSource.Body (based on the above InferParameterBindingInfoConvention) are bound + // This ensures that all parameters of type BindingSource.Body and those of complex type are bound // using our own UmbracoJsonModelBinder - new UmbracoJsonModelBinderConvention() + new UmbracoJsonModelBinderConvention(modelMetadataProvider) }; - // TODO: Need to determine exactly how this affects errors var defaultErrorType = typeof(ProblemDetails); var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute(defaultErrorType); _actionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute)); @@ -68,25 +67,24 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels /// public void OnProvidersExecuting(ApplicationModelProviderContext context) { - foreach (var controller in context.Result.Controllers) + foreach (ControllerModel controller in context.Result.Controllers) { if (!IsUmbracoApiController(controller)) { continue; } - foreach (var action in controller.Actions) + foreach (ActionModel action in controller.Actions) { - foreach (var convention in _actionModelConventions) + foreach (IActionModelConvention convention in _actionModelConventions) { convention.Apply(action); } } - } } - private bool IsUmbracoApiController(ControllerModel controller) + private static bool IsUmbracoApiController(ICommonModel controller) => controller.Attributes.OfType().Any(); } } diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs index e96bda8771..2eff08d54d 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs @@ -1,25 +1,61 @@ -using System.Linq; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.ModelBinders; namespace Umbraco.Cms.Web.Common.ApplicationModels { /// - /// Applies the body model binder to any parameter binding source of type + /// Applies the body model binder to any complex parameter and those with a + /// binding source of type /// - /// - /// For this to work Microsoft's own convention must be executed before this one - /// public class UmbracoJsonModelBinderConvention : IActionModelConvention { + private readonly IModelMetadataProvider _modelMetadataProvider; + + public UmbracoJsonModelBinderConvention() + : this(StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public UmbracoJsonModelBinderConvention(IModelMetadataProvider modelMetadataProvider) + { + _modelMetadataProvider = modelMetadataProvider; + } + /// public void Apply(ActionModel action) { - foreach (ParameterModel p in action.Parameters.Where(p => p.BindingInfo?.BindingSource == BindingSource.Body)) + foreach (ParameterModel p in action.Parameters) { - p.BindingInfo.BinderType = typeof(UmbracoJsonModelBinder); + if (p.BindingInfo == null) + { + if (IsComplexTypeParameter(p)) + { + p.BindingInfo = new BindingInfo + { + BindingSource = BindingSource.Body, + BinderType = typeof(UmbracoJsonModelBinder) + }; + } + + continue; + } + + if (p.BindingInfo.BindingSource == BindingSource.Body) + { + p.BindingInfo.BinderType = typeof(UmbracoJsonModelBinder); + } } } + + private bool IsComplexTypeParameter(ParameterModel parameter) + { + // No need for information from attributes on the parameter. Just use its type. + ModelMetadata metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterInfo.ParameterType); + + return metadata.IsComplexType; + } } } diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs index 19a9074613..9e5919c1e2 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs @@ -115,7 +115,7 @@ namespace Umbraco.Cms.Web.Common.AspNetCore // hopefully it gets a new Guid or new application id? string hashString = SiteName + "::" + ApplicationId; string hash = hashString.GenerateHash(); - string siteTemp = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", hash); + string siteTemp = Path.Combine(Path.GetTempPath(), "UmbracoData", hash); return _localTempPath = siteTemp; diff --git a/src/Umbraco.Web.Common/Authorization/FeatureAuthorizeHandler.cs b/src/Umbraco.Web.Common/Authorization/FeatureAuthorizeHandler.cs index 0a4981d6c6..283accb085 100644 --- a/src/Umbraco.Web.Common/Authorization/FeatureAuthorizeHandler.cs +++ b/src/Umbraco.Web.Common/Authorization/FeatureAuthorizeHandler.cs @@ -47,6 +47,13 @@ namespace Umbraco.Cms.Web.Common.Authorization break; } + case Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext authorizationFilterContext: + { + IEndpointFeature endpointFeature = authorizationFilterContext.HttpContext.Features.Get(); + endpoint = endpointFeature.Endpoint; + break; + } + case Endpoint resourceEndpoint: { endpoint = resourceEndpoint; diff --git a/src/Umbraco.Web.Common/DependencyInjection/ScopedServiceProvider.cs b/src/Umbraco.Web.Common/DependencyInjection/ScopedServiceProvider.cs new file mode 100644 index 0000000000..de660184f1 --- /dev/null +++ b/src/Umbraco.Web.Common/DependencyInjection/ScopedServiceProvider.cs @@ -0,0 +1,17 @@ +using System; +using Microsoft.AspNetCore.Http; +using Umbraco.Cms.Core.DependencyInjection; + +namespace Umbraco.Cms.Web.Common.DependencyInjection +{ + /// + internal class ScopedServiceProvider : IScopedServiceProvider + { + private readonly IHttpContextAccessor _accessor; + + public ScopedServiceProvider(IHttpContextAccessor accessor) => _accessor = accessor; + + /// + public IServiceProvider ServiceProvider => _accessor.HttpContext?.RequestServices; + } +} diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 2d584f198e..fda4d7ca32 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -176,6 +176,7 @@ namespace Umbraco.Extensions builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); + builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); @@ -348,6 +349,7 @@ namespace Umbraco.Extensions builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs index 22ddc15511..c59e7b1126 100644 --- a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs @@ -32,6 +32,15 @@ namespace Umbraco.Extensions UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode); + /// + /// Gets the underlying image processing service URL by the crop alias (from the "umbracoFile" property alias in the MediaWithCrops content item) on the MediaWithCrops item. + /// + /// The MediaWithCrops item. + /// The crop alias e.g. thumbnail. + /// The url mode. + /// + /// The URL of the cropped image. + /// public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string cropAlias, UrlMode urlMode = UrlMode.Default) => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode); @@ -69,6 +78,16 @@ namespace Umbraco.Extensions UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode); + /// + /// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper JSON data on the MediaWithCrops content item. + /// + /// The MediaWithCrops item. + /// The property alias of the property containing the JSON data e.g. umbracoFile. + /// The crop alias e.g. thumbnail. + /// The url mode. + /// + /// The URL of the cropped image. + /// public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string propertyAlias, string cropAlias, UrlMode urlMode = UrlMode.Default) => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode); @@ -126,6 +145,60 @@ namespace Umbraco.Extensions urlMode ); + /// + /// Gets the underlying image processing service URL from the MediaWithCrops item. + /// + /// The MediaWithCrops item. + /// The width of the output image. + /// The height of the output image. + /// Property alias of the property containing the JSON data. + /// The crop alias. + /// Quality percentage of the output image. + /// The image crop mode. + /// The image crop anchor. + /// Use focal point, to generate an output image using the focal point instead of the predefined crop. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. + /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: + /// + /// The url mode. + /// + /// The URL of the cropped image. + /// + public static string GetCropUrl( + this MediaWithCrops mediaWithCrops, + int? width = null, + int? height = null, + string propertyAlias = Cms.Core.Constants.Conventions.Media.File, + string cropAlias = null, + int? quality = null, + ImageCropMode? imageCropMode = null, + ImageCropAnchor? imageCropAnchor = null, + bool preferFocalPoint = false, + bool useCropDimensions = false, + bool cacheBuster = true, + string furtherOptions = null, + UrlMode urlMode = UrlMode.Default) + => mediaWithCrops.GetCropUrl( + ImageUrlGenerator, + PublishedValueFallback, + PublishedUrlProvider, + width, + height, + propertyAlias, + cropAlias, + quality, + imageCropMode, + imageCropAnchor, + preferFocalPoint, + useCropDimensions, + cacheBuster, + furtherOptions, + urlMode + ); + /// /// Gets the underlying image processing service URL from the image path. /// diff --git a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs index a3e96ebebb..f335a81ae7 100644 --- a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs @@ -32,6 +32,18 @@ namespace Umbraco.Extensions IPublishedUrlProvider publishedUrlProvider, UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode); + /// + /// Gets the underlying image processing service URL by the crop alias (from the "umbracoFile" property alias in the MediaWithCrops content item) on the MediaWithCrops item. + /// + /// The MediaWithCrops item. + /// The crop alias e.g. thumbnail. + /// The image URL generator. + /// The published value fallback. + /// The published URL provider. + /// The url mode. + /// + /// The URL of the cropped image. + /// public static string GetCropUrl( this MediaWithCrops mediaWithCrops, string cropAlias, @@ -84,6 +96,19 @@ namespace Umbraco.Extensions IPublishedUrlProvider publishedUrlProvider, UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode); + /// + /// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper JSON data on the MediaWithCrops content item. + /// + /// The MediaWithCrops item. + /// The property alias of the property containing the JSON data e.g. umbracoFile. + /// The crop alias e.g. thumbnail. + /// The image URL generator. + /// The published value fallback. + /// The published URL provider. + /// The url mode. + /// + /// The URL of the cropped image. + /// public static string GetCropUrl(this MediaWithCrops mediaWithCrops, IPublishedValueFallback publishedValueFallback, IPublishedUrlProvider publishedUrlProvider, @@ -135,6 +160,31 @@ namespace Umbraco.Extensions string furtherOptions = null, UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, urlMode); + /// + /// Gets the underlying image processing service URL from the MediaWithCrops item. + /// + /// The MediaWithCrops item. + /// The image URL generator. + /// The published value fallback. + /// The published URL provider. + /// The width of the output image. + /// The height of the output image. + /// Property alias of the property containing the JSON data. + /// The crop alias. + /// Quality percentage of the output image. + /// The image crop mode. + /// The image crop anchor. + /// Use focal point, to generate an output image using the focal point instead of the predefined crop. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. + /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: + /// + /// The url mode. + /// + /// The URL of the cropped image. + /// public static string GetCropUrl( this MediaWithCrops mediaWithCrops, IImageUrlGenerator imageUrlGenerator, diff --git a/src/Umbraco.Web.Common/Filters/AllowHttpJsonConfigrationAttribute.cs b/src/Umbraco.Web.Common/Filters/AllowHttpJsonConfigrationAttribute.cs new file mode 100644 index 0000000000..31fddc65f1 --- /dev/null +++ b/src/Umbraco.Web.Common/Filters/AllowHttpJsonConfigrationAttribute.cs @@ -0,0 +1,42 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using Umbraco.Cms.Web.Common.Formatters; + +namespace Umbraco.Cms.Web.Common.Filters +{ + public class AllowHttpJsonConfigrationAttribute : TypeFilterAttribute + { + /// + /// This filter overwrites AngularJsonOnlyConfigurationAttribute and get the api back to its defualt behavior + /// + public AllowHttpJsonConfigrationAttribute() : base(typeof(AllowJsonXHRConfigrationFilter)) + { + Order = 2; // this value must be more than the AngularJsonOnlyConfigurationAttribute on order to overwrtie it + } + + private class AllowJsonXHRConfigrationFilter : IResultFilter + { + public void OnResultExecuted(ResultExecutedContext context) + { + } + + public void OnResultExecuting(ResultExecutingContext context) + { + if (context.Result is ObjectResult objectResult) + { + objectResult.Formatters.RemoveType(); + } + } + } + } +} diff --git a/src/Umbraco.Web.Common/ModelsBuilder/ModelsBuilderNotificationHandler.cs b/src/Umbraco.Web.Common/ModelsBuilder/ModelsBuilderNotificationHandler.cs index 90a48c4017..ea8408b212 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/ModelsBuilderNotificationHandler.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/ModelsBuilderNotificationHandler.cs @@ -26,15 +26,17 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder private readonly ModelsBuilderSettings _config; private readonly IShortStringHelper _shortStringHelper; private readonly IModelsBuilderDashboardProvider _modelsBuilderDashboardProvider; + private readonly IDefaultViewContentProvider _defaultViewContentProvider; public ModelsBuilderNotificationHandler( IOptions config, IShortStringHelper shortStringHelper, - IModelsBuilderDashboardProvider modelsBuilderDashboardProvider) + IModelsBuilderDashboardProvider modelsBuilderDashboardProvider, IDefaultViewContentProvider defaultViewContentProvider) { _config = config.Value; _shortStringHelper = shortStringHelper; _modelsBuilderDashboardProvider = modelsBuilderDashboardProvider; + _defaultViewContentProvider = defaultViewContentProvider; } ///
@@ -123,7 +125,7 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder // we do not support configuring this at the moment, so just let Umbraco use its default value // var modelNamespaceAlias = ...; - var markup = ViewHelper.GetDefaultFileContent( + var markup = _defaultViewContentProvider.GetDefaultFileContent( modelClassName: className, modelNamespace: modelNamespace/*, modelNamespaceAlias: modelNamespaceAlias*/); diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 00d6103d33..bdcf3460bf 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1,20453 +1,7 @@ { - "name": "Umbraco.Web.UI.Client", - "lockfileVersion": 2, + "name": "ui", "requires": true, - "packages": { - "": { - "dependencies": { - "@microsoft/signalr": "^3.1.8", - "ace-builds": "1.4.2", - "angular": "1.8.0", - "angular-animate": "1.7.5", - "angular-aria": "1.7.9", - "angular-chart.js": "^1.1.1", - "angular-cookies": "1.7.5", - "angular-dynamic-locale": "0.1.37", - "angular-i18n": "1.7.5", - "angular-local-storage": "0.7.1", - "angular-messages": "1.7.5", - "angular-mocks": "1.7.5", - "angular-route": "1.7.5", - "angular-sanitize": "1.7.5", - "angular-touch": "1.7.5", - "angular-ui-sortable": "0.19.0", - "animejs": "2.2.0", - "bootstrap-social": "5.1.1", - "chart.js": "^2.9.3", - "clipboard": "2.0.4", - "diff": "3.5.0", - "flatpickr": "4.6.9", - "font-awesome": "4.7.0", - "jquery": "^3.5.1", - "jquery-ui-dist": "1.12.1", - "jquery-ui-touch-punch": "0.2.3", - "lazyload-js": "1.0.0", - "moment": "2.22.2", - "ng-file-upload": "12.2.13", - "nouislider": "15.4.0", - "npm": "^6.14.7", - "spectrum-colorpicker2": "2.0.8", - "tinymce": "4.9.11", - "typeahead.js": "0.11.1", - "underscore": "1.12.1", - "wicg-inert": "^3.0.2" - }, - "devDependencies": { - "@babel/core": "7.6.4", - "@babel/plugin-proposal-object-rest-spread": "7.13.8", - "@babel/preset-env": "7.6.3", - "autoprefixer": "9.6.5", - "caniuse-lite": "^1.0.30001237", - "cssnano": "4.1.10", - "fs": "0.0.2", - "gulp": "4.0.2", - "gulp-angular-embed-templates": "^2.3.0", - "gulp-babel": "8.0.0", - "gulp-clean-css": "4.2.0", - "gulp-cli": "^2.3.0", - "gulp-concat": "2.6.1", - "gulp-eslint": "6.0.0", - "gulp-imagemin": "7.1.0", - "gulp-less": "4.0.1", - "gulp-minify": "3.1.0", - "gulp-notify": "^3.0.0", - "gulp-postcss": "8.0.0", - "gulp-rename": "1.4.0", - "gulp-sort": "2.0.0", - "gulp-sourcemaps": "^2.6.5", - "gulp-watch": "5.0.1", - "gulp-wrap": "0.15.0", - "gulp-wrap-js": "0.4.1", - "jasmine-core": "3.5.0", - "jsdom": "16.4.0", - "karma": "4.4.1", - "karma-jasmine": "2.0.1", - "karma-jsdom-launcher": "^8.0.2", - "karma-junit-reporter": "2.0.1", - "karma-spec-reporter": "0.0.32", - "less": "3.10.3", - "lodash": "4.17.21", - "marked": "^0.7.0", - "merge-stream": "2.0.0", - "run-sequence": "2.2.1" - }, - "engines": { - "node": ">=10.00.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", - "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.4", - "@babel/helpers": "^7.6.2", - "@babel/parser": "^7.6.4", - "@babel/template": "^7.6.0", - "@babel/traverse": "^7.6.3", - "@babel/types": "^7.6.3", - "convert-source-map": "^1.1.0", - "debug": "^4.1.0", - "json5": "^2.1.0", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz", - "integrity": "sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.15.4.tgz", - "integrity": "sha512-P8o7JP2Mzi0SdC6eWr1zF+AEYvrsZa7GSY1lTayjF5XJhVH0kjLYUZPvTMflP7tBgZoe9gIhTa60QwFpqh/E0Q==", - "dev": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.15.4.tgz", - "integrity": "sha512-J14f/vq8+hdC2KoWLIQSsGrC9EFBKE4NFts8pfMpymfApds+fPqR30AOUWc4tyr56h9l/GA1Sxv2q3dLZWbQ/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", - "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.15.4.tgz", - "integrity": "sha512-v53MxgvMK/HCwckJ1bZrq6dNKlmwlyRNYM6ypaRTdXWGOE2c1/SCa6dL/HimhPulGhZKw9W0QhREM583F/t0vQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-wrap-function": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.15.4.tgz", - "integrity": "sha512-BMRLsdh+D1/aap19TycS4eD1qELGrCBJwzaY9IE8LrpJtJb+H7rQkPIdsfgnMtLBA6DJls7X9z93Z4U8h7xw0A==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.15.4.tgz", - "integrity": "sha512-Y2o+H/hRV5W8QhIfTpRIBwl57y8PrZt6JM3V8FOo5qarjshHItyH5lXlpMfBfmBefOqSCpKZs/6Dxqp0E/U+uw==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz", - "integrity": "sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.13.8", - "@babel/helper-compilation-targets": "^7.13.8", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.13.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread/node_modules/@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread/node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread/node_modules/@babel/plugin-transform-parameters": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.15.4.tgz", - "integrity": "sha512-9WB/GUTO6lvJU3XQsSr6J/WKvBC2hcs4Pew8YxZagi6GkTdniyqp8On5kqdK8MN0LMeu0mGbhPN+O049NV/9FQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.3.tgz", - "integrity": "sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.2.0", - "@babel/plugin-proposal-dynamic-import": "^7.5.0", - "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.6.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.6.2", - "@babel/plugin-syntax-async-generators": "^7.2.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-syntax-json-strings": "^7.2.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", - "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.6.3", - "@babel/plugin-transform-classes": "^7.5.5", - "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.6.0", - "@babel/plugin-transform-dotall-regex": "^7.6.2", - "@babel/plugin-transform-duplicate-keys": "^7.5.0", - "@babel/plugin-transform-exponentiation-operator": "^7.2.0", - "@babel/plugin-transform-for-of": "^7.4.4", - "@babel/plugin-transform-function-name": "^7.4.4", - "@babel/plugin-transform-literals": "^7.2.0", - "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.5.0", - "@babel/plugin-transform-modules-commonjs": "^7.6.0", - "@babel/plugin-transform-modules-systemjs": "^7.5.0", - "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.6.3", - "@babel/plugin-transform-new-target": "^7.4.4", - "@babel/plugin-transform-object-super": "^7.5.5", - "@babel/plugin-transform-parameters": "^7.4.4", - "@babel/plugin-transform-property-literals": "^7.2.0", - "@babel/plugin-transform-regenerator": "^7.4.5", - "@babel/plugin-transform-reserved-words": "^7.2.0", - "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.6.2", - "@babel/plugin-transform-sticky-regex": "^7.2.0", - "@babel/plugin-transform-template-literals": "^7.4.4", - "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.6.2", - "@babel/types": "^7.6.3", - "browserslist": "^4.6.0", - "core-js-compat": "^3.1.1", - "invariant": "^2.2.2", - "js-levenshtein": "^1.1.3", - "semver": "^5.5.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.15.8.tgz", - "integrity": "sha512-2Z5F2R2ibINTc63mY7FLqGfEbmofrHU9FitJW1Q7aPaKFhiPvSq6QEt/BoWN5oME3GVyjcRuNNSRbb9LC0CSWA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.15.4", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", - "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", - "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", - "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", - "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-unicode-property-regex/node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", - "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", - "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", - "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", - "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", - "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-classes": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.15.4.tgz", - "integrity": "sha512-Yjvhex8GzBmmPQUvpXRPWQ9WnxXgAFuZSrqOK/eJlOGIXwvv8H3UEdUigl1gb/bnjTrln+e8bkZUYCBt/xYlBg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", - "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-destructuring": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", - "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", - "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-dotall-regex/node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", - "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", - "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", - "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-for-of": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.15.4.tgz", - "integrity": "sha512-DRTY9fA751AFBDh2oxydvVm4SYevs5ILTWLs6xKXps4Re/KG5nfUkr+TdHCrRWB8C69TlzVgA9b3RmGWmgN9LA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", - "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", - "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", - "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", - "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.4.tgz", - "integrity": "sha512-qg4DPhwG8hKp4BbVDvX1s8cohM8a6Bvptu4l6Iingq5rW+yRUAhe/YRup/YcW2zCOlrysEWVhftIcKzrEZv3sA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-simple-access": "^7.15.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.15.4.tgz", - "integrity": "sha512-fJUnlQrl/mezMneR72CKCgtOoahqGJNVKpompKwzv3BrEXdlPspTcyxrZ1XmDTIr9PpULrgEQo3qNKp6dW7ssw==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.9", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", - "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz", - "integrity": "sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-named-capturing-groups-regex/node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", - "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-new-target": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", - "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-object-super": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", - "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-parameters": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.15.4.tgz", - "integrity": "sha512-9WB/GUTO6lvJU3XQsSr6J/WKvBC2hcs4Pew8YxZagi6GkTdniyqp8On5kqdK8MN0LMeu0mGbhPN+O049NV/9FQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-property-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", - "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-regenerator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", - "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", - "dev": true, - "dependencies": { - "regenerator-transform": "^0.14.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", - "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", - "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-spread": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.15.8.tgz", - "integrity": "sha512-/daZ8s2tNaRekl9YJa9X4bzjpeRZLt122cpgFnQPLGUe61PH8zMEBmYqKkW5xF5JUEh5buEGXJoQpqBmIbpmEQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", - "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-template-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", - "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", - "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", - "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-unicode-regex/node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", - "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@gulp-sourcemaps/identity-map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", - "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", - "dev": true, - "dependencies": { - "acorn": "^5.0.3", - "css": "^2.2.1", - "normalize-path": "^2.1.1", - "source-map": "^0.6.0", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@gulp-sourcemaps/map-sources": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", - "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", - "dev": true, - "dependencies": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@microsoft/signalr": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-3.1.20.tgz", - "integrity": "sha512-K9Ivqd+9vDm4vZlx7dXvpAZ3hPAOdCXkZw+QrelHgPHOdIcF2K6Z6XsskxQZfaUz2aLloaifCQNGzc6VTnsyhA==", - "dependencies": { - "eventsource": "^1.0.7", - "request": "^2.88.0", - "ws": "^6.0.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sindresorhus/is": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@types/angular": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.8.3.tgz", - "integrity": "sha512-vgc5Z+TD07DT7NEUjFm6XMp0kEbGXIa95XmOL5IiHXR9LdrJpcdDh3jl1nCuZbWyzFn5/1OqtMfomcnA1sUFXQ==" - }, - "node_modules/@types/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==", - "dev": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.0.tgz", - "integrity": "sha512-8MLkBIYQMuhRBQzGN9875bYsOhPnf/0rgXGo66S2FemHkhbn9qtsz9ywV1iCG+vbjigE4WUNVvw37Dx+L0qsPg==", - "dev": true - }, - "node_modules/@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==", - "dev": true - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accord": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/accord/-/accord-0.29.0.tgz", - "integrity": "sha512-3OOR92FTc2p5/EcOzPcXp+Cbo+3C15nV9RXHlOUBCBpHhcB+0frbSNR9ehED/o7sTcyGVtqGJpguToEdlXhD0w==", - "dev": true, - "dependencies": { - "convert-source-map": "^1.5.0", - "glob": "^7.0.5", - "indx": "^0.2.3", - "lodash.clone": "^4.3.2", - "lodash.defaults": "^4.0.1", - "lodash.flatten": "^4.2.0", - "lodash.merge": "^4.4.0", - "lodash.partialright": "^4.1.4", - "lodash.pick": "^4.2.1", - "lodash.uniq": "^4.3.0", - "resolve": "^1.5.0", - "semver": "^5.3.0", - "uglify-js": "^2.8.22", - "when": "^3.7.8" - } - }, - "node_modules/ace-builds": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.2.tgz", - "integrity": "sha512-M1JtZctO2Zg+1qeGUFZXtYKsyaRptqQtqpVzlj80I0NzGW9MF3um0DBuizIvQlrPYUlTdm+wcOPZpZoerkxQdA==" - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/align-text/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true, - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/angular": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.0.tgz", - "integrity": "sha512-VdaMx+Qk0Skla7B5gw77a8hzlcOakwF8mjlW13DpIWIDlfqwAbSSLfd8N/qZnzEmQF4jC4iofInd3gE7vL8ZZg==" - }, - "node_modules/angular-animate": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz", - "integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ==" - }, - "node_modules/angular-aria": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.9.tgz", - "integrity": "sha512-luI3Jemd1AbOQW0krdzfEG3fM0IFtLY0bSSqIDEx3POE0XjKIC1MkrO8Csyq9PPgueLphyAPofzUwZ8YeZ88SA==" - }, - "node_modules/angular-chart.js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/angular-chart.js/-/angular-chart.js-1.1.1.tgz", - "integrity": "sha1-SfDhjQgXYrbUyXkeSHr/L7sw9a4=", - "dependencies": { - "angular": "1.x", - "chart.js": "2.3.x" - } - }, - "node_modules/angular-chart.js/node_modules/chart.js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.3.0.tgz", - "integrity": "sha1-QEYOSOLEF8BfwzJc2E97AA3H19Y=", - "dependencies": { - "chartjs-color": "^2.0.0", - "moment": "^2.10.6" - } - }, - "node_modules/angular-cookies": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz", - "integrity": "sha512-/8xvvSl/Z9Vwu8ChRm+OQE3vmli8Icwl8uTYkHqD7j7cknJP9kNaf7SgsENlsLVtOqLE/I7TCFYrSx3bmSeNQA==" - }, - "node_modules/angular-dynamic-locale": { - "version": "0.1.37", - "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.37.tgz", - "integrity": "sha512-m5Kyk8W8/mOZSqRxuByOwHBjv8labLBAgvl0Z3iQx2xT/tWCqb94imKUPwumudszdPDjxeopwyucQvm8Sw7ogw==", - "dependencies": { - "@types/angular": "^1.6.25" - } - }, - "node_modules/angular-i18n": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-i18n/-/angular-i18n-1.7.5.tgz", - "integrity": "sha512-52+Jpt8HRJV2bqSbSU6fWkwOvGzj/DxbNpKXxnTuCS9heuJrlm77BS/lhrF4BA8+Uudnh7npr5/yRELobP+8Yw==" - }, - "node_modules/angular-local-storage": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/angular-local-storage/-/angular-local-storage-0.7.1.tgz", - "integrity": "sha1-+9JzB2PCn6mvVyXgGGx4BiHozdI=" - }, - "node_modules/angular-messages": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.7.5.tgz", - "integrity": "sha512-YDpJpFLyrIgZjE/sIAjgww1y6r3QqXBJbNDI0QjftD37vHXLkwvAOo3A4bxPw8BikyGLcJrFrgf6hRAzntJIWA==" - }, - "node_modules/angular-mocks": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.5.tgz", - "integrity": "sha512-I+Ue2Bkx6R9W5178DYrNvzjIdGh4wKKoCWsgz8dc7ysH4mA70Q3M9v5xRF0RUu7r+2CZj+nDeUecvh2paxcYvg==" - }, - "node_modules/angular-route": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-route/-/angular-route-1.7.5.tgz", - "integrity": "sha512-7KfyEVVOWTI+jTY/j5rUNCIHGRyeCOx7YqZI/Ci3IbDK7GIsy6xH+hS5ai0Xi0sLjzDZ0PUDO4gBn+K0dVtlOg==" - }, - "node_modules/angular-sanitize": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.7.5.tgz", - "integrity": "sha512-wjKCJOIwrkEvfD0keTnKGi6We13gtoCAQIHcdoqyoo3gwvcgNfYymVQIS3+iCGVcjfWz0jHuS3KgB4ysRWsTTA==" - }, - "node_modules/angular-touch": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-touch/-/angular-touch-1.7.5.tgz", - "integrity": "sha512-XNAZNG0RA1mtdwBJheViCF1H/7wOygp4MLIfs5y1K+rne6AeaYKZcV6EJs9fvgfLKLO6ecm1+3J8hoCkdhhxQw==" - }, - "node_modules/angular-ui-sortable": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/angular-ui-sortable/-/angular-ui-sortable-0.19.0.tgz", - "integrity": "sha512-u/uc981Nzg4XN1bMU9qKleMTSt7F1XjMWnyGw6gxPLIeQeLZm8jWNy7tj8y2r2HmvzXFbQVq2z6rObznFKAekQ==", - "dependencies": { - "angular": ">=1.2.x", - "jquery": ">=3.1.x", - "jquery-ui-dist": ">=1.12.x" - } - }, - "node_modules/animejs": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/animejs/-/animejs-2.2.0.tgz", - "integrity": "sha1-Ne79/FNbgZScnLBvCz5gwC5v3IA=" - }, - "node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", - "dev": true, - "dependencies": { - "buffer-equal": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, - "node_modules/archive-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", - "integrity": "sha1-+S5yIzBW38aWlHJ0nCZ72wRrHXA=", - "dev": true, - "optional": true, - "dependencies": { - "file-type": "^4.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/archive-type/node_modules/file-type": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", - "integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" - }, - "node_modules/argh": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/argh/-/argh-0.1.4.tgz", - "integrity": "sha1-PrTWEpc/xrbcbvM49W91nyrFw6Y=", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", - "dev": true, - "dependencies": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "dependencies": { - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "dependencies": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true, - "optional": true - }, - "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "node_modules/async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", - "dev": true, - "dependencies": { - "async-done": "^1.2.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/autoprefixer": { - "version": "9.6.5", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.5.tgz", - "integrity": "sha512-rGd50YV8LgwFQ2WQp4XzOTG69u1qQsXn0amww7tjqV5jJuNazgFKYEVItEBngyyvVITKOg20zr2V+9VsrXJQ2g==", - "dev": true, - "dependencies": { - "browserslist": "^4.7.0", - "caniuse-lite": "^1.0.30000999", - "chalk": "^2.4.2", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.18", - "postcss-value-parser": "^4.0.2" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", - "dev": true, - "dependencies": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, - "node_modules/base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "dev": true, - "dependencies": { - "callsite": "1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/bin-build": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", - "integrity": "sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==", - "dev": true, - "optional": true, - "dependencies": { - "decompress": "^4.0.0", - "download": "^6.2.2", - "execa": "^0.7.0", - "p-map-series": "^1.0.0", - "tempfile": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-build/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "optional": true, - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/bin-build/node_modules/execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "optional": true, - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-build/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-check": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", - "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", - "dev": true, - "optional": true, - "dependencies": { - "execa": "^0.7.0", - "executable": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-check/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "optional": true, - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/bin-check/node_modules/execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "optional": true, - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-check/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", - "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", - "dev": true, - "optional": true, - "dependencies": { - "execa": "^1.0.0", - "find-versions": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/bin-version-check": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", - "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", - "dev": true, - "optional": true, - "dependencies": { - "bin-version": "^3.0.0", - "semver": "^5.6.0", - "semver-truncate": "^1.1.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/bin-wrapper": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", - "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", - "dev": true, - "optional": true, - "dependencies": { - "bin-check": "^4.1.0", - "bin-version-check": "^4.0.0", - "download": "^7.1.0", - "import-lazy": "^3.1.0", - "os-filter-obj": "^2.0.0", - "pify": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/bin-wrapper/node_modules/download": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", - "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", - "dev": true, - "optional": true, - "dependencies": { - "archive-type": "^4.0.0", - "caw": "^2.0.1", - "content-disposition": "^0.5.2", - "decompress": "^4.2.0", - "ext-name": "^5.0.0", - "file-type": "^8.1.0", - "filenamify": "^2.0.0", - "get-stream": "^3.0.0", - "got": "^8.3.1", - "make-dir": "^1.2.0", - "p-event": "^2.1.0", - "pify": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/bin-wrapper/node_modules/download/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-wrapper/node_modules/file-type": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", - "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/bin-wrapper/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-wrapper/node_modules/got": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", - "dev": true, - "optional": true, - "dependencies": { - "@sindresorhus/is": "^0.7.0", - "cacheable-request": "^2.1.1", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "into-stream": "^3.1.0", - "is-retry-allowed": "^1.1.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "mimic-response": "^1.0.0", - "p-cancelable": "^0.4.0", - "p-timeout": "^2.0.1", - "pify": "^3.0.0", - "safe-buffer": "^5.1.1", - "timed-out": "^4.0.1", - "url-parse-lax": "^3.0.0", - "url-to-options": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-wrapper/node_modules/got/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-wrapper/node_modules/make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "optional": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-wrapper/node_modules/make-dir/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-wrapper/node_modules/p-cancelable": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-wrapper/node_modules/p-event": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", - "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", - "dev": true, - "optional": true, - "dependencies": { - "p-timeout": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/bin-wrapper/node_modules/p-timeout": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", - "dev": true, - "optional": true, - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-wrapper/node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/bin-wrapper/node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "optional": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", - "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", - "dev": true, - "optional": true, - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dev": true, - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "node_modules/bootstrap": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", - "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/bootstrap-social": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bootstrap-social/-/bootstrap-social-5.1.1.tgz", - "integrity": "sha1-dTDGeK31bPj60/qCwp1NPl0CdQE=", - "dependencies": { - "bootstrap": "~3", - "font-awesome": "~4.7" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.4.tgz", - "integrity": "sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001265", - "electron-to-chromium": "^1.3.867", - "escalade": "^3.1.1", - "node-releases": "^2.0.0", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true, - "optional": true, - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/bufferstreams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", - "integrity": "sha1-z7GtlWjTujz+k1upq92VLeiKqyo=", - "dev": true, - "dependencies": { - "readable-stream": "^1.0.33" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/bufferstreams/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/bufferstreams/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/bufferstreams/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cacheable-request": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", - "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", - "dev": true, - "optional": true, - "dependencies": { - "clone-response": "1.0.2", - "get-stream": "3.0.0", - "http-cache-semantics": "3.8.1", - "keyv": "3.0.0", - "lowercase-keys": "1.0.0", - "normalize-url": "2.0.1", - "responselike": "1.0.2" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cacheable-request/node_modules/normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", - "dev": true, - "optional": true, - "dependencies": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cacheable-request/node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cacheable-request/node_modules/sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "dev": true, - "optional": true, - "dependencies": { - "is-plain-obj": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "optional": true, - "dependencies": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001267", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001267.tgz", - "integrity": "sha512-r1mjTzAuJ9W8cPBGbbus8E0SKcUP7gn03R14Wk8FlAlqhH9hroy9nLqmpuXlfKEw/oILW+FGz47ipXV2O7x8lg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "node_modules/caw": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", - "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", - "dev": true, - "optional": true, - "dependencies": { - "get-proxy": "^2.0.0", - "isurl": "^1.0.0-alpha5", - "tunnel-agent": "^0.6.0", - "url-to-options": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "dependencies": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/chart.js": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", - "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", - "dependencies": { - "chartjs-color": "^2.1.0", - "moment": "^2.10.2" - } - }, - "node_modules/chartjs-color": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", - "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", - "dependencies": { - "chartjs-color-string": "^0.6.0", - "color-convert": "^1.9.3" - } - }, - "node_modules/chartjs-color-string": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", - "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", - "dependencies": { - "color-name": "^1.0.0" - } - }, - "node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-css": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/clipboard": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", - "dependencies": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "optional": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "node_modules/coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dev": true, - "dependencies": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", - "dev": true, - "dependencies": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-convert/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-string": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", - "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", - "dev": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/colornames": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", - "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=", - "dev": true - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", - "dev": true, - "dependencies": { - "color": "3.0.x", - "text-hex": "1.0.x" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", - "dev": true, - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/concat-with-sourcemaps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/console-stream": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/console-stream/-/console-stream-0.1.1.tgz", - "integrity": "sha1-oJX+B7IEZZVfL6/Si11yvM2UnUQ=", - "dev": true, - "optional": true - }, - "node_modules/consolidate": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", - "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", - "dev": true, - "dependencies": { - "bluebird": "^3.1.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "optional": true, - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", - "dev": true, - "dependencies": { - "each-props": "^1.3.2", - "is-plain-object": "^5.0.0" - } - }, - "node_modules/core-js-compat": { - "version": "3.18.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.18.3.tgz", - "integrity": "sha512-4zP6/y0a2RTHN5bRGT7PTq9lVt3WzvffTNjqnTKsXhkAYNDTkdCLOIfAdOLcQ/7TDdyRj3c+NeHe1NmF1eDScw==", - "dev": true, - "dependencies": { - "browserslist": "^4.17.3", - "semver": "7.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - } - }, - "node_modules/css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - }, - "engines": { - "node": ">4" - } - }, - "node_modules/css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "node_modules/css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", - "dev": true - }, - "node_modules/css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", - "dev": true, - "dependencies": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-preset-default": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz", - "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==", - "dev": true, - "dependencies": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.3", - "postcss-unique-selectors": "^4.0.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dev": true, - "dependencies": { - "css-tree": "^1.1.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "dev": true - }, - "node_modules/csso/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "optional": true, - "dependencies": { - "array-find-index": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug-fabulous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", - "dev": true, - "dependencies": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" - } - }, - "node_modules/debug-fabulous/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/decompress": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", - "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", - "dev": true, - "optional": true, - "dependencies": { - "decompress-tar": "^4.0.0", - "decompress-tarbz2": "^4.0.0", - "decompress-targz": "^4.0.0", - "decompress-unzip": "^4.0.1", - "graceful-fs": "^4.1.10", - "make-dir": "^1.0.0", - "pify": "^2.3.0", - "strip-dirs": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "optional": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tar": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", - "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-type": "^5.2.0", - "is-stream": "^1.1.0", - "tar-stream": "^1.5.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tar/node_modules/file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tarbz2": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", - "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", - "dev": true, - "optional": true, - "dependencies": { - "decompress-tar": "^4.1.0", - "file-type": "^6.1.0", - "is-stream": "^1.1.0", - "seek-bzip": "^1.0.5", - "unbzip2-stream": "^1.0.9" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tarbz2/node_modules/file-type": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-targz": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", - "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", - "dev": true, - "optional": true, - "dependencies": { - "decompress-tar": "^4.1.1", - "file-type": "^5.2.0", - "is-stream": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-targz/node_modules/file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-unzip": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", - "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", - "dev": true, - "optional": true, - "dependencies": { - "file-type": "^3.8.0", - "get-stream": "^2.2.0", - "pify": "^2.3.0", - "yauzl": "^2.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-unzip/node_modules/file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decompress-unzip/node_modules/get-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", - "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", - "dev": true, - "optional": true, - "dependencies": { - "object-assign": "^4.0.1", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decompress-unzip/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decompress/node_modules/make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "optional": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress/node_modules/make-dir/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "dependencies": { - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "node_modules/diagnostics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", - "dev": true, - "dependencies": { - "colorspace": "1.1.x", - "enabled": "1.0.x", - "kuler": "1.0.x" - } - }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "dev": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/dom-serializer/node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "dependencies": { - "domelementtype": "1" - } - }, - "node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/download": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/download/-/download-6.2.5.tgz", - "integrity": "sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==", - "dev": true, - "optional": true, - "dependencies": { - "caw": "^2.0.0", - "content-disposition": "^0.5.2", - "decompress": "^4.0.0", - "ext-name": "^5.0.0", - "file-type": "5.2.0", - "filenamify": "^2.0.0", - "get-stream": "^3.0.0", - "got": "^7.0.0", - "make-dir": "^1.0.0", - "p-event": "^1.0.0", - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/download/node_modules/file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/download/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/download/node_modules/make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "optional": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/download/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "dependencies": { - "readable-stream": "~1.1.9" - } - }, - "node_modules/duplexer2/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/duplexer2/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true, - "optional": true - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, - "node_modules/each-props/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.3.870", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.870.tgz", - "integrity": "sha512-PiJMshfq6PL+i1V+nKLwhHbCKeD8eAz8rvO9Cwk/7cChOHJBtufmjajLyYLsSRHguRFiOCVx3XzJLeZsIAYfSA==", - "dev": true - }, - "node_modules/emits": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emits/-/emits-3.0.0.tgz", - "integrity": "sha1-MnUrupXhcHshlWI4Srm7ix/WL3A=", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "dev": true, - "dependencies": { - "env-variable": "0.0.x" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", - "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.0", - "ws": "~3.3.1" - } - }, - "node_modules/engine.io-client": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", - "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", - "dev": true, - "dependencies": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.1", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "~3.3.1", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - } - }, - "node_modules/engine.io-client/node_modules/component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/engine.io-client/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/engine.io-client/node_modules/ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - }, - "node_modules/engine.io-parser": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", - "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", - "dev": true, - "dependencies": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/engine.io/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/engine.io/node_modules/ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "node_modules/env-variable": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", - "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==", - "dev": true - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", - "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dev": true, - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "node_modules/es5-ext/node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "dependencies": { - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estemplate": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/estemplate/-/estemplate-0.5.1.tgz", - "integrity": "sha1-FxSp1GGQc4rJWLyv1J4CnNpWo54=", - "dev": true, - "dependencies": { - "esprima": "^2.7.2", - "estraverse": "^4.1.1" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", - "dependencies": { - "original": "^1.0.0" - }, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/exec-buffer": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", - "dev": true, - "optional": true, - "dependencies": { - "execa": "^0.7.0", - "p-finally": "^1.0.0", - "pify": "^3.0.0", - "rimraf": "^2.5.4", - "tempfile": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/exec-buffer/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "optional": true, - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/exec-buffer/node_modules/execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "optional": true, - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/exec-buffer/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/exec-buffer/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "optional": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "optional": true, - "dependencies": { - "pify": "^2.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/executable/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "dependencies": { - "fill-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-range/node_modules/fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "dependencies": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-range/node_modules/is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-range/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-range/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", - "dev": true, - "dependencies": { - "type": "^2.5.0" - } - }, - "node_modules/ext-list": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", - "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", - "dev": true, - "optional": true, - "dependencies": { - "mime-db": "^1.28.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", - "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", - "dev": true, - "optional": true, - "dependencies": { - "ext-list": "^2.0.0", - "sort-keys-length": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", - "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", - "dev": true - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-glob/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/fast-glob/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/fast-glob/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fast-xml-parser": { - "version": "3.20.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.20.3.tgz", - "integrity": "sha512-FfHJ/QCpo4K2gquBX7dIAcmShSBG4dMtYJ3ghSiR4w7YqlUujuamrM57C+mKLNWS3mvZzmm2B2Qx8Q6Gfw+lDQ==", - "dev": true, - "optional": true, - "dependencies": { - "strnum": "^1.0.4" - }, - "bin": { - "xml2js": "cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, - "optional": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "dependencies": { - "flat-cache": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/file-type": { - "version": "12.4.2", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", - "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "node_modules/filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/filenamify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", - "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", - "dev": true, - "optional": true, - "dependencies": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.0", - "trim-repeated": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-versions": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", - "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", - "dev": true, - "optional": true, - "dependencies": { - "semver-regex": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fined/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/first-chunk-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", - "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "dependencies": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/flatpickr": { - "version": "4.6.9", - "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz", - "integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw==" - }, - "node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=", - "engines": { - "node": ">=0.10.3" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "optional": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fs": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.2.tgz", - "integrity": "sha1-4fJE7zkzwbKmS9R5kTYGDQ9ZFPg=", - "dev": true - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, - "optional": true - }, - "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fs-readfile-promise": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-3.0.1.tgz", - "integrity": "sha512-LsSxMeaJdYH27XrW7Dmq0Gx63mioULCRel63B5VeELYLavi1wF5s0XfsIdKDFdCL9hsfQ2qBvXJszQtQJ9h17A==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proxy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", - "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", - "dev": true, - "optional": true, - "dependencies": { - "npm-conf": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "optional": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/gifsicle": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/gifsicle/-/gifsicle-5.2.1.tgz", - "integrity": "sha512-9ewIQQCAnSmkU2DhuWafd1DdsgzAkKqIWnY+023xBLSiK9Az2TDUozWQW+SyRQgFMclbe6RQldUk/49TRO3Aqw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "bin-build": "^3.0.0", - "bin-wrapper": "^4.0.0", - "execa": "^5.0.0" - }, - "bin": { - "gifsicle": "cli.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/imagemin/gisicle-bin?sponsor=1" - } - }, - "node_modules/gifsicle/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "optional": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/gifsicle/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "optional": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/gifsicle/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gifsicle/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gifsicle/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "optional": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gifsicle/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gifsicle/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "optional": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gifsicle/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gifsicle/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "optional": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "dependencies": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-base/node_modules/glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "dependencies": { - "is-glob": "^2.0.0" - } - }, - "node_modules/glob-base/node_modules/is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-base/node_modules/is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", - "dev": true, - "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/globby/node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/globby/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", - "dependencies": { - "delegate": "^3.1.2" - } - }, - "node_modules/got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", - "dev": true, - "optional": true, - "dependencies": { - "decompress-response": "^3.2.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/got/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true - }, - "node_modules/gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "dev": true, - "dependencies": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-angular-embed-templates": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-angular-embed-templates/-/gulp-angular-embed-templates-2.3.0.tgz", - "integrity": "sha1-wBDv3VlN7pRRMoNFN9eSOt6EDXk=", - "dev": true, - "dependencies": { - "gulp-util": "^3.0.6", - "htmlparser2": "~3.9.1", - "minimize": "^2.0.0", - "object-assign": "4.1.0", - "through2": "^2.0.1" - } - }, - "node_modules/gulp-babel": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", - "integrity": "sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ==", - "dev": true, - "dependencies": { - "plugin-error": "^1.0.1", - "replace-ext": "^1.0.0", - "through2": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/gulp-clean-css": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-4.2.0.tgz", - "integrity": "sha512-r4zQsSOAK2UYUL/ipkAVCTRg/2CLZ2A+oPVORopBximRksJ6qy3EX1KGrIWT4ZrHxz3Hlobb1yyJtqiut7DNjA==", - "dev": true, - "dependencies": { - "clean-css": "4.2.1", - "plugin-error": "1.0.1", - "through2": "3.0.1", - "vinyl-sourcemaps-apply": "0.2.1" - } - }, - "node_modules/gulp-clean-css/node_modules/through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "dependencies": { - "readable-stream": "2 || 3" - } - }, - "node_modules/gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-concat": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", - "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", - "dev": true, - "dependencies": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-eslint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", - "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", - "dev": true, - "dependencies": { - "eslint": "^6.0.0", - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1" - } - }, - "node_modules/gulp-imagemin": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/gulp-imagemin/-/gulp-imagemin-7.1.0.tgz", - "integrity": "sha512-6xBTNybmPY2YrvrhhlS8Mxi0zn0ypusLon63p9XXxDtIf7U7c6KcViz94K7Skosucr3378A6IY2kJSjJyuwylQ==", - "dev": true, - "dependencies": { - "chalk": "^3.0.0", - "fancy-log": "^1.3.2", - "imagemin": "^7.0.0", - "plugin-error": "^1.0.1", - "plur": "^3.0.1", - "pretty-bytes": "^5.3.0", - "through2-concurrent": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - }, - "optionalDependencies": { - "imagemin-gifsicle": "^7.0.0", - "imagemin-mozjpeg": "^8.0.0", - "imagemin-optipng": "^7.0.0", - "imagemin-svgo": "^7.0.0" - }, - "peerDependencies": { - "gulp": ">=4" - }, - "peerDependenciesMeta": { - "gulp": { - "optional": true - } - } - }, - "node_modules/gulp-imagemin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/gulp-imagemin/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-imagemin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/gulp-imagemin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-imagemin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-less": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz", - "integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==", - "dev": true, - "dependencies": { - "accord": "^0.29.0", - "less": "2.6.x || ^3.7.1", - "object-assign": "^4.0.1", - "plugin-error": "^0.1.2", - "replace-ext": "^1.0.0", - "through2": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-minify": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/gulp-minify/-/gulp-minify-3.1.0.tgz", - "integrity": "sha512-ixF41aYg+NQikI8hpoHdEclYcQkbGdXQu1CBdHaU7Epg8H6e8d2jWXw1+rBPgYwl/XpKgjHj7NI6gkhoSNSSAg==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "minimatch": "^3.0.2", - "plugin-error": "^0.1.2", - "terser": "^3.7.6", - "through2": "^2.0.3", - "vinyl": "^2.1.0" - } - }, - "node_modules/gulp-minify/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-minify/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-minify/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-minify/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-minify/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-minify/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gulp-notify/-/gulp-notify-3.2.0.tgz", - "integrity": "sha512-qEocs1UVoDKKUjfsxJNMNwkRla0PbsyJwsqNNXpzYWsLQ29LhxRMY3wnTGZcc4hMHtalnvah/Dwlwb4NijH/0A==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "fancy-log": "^1.3.2", - "lodash.template": "^4.4.0", - "node-notifier": "^5.2.1", - "node.extend": "^2.0.0", - "plugin-error": "^0.1.2", - "through2": "^2.0.3" - }, - "engines": { - "node": ">=0.8.0", - "npm": ">=1.2.10" - } - }, - "node_modules/gulp-notify/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-postcss": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-8.0.0.tgz", - "integrity": "sha512-Wtl6vH7a+8IS/fU5W9IbOpcaLqKxd5L1DUOzaPmlnCbX1CrG0aWdwVnC3Spn8th0m8D59YbysV5zPUe1n/GJYg==", - "dev": true, - "dependencies": { - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1", - "postcss": "^7.0.2", - "postcss-load-config": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.1" - } - }, - "node_modules/gulp-rename": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", - "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-sort": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-sort/-/gulp-sort-2.0.0.tgz", - "integrity": "sha1-xnYqLx8N4KP8WVohWZ0/rI26Gso=", - "dev": true, - "dependencies": { - "through2": "^2.0.1" - } - }, - "node_modules/gulp-sourcemaps": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", - "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", - "dev": true, - "dependencies": { - "@gulp-sourcemaps/identity-map": "1.X", - "@gulp-sourcemaps/map-sources": "1.X", - "acorn": "5.X", - "convert-source-map": "1.X", - "css": "2.X", - "debug-fabulous": "1.X", - "detect-newline": "2.X", - "graceful-fs": "4.X", - "source-map": "~0.6.0", - "strip-bom-string": "1.X", - "through2": "2.X" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-sourcemaps/node_modules/acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/gulp-sourcemaps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", - "dev": true, - "dependencies": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/gulp-util/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/gulp-util/node_modules/clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "node_modules/gulp-util/node_modules/lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "dependencies": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gulp-util/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/gulp-util/node_modules/vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true, - "dependencies": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - }, - "engines": { - "node": ">= 0.9" - } - }, - "node_modules/gulp-watch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gulp-watch/-/gulp-watch-5.0.1.tgz", - "integrity": "sha512-HnTSBdzAOFIT4wmXYPDUn783TaYAq9bpaN05vuZNP5eni3z3aRx0NAKbjhhMYtcq76x4R1wf4oORDGdlrEjuog==", - "dev": true, - "dependencies": { - "ansi-colors": "1.1.0", - "anymatch": "^1.3.0", - "chokidar": "^2.0.0", - "fancy-log": "1.3.2", - "glob-parent": "^3.0.1", - "object-assign": "^4.1.0", - "path-is-absolute": "^1.0.1", - "plugin-error": "1.0.1", - "readable-stream": "^2.2.2", - "slash": "^1.0.0", - "vinyl": "^2.1.0", - "vinyl-file": "^2.0.0" - } - }, - "node_modules/gulp-watch/node_modules/anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "dev": true, - "dependencies": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" - } - }, - "node_modules/gulp-watch/node_modules/arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-watch/node_modules/array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-watch/node_modules/braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "dependencies": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-watch/node_modules/expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "dependencies": { - "is-posix-bracket": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-watch/node_modules/extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-watch/node_modules/fancy-log": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", - "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", - "dev": true, - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-watch/node_modules/is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-watch/node_modules/is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-watch/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-watch/node_modules/micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "dependencies": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-watch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-wrap": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/gulp-wrap/-/gulp-wrap-0.15.0.tgz", - "integrity": "sha512-f17zkGObA+hE/FThlg55gfA0nsXbdmHK1WqzjjB2Ytq1TuhLR7JiCBJ3K4AlMzCyoFaCjfowos+VkToUNE0WTQ==", - "dev": true, - "dependencies": { - "consolidate": "^0.15.1", - "es6-promise": "^4.2.6", - "fs-readfile-promise": "^3.0.1", - "js-yaml": "^3.13.0", - "lodash": "^4.17.11", - "node.extend": "2.0.2", - "plugin-error": "^1.0.1", - "through2": "^3.0.1", - "tryit": "^1.0.1", - "vinyl-bufferstream": "^1.0.1" - }, - "engines": { - "node": ">=6.14", - "npm": ">=1.4.3" - } - }, - "node_modules/gulp-wrap-js": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/gulp-wrap-js/-/gulp-wrap-js-0.4.1.tgz", - "integrity": "sha1-3uYqpISqupVHqT0f9c0MPQvtwDE=", - "dev": true, - "dependencies": { - "escodegen": "^1.6.1", - "esprima": "^2.3.0", - "estemplate": "*", - "gulp-util": "~3.0.5", - "through2": "*", - "vinyl-sourcemaps-apply": "^0.1.4" - }, - "engines": { - "node": ">=0.8.0", - "npm": ">=1.2.10" - } - }, - "node_modules/gulp-wrap-js/node_modules/source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "dev": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/gulp-wrap-js/node_modules/vinyl-sourcemaps-apply": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.1.4.tgz", - "integrity": "sha1-xfy9Q+LyOEI8LcmL3db3m3K8NFs=", - "dev": true, - "dependencies": { - "source-map": "^0.1.39" - } - }, - "node_modules/gulp-wrap/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "node_modules/gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true, - "dependencies": { - "glogg": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "dev": true, - "dependencies": { - "isarray": "2.0.1" - } - }, - "node_modules/has-binary2/node_modules/isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "node_modules/has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "dev": true, - "optional": true, - "engines": { - "node": "*" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "dev": true, - "optional": true, - "dependencies": { - "has-symbol-support-x": "^1.4.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", - "dev": true - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", - "dev": true - }, - "node_modules/hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", - "dev": true - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/htmlparser2": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", - "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", - "dev": true, - "dependencies": { - "domelementtype": "^1.3.0", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", - "dev": true, - "optional": true - }, - "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/imagemin": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.1.tgz", - "integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==", - "dev": true, - "dependencies": { - "file-type": "^12.0.0", - "globby": "^10.0.0", - "graceful-fs": "^4.2.2", - "junk": "^3.1.0", - "make-dir": "^3.0.0", - "p-pipe": "^3.0.0", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imagemin-gifsicle": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/imagemin-gifsicle/-/imagemin-gifsicle-7.0.0.tgz", - "integrity": "sha512-LaP38xhxAwS3W8PFh4y5iQ6feoTSF+dTAXFRUEYQWYst6Xd+9L/iPk34QGgK/VO/objmIlmq9TStGfVY2IcHIA==", - "dev": true, - "optional": true, - "dependencies": { - "execa": "^1.0.0", - "gifsicle": "^5.0.0", - "is-gif": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/imagemin/imagemin-gifsicle?sponsor=1" - } - }, - "node_modules/imagemin-mozjpeg": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/imagemin-mozjpeg/-/imagemin-mozjpeg-8.0.0.tgz", - "integrity": "sha512-+EciPiIjCb8JWjQNr1q8sYWYf7GDCNDxPYnkD11TNIjjWNzaV+oTg4DpOPQjl5ZX/KRCPMEgS79zLYAQzLitIA==", - "dev": true, - "optional": true, - "dependencies": { - "execa": "^1.0.0", - "is-jpg": "^2.0.0", - "mozjpeg": "^6.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/imagemin-optipng": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/imagemin-optipng/-/imagemin-optipng-7.1.0.tgz", - "integrity": "sha512-JNORTZ6j6untH7e5gF4aWdhDCxe3ODsSLKs/f7Grewy3ebZpl1ZsU+VUTPY4rzeHgaFA8GSWOoA8V2M3OixWZQ==", - "dev": true, - "optional": true, - "dependencies": { - "exec-buffer": "^3.0.0", - "is-png": "^2.0.0", - "optipng-bin": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imagemin-svgo": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/imagemin-svgo/-/imagemin-svgo-7.1.0.tgz", - "integrity": "sha512-0JlIZNWP0Luasn1HT82uB9nU9aa+vUj6kpT+MjPW11LbprXC+iC4HDwn1r4Q2/91qj4iy9tRZNsFySMlEpLdpg==", - "dev": true, - "optional": true, - "dependencies": { - "is-svg": "^4.2.1", - "svgo": "^1.3.2" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sindresorhus/imagemin-svgo?sponsor=1" - } - }, - "node_modules/import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "dev": true, - "dependencies": { - "import-from": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dev": true, - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "dev": true, - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-lazy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", - "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "optional": true, - "dependencies": { - "repeating": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "node_modules/indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "node_modules/indx": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/indx/-/indx-0.2.3.tgz", - "integrity": "sha1-Fdz1bunPZcAjTFE8J/vVgOcPvFA=", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/into-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", - "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", - "dev": true, - "optional": true, - "dependencies": { - "from2": "^2.1.1", - "p-is-promise": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/irregular-plurals": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", - "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "dev": true, - "dependencies": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "node_modules/is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "dependencies": { - "is-primitive": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-gif": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-gif/-/is-gif-3.0.0.tgz", - "integrity": "sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==", - "dev": true, - "optional": true, - "dependencies": { - "file-type": "^10.4.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-gif/node_modules/file-type": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", - "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-jpg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-jpg/-/is-jpg-2.0.0.tgz", - "integrity": "sha1-LhmX+m6RZuqsAkLarkQ0A+TvHZc=", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-natural-number": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", - "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", - "dev": true, - "optional": true - }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true, - "optional": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-png": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-png/-/is-png-2.0.0.tgz", - "integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "node_modules/is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-svg": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.3.1.tgz", - "integrity": "sha512-h2CGs+yPUyvkgTJQS9cJzo9lYK06WgRiXUqBBHtglSzVKAuH4/oWsqk7LGfbSa1hGk9QcZ0SyQtVggvBA8LZXA==", - "dev": true, - "optional": true, - "dependencies": { - "fast-xml-parser": "^3.19.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "node_modules/is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/isbinaryfile": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", - "dev": true, - "dependencies": { - "buffer-alloc": "^1.2.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "node_modules/isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "dev": true, - "optional": true, - "dependencies": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/jasmine-core": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", - "dev": true - }, - "node_modules/jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" - }, - "node_modules/jquery-ui-dist": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.12.1.tgz", - "integrity": "sha1-XAgV08xvkP9fqvWyaKbiO0ypBPo=" - }, - "node_modules/jquery-ui-touch-punch": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/jquery-ui-touch-punch/-/jquery-ui-touch-punch-0.2.3.tgz", - "integrity": "sha1-7tgiQnM7okP0az6HwYQbMIGR2mg=" - }, - "node_modules/js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js-yaml/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "node_modules/jsdom": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", - "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "acorn": "^7.1.1", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.2.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.0", - "domexception": "^2.0.1", - "escodegen": "^1.14.1", - "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", - "nwsapi": "^2.2.0", - "parse5": "5.1.1", - "request": "^2.88.2", - "request-promise-native": "^1.0.8", - "saxes": "^5.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0", - "ws": "^7.2.3", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true, - "optional": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "node_modules/junk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/just-debounce": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", - "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", - "dev": true - }, - "node_modules/karma": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-4.4.1.tgz", - "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", - "dev": true, - "dependencies": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "braces": "^3.0.2", - "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "flatted": "^2.0.0", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^4.17.14", - "log4js": "^4.0.0", - "mime": "^2.3.1", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", - "socket.io": "2.1.1", - "source-map": "^0.6.1", - "tmp": "0.0.33", - "useragent": "2.3.0" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/karma-jasmine": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", - "dev": true, - "dependencies": { - "jasmine-core": "^3.3" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "karma": "*" - } - }, - "node_modules/karma-jsdom-launcher": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/karma-jsdom-launcher/-/karma-jsdom-launcher-8.0.2.tgz", - "integrity": "sha512-jxO+Nf9U/XNSnHXrNpxBbbMyeYuvJH1V++bRdZv20vJ9pvaLuQ6LFNIgn4hA1WAVmzMsvW9j0P2Q2hTLMWcSvw==", - "dev": true, - "peerDependencies": { - "jsdom": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", - "karma": "^1.4.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" - } - }, - "node_modules/karma-junit-reporter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", - "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", - "dev": true, - "dependencies": { - "path-is-absolute": "^1.0.0", - "xmlbuilder": "12.0.0" - }, - "engines": { - "node": ">= 8" - }, - "peerDependencies": { - "karma": ">=0.9" - } - }, - "node_modules/karma-spec-reporter": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.32.tgz", - "integrity": "sha1-LpxyB+pyZ3EmAln4K+y1QyCeRAo=", - "dev": true, - "dependencies": { - "colors": "^1.1.2" - }, - "peerDependencies": { - "karma": ">=0.9" - } - }, - "node_modules/karma/node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/karma/node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/karma/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/karma/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/karma/node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/karma/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/karma/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/karma/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/keyv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", - "dev": true, - "optional": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "dev": true, - "dependencies": { - "colornames": "^1.1.1" - } - }, - "node_modules/last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", - "dev": true, - "dependencies": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lazyload-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazyload-js/-/lazyload-js-1.0.0.tgz", - "integrity": "sha1-jBA5sbaRec1J/cMkICOvSM4IOSU=" - }, - "node_modules/lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", - "dev": true, - "dependencies": { - "flush-write-stream": "^1.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/less": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", - "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", - "dev": true, - "dependencies": { - "clone": "^2.1.2" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.0", - "source-map": "~0.6.0" - } - }, - "node_modules/less/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/less/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/liftoff/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "node_modules/lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "node_modules/lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "node_modules/lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "node_modules/lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "node_modules/lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "node_modules/lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "node_modules/lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "node_modules/lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "node_modules/lodash.clone": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", - "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", - "dev": true - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true - }, - "node_modules/lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "dependencies": { - "lodash._root": "^3.0.0" - } - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "node_modules/lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "node_modules/lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "dependencies": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.partialright": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.partialright/-/lodash.partialright-4.2.1.tgz", - "integrity": "sha1-ATDYDoM2MmTUAHTzKbij56ihzEs=", - "dev": true - }, - "node_modules/lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", - "dev": true - }, - "node_modules/lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" - }, - "node_modules/lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "dev": true, - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "node_modules/lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "dev": true, - "dependencies": { - "lodash._reinterpolate": "^3.0.0" - } - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "node_modules/log4js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", - "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", - "dev": true, - "dependencies": { - "date-format": "^2.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.0", - "rfdc": "^1.1.4", - "streamroller": "^1.0.6" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/logalot": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/logalot/-/logalot-2.1.0.tgz", - "integrity": "sha1-X46MkNME7fElMJUaVVSruMXj9VI=", - "dev": true, - "optional": true, - "dependencies": { - "figures": "^1.3.5", - "squeak": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/logalot/node_modules/figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "optional": true, - "dependencies": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "optional": true, - "dependencies": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lpad-align": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/lpad-align/-/lpad-align-1.1.2.tgz", - "integrity": "sha1-IfYArBwwlcPG5JfuZyce4ISB/p4=", - "dev": true, - "optional": true, - "dependencies": { - "get-stdin": "^4.0.1", - "indent-string": "^2.1.0", - "longest": "^1.0.0", - "meow": "^3.3.0" - }, - "bin": { - "lpad-align": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dev": true, - "dependencies": { - "es5-ext": "~0.10.2" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/make-iterator/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", - "dev": true, - "bin": { - "marked": "bin/marked" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", - "dev": true, - "dependencies": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/matchdep/node_modules/findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/matchdep/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "node_modules/mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", - "dev": true - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" - } - }, - "node_modules/meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "optional": true, - "dependencies": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", - "dependencies": { - "mime-db": "1.50.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "node_modules/minimize": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/minimize/-/minimize-2.2.0.tgz", - "integrity": "sha512-IxR2XMbw9pXCxApkdD9BTcH2U4XlXhbeySUrv71rmMS9XDA8BVXEsIuFu24LtwCfBgfbL7Fuh8/ZzkO5DaTLlQ==", - "dev": true, - "dependencies": { - "argh": "^0.1.4", - "async": "^2.1.5", - "cli-color": "^1.2.0", - "diagnostics": "^1.1.0", - "emits": "^3.0.0", - "htmlparser2": "^3.9.2", - "uuid": "^3.0.0" - }, - "bin": { - "minimize": "bin/minimize" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=", - "engines": { - "node": "*" - } - }, - "node_modules/mozjpeg": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/mozjpeg/-/mozjpeg-6.0.1.tgz", - "integrity": "sha512-9Z59pJMi8ni+IUvSH5xQwK5tNLw7p3dwDNCZ3o1xE+of3G5Hc/yOz6Ue/YuLiBXU3ZB5oaHPURyPdqfBX/QYJA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "bin-build": "^3.0.0", - "bin-wrapper": "^4.0.0", - "logalot": "^2.1.0" - }, - "bin": { - "mozjpeg": "cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "dependencies": { - "duplexer2": "0.0.2" - } - }, - "node_modules/mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "dev": true, - "optional": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, - "node_modules/ng-file-upload": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/ng-file-upload/-/ng-file-upload-12.2.13.tgz", - "integrity": "sha1-AYAPOHLlJvlTEPhHfpnk8S0NjRQ=" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-notifier": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz", - "integrity": "sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==", - "dev": true, - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.0.tgz", - "integrity": "sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA==", - "dev": true - }, - "node_modules/node.extend": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", - "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3", - "is": "^3.2.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/nouislider": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.4.0.tgz", - "integrity": "sha512-AV7UMhGhZ4Mj6ToMT812Ib8OJ4tAXR2/Um7C4l4ZvvsqujF0WpQTpqqHJ+9xt4174R7ueQOUrBR4yakJpAIPCA==" - }, - "node_modules/now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "dependencies": { - "once": "^1.3.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/npm": { - "version": "6.14.15", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.15.tgz", - "integrity": "sha512-dkcQc4n+DiJAMYG2haNAMyJbmuvevjXz+WC9dCUzodw8EovwTIc6CATSsTEplCY6c0jG4OshxFGFJsrnKJguWA==", - "bundleDependencies": [ - "abbrev", - "ansicolors", - "ansistyles", - "aproba", - "archy", - "bin-links", - "bluebird", - "byte-size", - "cacache", - "call-limit", - "chownr", - "ci-info", - "cli-columns", - "cli-table3", - "cmd-shim", - "columnify", - "config-chain", - "debuglog", - "detect-indent", - "detect-newline", - "dezalgo", - "editor", - "figgy-pudding", - "find-npm-prefix", - "fs-vacuum", - "fs-write-stream-atomic", - "gentle-fs", - "glob", - "graceful-fs", - "has-unicode", - "hosted-git-info", - "iferr", - "imurmurhash", - "infer-owner", - "inflight", - "inherits", - "ini", - "init-package-json", - "is-cidr", - "json-parse-better-errors", - "JSONStream", - "lazy-property", - "libcipm", - "libnpm", - "libnpmaccess", - "libnpmhook", - "libnpmorg", - "libnpmsearch", - "libnpmteam", - "libnpx", - "lock-verify", - "lockfile", - "lodash._baseindexof", - "lodash._baseuniq", - "lodash._bindcallback", - "lodash._cacheindexof", - "lodash._createcache", - "lodash._getnative", - "lodash.clonedeep", - "lodash.restparam", - "lodash.union", - "lodash.uniq", - "lodash.without", - "lru-cache", - "meant", - "mississippi", - "mkdirp", - "move-concurrently", - "node-gyp", - "nopt", - "normalize-package-data", - "npm-audit-report", - "npm-cache-filename", - "npm-install-checks", - "npm-lifecycle", - "npm-package-arg", - "npm-packlist", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "npmlog", - "once", - "opener", - "osenv", - "pacote", - "path-is-inside", - "promise-inflight", - "qrcode-terminal", - "query-string", - "qw", - "read-cmd-shim", - "read-installed", - "read-package-json", - "read-package-tree", - "read", - "readable-stream", - "readdir-scoped-modules", - "request", - "retry", - "rimraf", - "safe-buffer", - "semver", - "sha", - "slide", - "sorted-object", - "sorted-union-stream", - "ssri", - "stringify-package", - "tar", - "text-table", - "tiny-relative-date", - "uid-number", - "umask", - "unique-filename", - "unpipe", - "update-notifier", - "uuid", - "validate-npm-package-license", - "validate-npm-package-name", - "which", - "worker-farm", - "write-file-atomic" - ], - "dependencies": { - "abbrev": "~1.1.1", - "ansicolors": "~0.3.2", - "ansistyles": "~0.1.3", - "aproba": "^2.0.0", - "archy": "~1.0.0", - "bin-links": "^1.1.8", - "bluebird": "^3.5.5", - "byte-size": "^5.0.1", - "cacache": "^12.0.3", - "call-limit": "^1.1.1", - "chownr": "^1.1.4", - "ci-info": "^2.0.0", - "cli-columns": "^3.1.2", - "cli-table3": "^0.5.1", - "cmd-shim": "^3.0.3", - "columnify": "~1.5.4", - "config-chain": "^1.1.12", - "debuglog": "*", - "detect-indent": "~5.0.0", - "detect-newline": "^2.1.0", - "dezalgo": "~1.0.3", - "editor": "~1.0.0", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "fs-vacuum": "~1.2.10", - "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.3.1", - "glob": "^7.1.6", - "graceful-fs": "^4.2.4", - "has-unicode": "~2.0.1", - "hosted-git-info": "^2.8.9", - "iferr": "^1.0.2", - "imurmurhash": "*", - "infer-owner": "^1.0.4", - "inflight": "~1.0.6", - "inherits": "^2.0.4", - "ini": "^1.3.8", - "init-package-json": "^1.10.3", - "is-cidr": "^3.0.0", - "json-parse-better-errors": "^1.0.2", - "JSONStream": "^1.3.5", - "lazy-property": "~1.0.0", - "libcipm": "^4.0.8", - "libnpm": "^3.0.1", - "libnpmaccess": "^3.0.2", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "libnpx": "^10.2.4", - "lock-verify": "^2.1.0", - "lockfile": "^1.0.4", - "lodash._baseindexof": "*", - "lodash._baseuniq": "~4.6.0", - "lodash._bindcallback": "*", - "lodash._cacheindexof": "*", - "lodash._createcache": "*", - "lodash._getnative": "*", - "lodash.clonedeep": "~4.5.0", - "lodash.restparam": "*", - "lodash.union": "~4.6.0", - "lodash.uniq": "~4.5.0", - "lodash.without": "~4.4.0", - "lru-cache": "^5.1.1", - "meant": "^1.0.2", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.5", - "move-concurrently": "^1.0.1", - "node-gyp": "^5.1.0", - "nopt": "^4.0.3", - "normalize-package-data": "^2.5.0", - "npm-audit-report": "^1.3.3", - "npm-cache-filename": "~1.0.2", - "npm-install-checks": "^3.0.2", - "npm-lifecycle": "^3.1.5", - "npm-package-arg": "^6.1.1", - "npm-packlist": "^1.4.8", - "npm-pick-manifest": "^3.0.2", - "npm-profile": "^4.0.4", - "npm-registry-fetch": "^4.0.7", - "npm-user-validate": "^1.0.1", - "npmlog": "~4.1.2", - "once": "~1.4.0", - "opener": "^1.5.2", - "osenv": "^0.1.5", - "pacote": "^9.5.12", - "path-is-inside": "~1.0.2", - "promise-inflight": "~1.0.1", - "qrcode-terminal": "^0.12.0", - "query-string": "^6.8.2", - "qw": "~1.0.1", - "read": "~1.0.7", - "read-cmd-shim": "^1.0.5", - "read-installed": "~4.0.3", - "read-package-json": "^2.1.1", - "read-package-tree": "^5.3.1", - "readable-stream": "^3.6.0", - "readdir-scoped-modules": "^1.1.0", - "request": "^2.88.0", - "retry": "^0.12.0", - "rimraf": "^2.7.1", - "safe-buffer": "^5.1.2", - "semver": "^5.7.1", - "sha": "^3.0.0", - "slide": "~1.1.6", - "sorted-object": "~2.0.1", - "sorted-union-stream": "~2.1.3", - "ssri": "^6.0.2", - "stringify-package": "^1.0.1", - "tar": "^4.4.19", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "uid-number": "0.0.6", - "umask": "~1.1.0", - "unique-filename": "^1.1.1", - "unpipe": "~1.0.0", - "update-notifier": "^2.5.0", - "uuid": "^3.3.3", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "~3.0.0", - "which": "^1.3.1", - "worker-farm": "^1.7.0", - "write-file-atomic": "^2.4.3" - }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" - }, - "engines": { - "node": "6 >=6.2.0 || 8 || >=9.3.0" - } - }, - "node_modules/npm-conf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", - "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", - "dev": true, - "optional": true, - "dependencies": { - "config-chain": "^1.1.11", - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-conf/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "optional": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/abbrev": { - "version": "1.1.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/agent-base": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/npm/node_modules/agentkeepalive": { - "version": "3.5.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/npm/node_modules/ansi-align": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^2.0.0" - } - }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "3.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/ansicolors": { - "version": "0.3.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/ansistyles": { - "version": "0.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/are-we-there-yet": { - "version": "1.1.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "node_modules/npm/node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "2.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npm/node_modules/are-we-there-yet/node_modules/string_decoder": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npm/node_modules/asap": { - "version": "2.0.6", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/asn1": { - "version": "0.2.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/npm/node_modules/assert-plus": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npm/node_modules/asynckit": { - "version": "0.4.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/aws-sign2": { - "version": "0.7.0", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/aws4": { - "version": "1.8.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "inBundle": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/npm/node_modules/bin-links": { - "version": "1.1.8", - "inBundle": true, - "license": "Artistic-2.0", - "dependencies": { - "bluebird": "^3.5.3", - "cmd-shim": "^3.0.0", - "gentle-fs": "^2.3.0", - "graceful-fs": "^4.1.15", - "npm-normalize-package-bin": "^1.0.0", - "write-file-atomic": "^2.3.0" - } - }, - "node_modules/npm/node_modules/bluebird": { - "version": "3.5.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/boxen": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npm/node_modules/buffer-from": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/builtins": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/byline": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/byte-size": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/npm/node_modules/cacache": { - "version": "12.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/npm/node_modules/call-limit": { - "version": "1.1.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/camelcase": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/capture-stack-trace": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/caseless": { - "version": "0.12.0", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/npm/node_modules/chalk": { - "version": "2.4.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/chownr": { - "version": "1.1.4", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/ci-info": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/cidr-regex": { - "version": "2.0.10", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/cli-boxes": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/cli-columns": { - "version": "3.1.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^2.0.0", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm/node_modules/cli-table3": { - "version": "0.5.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "colors": "^1.1.2" - } - }, - "node_modules/npm/node_modules/cliui": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/npm/node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/clone": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "3.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" - } - }, - "node_modules/npm/node_modules/code-point-at": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/color-convert": { - "version": "1.9.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "^1.1.1" - } - }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/colors": { - "version": "1.3.3", - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/npm/node_modules/columnify": { - "version": "1.5.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" - } - }, - "node_modules/npm/node_modules/combined-stream": { - "version": "1.0.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/npm/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/concat-stream": { - "version": "1.6.2", - "engines": [ - "node >= 0.8" - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/npm/node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npm/node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npm/node_modules/config-chain": { - "version": "1.1.12", - "inBundle": true, - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/npm/node_modules/configstore": { - "version": "3.1.5", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "dot-prop": "^4.2.1", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/console-control-strings": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/copy-concurrently": { - "version": "1.0.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/npm/node_modules/copy-concurrently/node_modules/aproba": { - "version": "1.2.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/copy-concurrently/node_modules/iferr": { - "version": "0.1.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/core-util-is": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/create-error-class": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "capture-stack-trace": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/cross-spawn": { - "version": "5.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/npm/node_modules/cross-spawn/node_modules/lru-cache": { - "version": "4.1.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/npm/node_modules/cross-spawn/node_modules/yallist": { - "version": "2.1.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/crypto-random-string": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/cyclist": { - "version": "0.2.2", - "inBundle": true - }, - "node_modules/npm/node_modules/dashdash": { - "version": "1.14.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/npm/node_modules/debug": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/npm/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/debuglog": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/decamelize": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/decode-uri-component": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/npm/node_modules/deep-extend": { - "version": "0.6.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/npm/node_modules/defaults": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/npm/node_modules/define-properties": { - "version": "1.1.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/npm/node_modules/delayed-stream": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/npm/node_modules/delegates": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/detect-indent": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/detect-newline": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/dezalgo": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/dot-prop": { - "version": "4.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-obj": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/dotenv": { - "version": "5.0.1", - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.6.0" - } - }, - "node_modules/npm/node_modules/duplexer3": { - "version": "0.1.4", - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/npm/node_modules/duplexify": { - "version": "3.6.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/npm/node_modules/duplexify/node_modules/readable-stream": { - "version": "2.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npm/node_modules/duplexify/node_modules/string_decoder": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npm/node_modules/ecc-jsbn": { - "version": "0.1.2", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/npm/node_modules/editor": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "7.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.12", - "inBundle": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "~0.4.13" - } - }, - "node_modules/npm/node_modules/end-of-stream": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/npm/node_modules/env-paths": { - "version": "2.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/err-code": { - "version": "1.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/errno": { - "version": "0.1.7", - "inBundle": true, - "license": "MIT", - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/npm/node_modules/es-abstract": { - "version": "1.12.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/npm/node_modules/es-to-primitive": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/npm/node_modules/es6-promise": { - "version": "4.2.8", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/es6-promisify": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/npm/node_modules/escape-string-regexp": { - "version": "1.0.5", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/npm/node_modules/execa": { - "version": "0.7.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/execa/node_modules/get-stream": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/extend": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/extsprintf": { - "version": "1.3.0", - "engines": [ - "node >=0.6.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/fast-json-stable-stringify": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/figgy-pudding": { - "version": "3.5.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/find-npm-prefix": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/flush-write-stream": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" - } - }, - "node_modules/npm/node_modules/flush-write-stream/node_modules/readable-stream": { - "version": "2.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npm/node_modules/flush-write-stream/node_modules/string_decoder": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npm/node_modules/forever-agent": { - "version": "0.6.1", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/form-data": { - "version": "2.3.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/npm/node_modules/from2": { - "version": "2.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/npm/node_modules/from2/node_modules/readable-stream": { - "version": "2.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npm/node_modules/from2/node_modules/string_decoder": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npm/node_modules/fs-minipass": { - "version": "1.2.7", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^2.6.0" - } - }, - "node_modules/npm/node_modules/fs-minipass/node_modules/minipass": { - "version": "2.9.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/npm/node_modules/fs-vacuum": { - "version": "1.2.10", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "path-is-inside": "^1.0.1", - "rimraf": "^2.5.2" - } - }, - "node_modules/npm/node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "node_modules/npm/node_modules/fs-write-stream-atomic/node_modules/iferr": { - "version": "0.1.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/fs-write-stream-atomic/node_modules/readable-stream": { - "version": "2.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npm/node_modules/fs-write-stream-atomic/node_modules/string_decoder": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npm/node_modules/fs.realpath": { - "version": "1.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/function-bind": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/gauge": { - "version": "2.7.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/npm/node_modules/gauge/node_modules/aproba": { - "version": "1.2.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/gauge/node_modules/string-width": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/genfun": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/gentle-fs": { - "version": "2.3.1", - "inBundle": true, - "license": "Artistic-2.0", - "dependencies": { - "aproba": "^1.1.2", - "chownr": "^1.1.2", - "cmd-shim": "^3.0.3", - "fs-vacuum": "^1.2.10", - "graceful-fs": "^4.1.11", - "iferr": "^0.1.5", - "infer-owner": "^1.0.4", - "mkdirp": "^0.5.1", - "path-is-inside": "^1.0.2", - "read-cmd-shim": "^1.0.1", - "slide": "^1.1.6" - } - }, - "node_modules/npm/node_modules/gentle-fs/node_modules/aproba": { - "version": "1.2.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/gentle-fs/node_modules/iferr": { - "version": "0.1.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/get-caller-file": { - "version": "2.0.5", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/npm/node_modules/get-stream": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/getpass": { - "version": "0.1.7", - "inBundle": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/npm/node_modules/glob": { - "version": "7.1.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/global-dirs": { - "version": "0.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/got": { - "version": "6.7.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/got/node_modules/get-stream": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.4", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/har-schema": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/har-validator": { - "version": "5.1.5", - "deprecated": "this library is no longer supported", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/har-validator/node_modules/ajv": { - "version": "6.12.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/npm/node_modules/har-validator/node_modules/fast-deep-equal": { - "version": "3.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/har-validator/node_modules/json-schema-traverse": { - "version": "0.4.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/has": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/npm/node_modules/has-flag": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/has-symbols": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/npm/node_modules/has-unicode": { - "version": "2.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/hosted-git-info": { - "version": "2.8.9", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/http-cache-semantics": { - "version": "3.8.1", - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npm/node_modules/http-proxy-agent": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "4", - "debug": "3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/npm/node_modules/http-signature": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/npm/node_modules/https-proxy-agent": { - "version": "2.2.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/npm/node_modules/humanize-ms": { - "version": "1.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/npm/node_modules/iconv-lite": { - "version": "0.4.23", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/iferr": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/npm/node_modules/ignore-walk": { - "version": "3.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^3.0.4" - } - }, - "node_modules/npm/node_modules/import-lazy": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/npm/node_modules/infer-owner": { - "version": "1.0.4", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/inflight": { - "version": "1.0.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/inherits": { - "version": "2.0.4", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/ini": { - "version": "1.3.8", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/init-package-json": { - "version": "1.10.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" - } - }, - "node_modules/npm/node_modules/ip": { - "version": "1.1.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/ip-regex": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/is-callable": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/npm/node_modules/is-ci": { - "version": "1.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ci-info": "^1.5.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/npm/node_modules/is-ci/node_modules/ci-info": { - "version": "1.6.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/is-cidr": { - "version": "3.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "cidr-regex": "^2.0.10" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/is-date-object": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/is-installed-globally": { - "version": "0.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/is-npm": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/is-obj": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/is-path-inside": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-is-inside": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/is-redirect": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/is-regex": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/npm/node_modules/is-retry-allowed": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/is-stream": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/is-symbol": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/npm/node_modules/is-typedarray": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/isarray": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/isstream": { - "version": "0.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/jsbn": { - "version": "0.1.1", - "inBundle": true, - "license": "MIT", - "optional": true - }, - "node_modules/npm/node_modules/json-parse-better-errors": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/json-schema": { - "version": "0.2.3", - "inBundle": true - }, - "node_modules/npm/node_modules/json-stringify-safe": { - "version": "5.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/JSONStream": { - "version": "1.3.5", - "inBundle": true, - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/jsprim": { - "version": "1.4.1", - "engines": [ - "node >=0.6.0" - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "node_modules/npm/node_modules/latest-version": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "package-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/lazy-property": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/libcipm": { - "version": "4.0.8", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "graceful-fs": "^4.1.11", - "ini": "^1.3.5", - "lock-verify": "^2.1.0", - "mkdirp": "^0.5.1", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "pacote": "^9.1.0", - "read-package-json": "^2.0.13", - "rimraf": "^2.6.2", - "worker-farm": "^1.6.0" - } - }, - "node_modules/npm/node_modules/libnpm": { - "version": "3.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.3", - "find-npm-prefix": "^1.0.2", - "libnpmaccess": "^3.0.2", - "libnpmconfig": "^1.2.1", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmpublish": "^1.1.2", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "lock-verify": "^2.0.2", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.0", - "npmlog": "^4.1.2", - "pacote": "^9.5.3", - "read-package-json": "^2.0.13", - "stringify-package": "^1.0.0" - } - }, - "node_modules/npm/node_modules/libnpmaccess": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "get-stream": "^4.0.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "node_modules/npm/node_modules/libnpmconfig": { - "version": "1.2.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "figgy-pudding": "^3.5.1", - "find-up": "^3.0.0", - "ini": "^1.3.5" - } - }, - "node_modules/npm/node_modules/libnpmconfig/node_modules/find-up": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/libnpmconfig/node_modules/locate-path": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/libnpmconfig/node_modules/p-limit": { - "version": "2.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/libnpmconfig/node_modules/p-locate": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/libnpmconfig/node_modules/p-try": { - "version": "2.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/libnpmhook": { - "version": "5.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "node_modules/npm/node_modules/libnpmorg": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "node_modules/npm/node_modules/libnpmpublish": { - "version": "1.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "lodash.clonedeep": "^4.5.0", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0", - "semver": "^5.5.1", - "ssri": "^6.0.1" - } - }, - "node_modules/npm/node_modules/libnpmsearch": { - "version": "2.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "node_modules/npm/node_modules/libnpmteam": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "node_modules/npm/node_modules/libnpx": { - "version": "10.2.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "dotenv": "^5.0.1", - "npm-package-arg": "^6.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.0", - "update-notifier": "^2.3.0", - "which": "^1.3.0", - "y18n": "^4.0.0", - "yargs": "^14.2.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/lock-verify": { - "version": "2.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-package-arg": "^6.1.0", - "semver": "^5.4.1" - } - }, - "node_modules/npm/node_modules/lockfile": { - "version": "1.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "signal-exit": "^3.0.2" - } - }, - "node_modules/npm/node_modules/lodash._baseindexof": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash._baseuniq": { - "version": "4.6.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } - }, - "node_modules/npm/node_modules/lodash._bindcallback": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash._cacheindexof": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash._createcache": { - "version": "3.1.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "lodash._getnative": "^3.0.0" - } - }, - "node_modules/npm/node_modules/lodash._createset": { - "version": "4.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash._getnative": { - "version": "3.9.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash._root": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash.clonedeep": { - "version": "4.5.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash.restparam": { - "version": "3.6.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash.union": { - "version": "4.6.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash.uniq": { - "version": "4.5.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lodash.without": { - "version": "4.4.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/lowercase-keys": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/lru-cache": { - "version": "5.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/npm/node_modules/make-dir": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/make-fetch-happen": { - "version": "5.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^3.4.1", - "cacache": "^12.0.0", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" - } - }, - "node_modules/npm/node_modules/meant": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/mime-db": { - "version": "1.35.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npm/node_modules/mime-types": { - "version": "2.1.19", - "inBundle": true, - "license": "MIT", - "dependencies": { - "mime-db": "~1.35.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npm/node_modules/minimatch": { - "version": "3.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/minimist": { - "version": "1.2.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/minizlib": { - "version": "1.3.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^2.9.0" - } - }, - "node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "2.9.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/npm/node_modules/mississippi": { - "version": "3.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/npm/node_modules/mkdirp": { - "version": "0.5.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/npm/node_modules/mkdirp/node_modules/minimist": { - "version": "1.2.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/move-concurrently": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "node_modules/npm/node_modules/move-concurrently/node_modules/aproba": { - "version": "1.2.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/ms": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/mute-stream": { - "version": "0.0.7", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/node-fetch-npm": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/node-gyp": { - "version": "5.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "npmlog": "^4.1.2", - "request": "^2.88.0", - "rimraf": "^2.6.3", - "semver": "^5.7.1", - "tar": "^4.4.12", - "which": "^1.3.1" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/npm/node_modules/nopt": { - "version": "4.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "2.5.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/npm/node_modules/normalize-package-data/node_modules/resolve": { - "version": "1.10.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-parse": "^1.0.6" - } - }, - "node_modules/npm/node_modules/npm-audit-report": { - "version": "1.3.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cli-table3": "^0.5.0", - "console-control-strings": "^1.1.0" - } - }, - "node_modules/npm/node_modules/npm-bundled": { - "version": "1.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/npm/node_modules/npm-cache-filename": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/npm-install-checks": { - "version": "3.0.2", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^2.3.0 || 3.x || 4 || 5" - } - }, - "node_modules/npm/node_modules/npm-lifecycle": { - "version": "3.1.5", - "inBundle": true, - "license": "Artistic-2.0", - "dependencies": { - "byline": "^5.0.0", - "graceful-fs": "^4.1.15", - "node-gyp": "^5.0.2", - "resolve-from": "^4.0.0", - "slide": "^1.1.6", - "uid-number": "0.0.6", - "umask": "^1.1.0", - "which": "^1.3.1" - } - }, - "node_modules/npm/node_modules/npm-logical-tree": { - "version": "1.2.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/npm-package-arg": { - "version": "6.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" - } - }, - "node_modules/npm/node_modules/npm-packlist": { - "version": "1.4.8", - "inBundle": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" - } - }, - "node_modules/npm/node_modules/npm-profile": { - "version": "4.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.1.2 || 2", - "figgy-pudding": "^3.4.1", - "npm-registry-fetch": "^4.0.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "4.0.7", - "inBundle": true, - "license": "ISC", - "dependencies": { - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", - "JSONStream": "^1.3.4", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "npm-package-arg": "^6.1.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/npm-run-path": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/npm-user-validate": { - "version": "1.0.1", - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npm/node_modules/npmlog": { - "version": "4.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "node_modules/npm/node_modules/number-is-nan": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/oauth-sign": { - "version": "0.9.0", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/object-assign": { - "version": "4.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/object-keys": { - "version": "1.0.12", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/npm/node_modules/object.getownpropertydescriptors": { - "version": "2.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/npm/node_modules/once": { - "version": "1.4.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/opener": { - "version": "1.5.2", - "inBundle": true, - "license": "(WTFPL OR MIT)", - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/npm/node_modules/os-homedir": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/os-tmpdir": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/osenv": { - "version": "0.1.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "node_modules/npm/node_modules/p-finally": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/package-json": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/pacote": { - "version": "9.5.12", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bluebird": "^3.5.3", - "cacache": "^12.0.2", - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.1.0", - "glob": "^7.1.3", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "minimatch": "^3.0.4", - "minipass": "^2.3.5", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-normalize-package-bin": "^1.0.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^3.0.0", - "npm-registry-fetch": "^4.0.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.1", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.6.0", - "ssri": "^6.0.1", - "tar": "^4.4.10", - "unique-filename": "^1.1.1", - "which": "^1.3.1" - } - }, - "node_modules/npm/node_modules/pacote/node_modules/minipass": { - "version": "2.9.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/npm/node_modules/parallel-transform": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "node_modules/npm/node_modules/parallel-transform/node_modules/readable-stream": { - "version": "2.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npm/node_modules/parallel-transform/node_modules/string_decoder": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npm/node_modules/path-exists": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/path-is-absolute": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/path-is-inside": { - "version": "1.0.2", - "inBundle": true, - "license": "(WTFPL OR MIT)" - }, - "node_modules/npm/node_modules/path-key": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/path-parse": { - "version": "1.0.7", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/performance-now": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/pify": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/prepend-http": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/process-nextick-args": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/promise-retry": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "err-code": "^1.0.0", - "retry": "^0.10.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/npm/node_modules/promise-retry/node_modules/retry": { - "version": "0.10.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/promzard": { - "version": "0.3.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "read": "1" - } - }, - "node_modules/npm/node_modules/proto-list": { - "version": "1.2.4", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/protoduck": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "genfun": "^5.0.0" - } - }, - "node_modules/npm/node_modules/prr": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/pseudomap": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/psl": { - "version": "1.1.29", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/pump": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/npm/node_modules/pumpify": { - "version": "1.5.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/npm/node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/npm/node_modules/punycode": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", - "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, - "node_modules/npm/node_modules/qs": { - "version": "6.5.2", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/npm/node_modules/query-string": { - "version": "6.8.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "decode-uri-component": "^0.2.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/qw": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/rc": { - "version": "1.2.8", - "inBundle": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/npm/node_modules/read": { - "version": "1.0.7", - "inBundle": true, - "license": "ISC", - "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npm/node_modules/read-cmd-shim": { - "version": "1.0.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/npm/node_modules/read-installed": { - "version": "4.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/npm/node_modules/read-package-json": { - "version": "2.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.1", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/npm/node_modules/read-package-tree": { - "version": "5.3.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "util-promisify": "^2.1.0" - } - }, - "node_modules/npm/node_modules/readable-stream": { - "version": "3.6.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/npm/node_modules/readdir-scoped-modules": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, - "node_modules/npm/node_modules/registry-auth-token": { - "version": "3.4.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/npm/node_modules/registry-url": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "rc": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/request": { - "version": "2.88.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm/node_modules/require-directory": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/require-main-filename": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/resolve-from": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm/node_modules/rimraf": { - "version": "2.7.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/npm/node_modules/run-queue": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.1.1" - } - }, - "node_modules/npm/node_modules/run-queue/node_modules/aproba": { - "version": "1.2.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/safe-buffer": { - "version": "5.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/semver": { - "version": "5.7.1", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npm/node_modules/semver-diff": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "semver": "^5.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/set-blocking": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/sha": { - "version": "3.0.0", - "inBundle": true, - "license": "(BSD-2-Clause OR MIT)", - "dependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/npm/node_modules/shebang-command": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/shebang-regex": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/signal-exit": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/slide": { - "version": "1.1.6", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/smart-buffer": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks": { - "version": "2.3.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - }, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "4.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/npm/node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "4.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/npm/node_modules/sorted-object": { - "version": "2.0.1", - "inBundle": true, - "license": "(WTFPL OR MIT)" - }, - "node_modules/npm/node_modules/sorted-union-stream": { - "version": "2.1.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "from2": "^1.3.0", - "stream-iterate": "^1.1.0" - } - }, - "node_modules/npm/node_modules/sorted-union-stream/node_modules/from2": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.10" - } - }, - "node_modules/npm/node_modules/sorted-union-stream/node_modules/isarray": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/sorted-union-stream/node_modules/readable-stream": { - "version": "1.1.14", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/npm/node_modules/sorted-union-stream/node_modules/string_decoder": { - "version": "0.10.31", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.0.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.1.0", - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.5", - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/npm/node_modules/split-on-first": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/sshpk": { - "version": "1.14.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "dashdash": "^1.12.0", - "getpass": "^0.1.1", - "safer-buffer": "^2.0.2" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - }, - "optionalDependencies": { - "bcrypt-pbkdf": "^1.0.0", - "ecc-jsbn": "~0.1.1", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" - } - }, - "node_modules/npm/node_modules/ssri": { - "version": "6.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, - "node_modules/npm/node_modules/stream-each": { - "version": "1.2.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/npm/node_modules/stream-iterate": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.1.5", - "stream-shift": "^1.0.0" - } - }, - "node_modules/npm/node_modules/stream-iterate/node_modules/readable-stream": { - "version": "2.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npm/node_modules/stream-iterate/node_modules/string_decoder": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npm/node_modules/stream-shift": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/strict-uri-encode": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/string_decoder": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/npm/node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/string-width": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/string-width/node_modules/ansi-regex": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/stringify-package": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/strip-eof": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/strip-json-comments": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/supports-color": { - "version": "5.4.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/tar": { - "version": "4.4.19", - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" - }, - "engines": { - "node": ">=4.5" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "2.9.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/npm/node_modules/tar/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tar/node_modules/yallist": { - "version": "3.1.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/term-size": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "execa": "^0.7.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/through": { - "version": "2.3.8", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/through2": { - "version": "2.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" - } - }, - "node_modules/npm/node_modules/through2/node_modules/readable-stream": { - "version": "2.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npm/node_modules/through2/node_modules/string_decoder": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npm/node_modules/timed-out": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tough-cookie": { - "version": "2.4.3", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npm/node_modules/tunnel-agent": { - "version": "0.6.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/tweetnacl": { - "version": "0.14.5", - "inBundle": true, - "license": "Unlicense", - "optional": true - }, - "node_modules/npm/node_modules/typedarray": { - "version": "0.0.6", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/uid-number": { - "version": "0.0.6", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/umask": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/unique-filename": { - "version": "1.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/npm/node_modules/unique-slug": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/npm/node_modules/unique-string": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/unpipe": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/npm/node_modules/unzip-response": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/update-notifier": { - "version": "2.5.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/uri-js": { - "version": "4.4.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/npm/node_modules/uri-js/node_modules/punycode": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/url-parse-lax": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "prepend-http": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/util-extend": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/util-promisify": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "node_modules/npm/node_modules/uuid": { - "version": "3.3.3", - "inBundle": true, - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "builtins": "^1.0.3" - } - }, - "node_modules/npm/node_modules/verror": { - "version": "1.10.0", - "engines": [ - "node >=0.6.0" - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/npm/node_modules/wcwidth": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/npm/node_modules/which": { - "version": "1.3.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/npm/node_modules/which-module": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/wide-align": { - "version": "1.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2" - } - }, - "node_modules/npm/node_modules/wide-align/node_modules/string-width": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/widest-line": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^2.1.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/worker-farm": { - "version": "1.7.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "errno": "~0.1.7" - } - }, - "node_modules/npm/node_modules/wrap-ansi": { - "version": "5.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "5.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/wrappy": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/write-file-atomic": { - "version": "2.4.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "node_modules/npm/node_modules/xdg-basedir": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/xtend": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/npm/node_modules/y18n": { - "version": "4.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/yallist": { - "version": "3.0.3", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/yargs": { - "version": "14.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cliui": "^5.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^15.0.1" - } - }, - "node_modules/npm/node_modules/yargs-parser": { - "version": "15.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/npm/node_modules/yargs-parser/node_modules/camelcase": { - "version": "5.3.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/yargs/node_modules/ansi-regex": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/yargs/node_modules/find-up": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/yargs/node_modules/locate-path": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/yargs/node_modules/p-locate": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/yargs/node_modules/p-try": { - "version": "2.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "dependencies": { - "boolbase": "~1.0.0" - } - }, - "node_modules/num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", - "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", - "dev": true, - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz", - "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "dependencies": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.omit/node_modules/for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "dependencies": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "node_modules/optimist/node_modules/minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/optipng-bin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-6.0.0.tgz", - "integrity": "sha512-95bB4y8IaTsa/8x6QH4bLUuyvyOoGBCLDA7wOgDL8UFqJpSUh1Hob8JRJhit+wC1ZLN3tQ7mFt7KuBj0x8F2Wg==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "bin-build": "^3.0.0", - "bin-wrapper": "^4.0.0", - "logalot": "^2.0.0" - }, - "bin": { - "optipng": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dependencies": { - "url-parse": "^1.4.3" - } - }, - "node_modules/os-filter-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", - "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", - "dev": true, - "optional": true, - "dependencies": { - "arch": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-event": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", - "integrity": "sha1-jmtPT2XHK8W2/ii3XtqHT5akoIU=", - "dev": true, - "optional": true, - "dependencies": { - "p-timeout": "^1.1.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-is-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-map-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", - "integrity": "sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=", - "dev": true, - "optional": true, - "dependencies": { - "p-reduce": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-pipe": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-3.1.0.tgz", - "integrity": "sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "dev": true, - "optional": true, - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module/node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "dependencies": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-glob/node_modules/is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-glob/node_modules/is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true - }, - "node_modules/parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "dependencies": { - "better-assert": "~1.0.0" - } - }, - "node_modules/parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "dev": true, - "dependencies": { - "better-assert": "~1.0.0" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true, - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true, - "optional": true - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/plugin-error/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plur": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", - "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", - "dev": true, - "dependencies": { - "irregular-plurals": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-calc": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", - "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-colormin/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-convert-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-load-config": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", - "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", - "dev": true, - "dependencies": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "dev": true, - "dependencies": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-merge-longhand/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-font-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-gradients/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "dev": true, - "dependencies": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-params/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "dev": true, - "dependencies": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "dev": true, - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-display-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-positions/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-repeat-style/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "dev": true, - "dependencies": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-string/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "dev": true, - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-timing-functions/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-unicode/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "dev": true, - "dependencies": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-url/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-whitespace/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-ordered-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "dev": true, - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-reduce-transforms/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz", - "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-svgo/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "dev": true, - "dependencies": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true - }, - "node_modules/postcss/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "node_modules/postcss/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "optional": true, - "dependencies": { - "asap": "~2.0.3" - } - }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true, - "optional": true - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "optional": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true, - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "dependencies": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "dependencies": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/randomatic/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/randomatic/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dev": true, - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "optional": true, - "dependencies": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", - "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "dependencies": { - "is-equal-shallow": "^0.1.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true, - "engines": { - "node": ">=6.5.0" - } - }, - "node_modules/regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", - "dev": true, - "dependencies": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "optional": true, - "dependencies": { - "is-finite": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.19" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "engines": { - "node": ">=0.12.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/request/node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/request/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", - "dev": true, - "dependencies": { - "value-or-function": "^3.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "optional": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", - "dev": true - }, - "node_modules/rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", - "dev": true - }, - "node_modules/right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "dependencies": { - "align-text": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/run-sequence": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", - "integrity": "sha512-qkzZnQWMZjcKbh3CNly2srtrkaO/2H/SI5f2eliMCapdRD3UhMrwjfOAZJAnZ2H8Ju4aBzFZkBGXUqFs9V0yxw==", - "dev": true, - "dependencies": { - "chalk": "^1.1.3", - "fancy-log": "^1.3.2", - "plugin-error": "^0.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/run-sequence/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-sequence/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-sequence/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-sequence/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-sequence/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-sequence/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-sequence/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-sequence/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-sequence/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-sequence/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/seek-bzip": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", - "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", - "dev": true, - "optional": true, - "dependencies": { - "commander": "^2.8.1" - }, - "bin": { - "seek-bunzip": "bin/seek-bunzip", - "seek-table": "bin/seek-bzip-table" - } - }, - "node_modules/select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", - "dev": true, - "dependencies": { - "sver-compat": "^1.5.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/semver-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", - "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/semver-truncate": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", - "integrity": "sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g=", - "dev": true, - "optional": true, - "dependencies": { - "semver": "^5.3.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", - "dev": true - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - }, - "node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/socket.io": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", - "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", - "dev": true, - "dependencies": { - "debug": "~3.1.0", - "engine.io": "~3.2.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.1.1", - "socket.io-parser": "~3.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", - "dev": true - }, - "node_modules/socket.io-client": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", - "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", - "dev": true, - "dependencies": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "engine.io-client": "~3.2.0", - "has-binary2": "~1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "~3.2.0", - "to-array": "0.1.4" - } - }, - "node_modules/socket.io-client/node_modules/component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/socket.io-client/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/socket.io-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", - "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", - "dev": true, - "dependencies": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" - } - }, - "node_modules/socket.io-parser/node_modules/component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "node_modules/socket.io-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/socket.io/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/socket.io/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "optional": true, - "dependencies": { - "is-plain-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sort-keys-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", - "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", - "dev": true, - "optional": true, - "dependencies": { - "sort-keys": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "node_modules/sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==" - }, - "node_modules/spectrum-colorpicker2": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/spectrum-colorpicker2/-/spectrum-colorpicker2-2.0.8.tgz", - "integrity": "sha512-z8Wlww/SgRbMgwgljfPxA3HNJMRQ0PRKB9ef0bKVDB1EeSR+MsC0KuOjt9H/R/CdAIoBmvKJEfIAvWS4PQpL0Q==" - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/squeak": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/squeak/-/squeak-1.3.0.tgz", - "integrity": "sha1-MwRQN7ZDiLVnZ0uEMiplIQc5FsM=", - "dev": true, - "optional": true, - "dependencies": { - "chalk": "^1.0.0", - "console-stream": "^0.1.1", - "lpad-align": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/squeak/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/squeak/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "optional": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/squeak/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "optional": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/squeak/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "dev": true - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/streamroller": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", - "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", - "dev": true, - "dependencies": { - "async": "^2.6.2", - "date-format": "^2.0.0", - "debug": "^3.2.6", - "fs-extra": "^7.0.1", - "lodash": "^4.17.14" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/streamroller/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-bom-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", - "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", - "dev": true, - "dependencies": { - "first-chunk-stream": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", - "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", - "dev": true, - "optional": true, - "dependencies": { - "is-natural-number": "^4.0.1" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "optional": true, - "dependencies": { - "get-stdin": "^4.0.1" - }, - "bin": { - "strip-indent": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "dev": true, - "optional": true, - "dependencies": { - "escape-string-regexp": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strnum": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.4.tgz", - "integrity": "sha512-lMzNMfDpaQOLt4B2mEbfzYS0+T7dvCXeojnlGf6f1AygvWDMcWyXYaLbyICfjVu29sErR8fnRagQfBW/N/hGgw==", - "dev": true, - "optional": true - }, - "node_modules/stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/stylehacks/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", - "dev": true, - "dependencies": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", - "dev": true, - "dependencies": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/table/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "dev": true, - "optional": true, - "dependencies": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/tempfile": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", - "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=", - "dev": true, - "optional": true, - "dependencies": { - "temp-dir": "^1.0.0", - "uuid": "^3.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/terser": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", - "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", - "dev": true, - "dependencies": { - "commander": "^2.19.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.10" - }, - "bin": { - "terser": "bin/uglifyjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", - "dev": true - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2-concurrent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/through2-concurrent/-/through2-concurrent-2.0.0.tgz", - "integrity": "sha512-R5/jLkfMvdmDD+seLwN7vB+mhbqzWop5fAjx5IX8/yQq7VhBhzDmhXgaHAOnhnWkCpRMM7gToYHycB0CS/pd+A==", - "dev": true, - "dependencies": { - "through2": "^2.0.0" - } - }, - "node_modules/through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "dependencies": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "node_modules/time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dev": true, - "dependencies": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, - "node_modules/timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", - "dev": true - }, - "node_modules/tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" - }, - "node_modules/tinymce": { - "version": "4.9.11", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.11.tgz", - "integrity": "sha512-nkSLsax+VY5DBRjMFnHFqPwTnlLEGHCco82FwJF2JNH6W+5/ClvNC1P4uhD5lXPDNiDykSHR0XJdEh7w/ICHzA==" - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, - "node_modules/to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true, - "optional": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", - "dev": true, - "dependencies": { - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "dev": true, - "dependencies": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", - "dev": true, - "optional": true, - "dependencies": { - "escape-string-regexp": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", - "dev": true - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typeahead.js": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/typeahead.js/-/typeahead.js-0.11.1.tgz", - "integrity": "sha1-TmTmcbIjEKhgb0rsgFkkuoSwFbg=", - "dependencies": { - "jquery": ">=1.7" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "node_modules/uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "dependencies": { - "source-map": "~0.5.1", - "yargs": "~3.10.0" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - }, - "optionalDependencies": { - "uglify-to-browserify": "~1.0.0" - } - }, - "node_modules/uglify-js/node_modules/camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uglify-js/node_modules/cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "dependencies": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "node_modules/uglify-js/node_modules/wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/uglify-js/node_modules/yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "dependencies": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - }, - "node_modules/uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "node_modules/ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "optional": true, - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" - }, - "node_modules/undertaker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", - "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "fast-levenshtein": "^1.0.0", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker/node_modules/fast-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", - "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", - "dev": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "node_modules/uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "node_modules/unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "dependencies": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", - "dev": true - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/url-parse": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", - "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "optional": true, - "dependencies": { - "prepend-http": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true, - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", - "dev": true, - "dependencies": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-bufferstream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vinyl-bufferstream/-/vinyl-bufferstream-1.0.1.tgz", - "integrity": "sha1-BTeGn1gO/6TKRay0dXnkuf5jCBo=", - "dev": true, - "dependencies": { - "bufferstreams": "1.0.1" - } - }, - "node_modules/vinyl-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", - "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.3.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0", - "strip-bom-stream": "^2.0.0", - "vinyl": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vinyl-file/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/vinyl-file/node_modules/clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "node_modules/vinyl-file/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vinyl-file/node_modules/replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/vinyl-file/node_modules/vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true, - "dependencies": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - }, - "engines": { - "node": ">= 0.9" - } - }, - "node_modules/vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "dev": true, - "dependencies": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", - "dev": true, - "dependencies": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-sourcemap/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", - "dev": true, - "dependencies": { - "source-map": "^0.5.1" - } - }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/when": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", - "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=", - "dev": true - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "node_modules/wicg-inert": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.1.tgz", - "integrity": "sha512-PhBaNh8ur9Xm4Ggy4umelwNIP6pPP1bv3EaWaKqfb/QNme2rdLjm7wIInvV4WhxVHhzA4Spgw9qNSqWtB/ca2A==" - }, - "node_modules/window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlbuilder": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", - "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, - "node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "node_modules/yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" - } - }, - "node_modules/yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } - }, - "node_modules/yargs-parser/node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs/node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, - "optional": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - } - }, + "lockfileVersion": 1, "dependencies": { "@babel/code-frame": { "version": "7.15.8", @@ -20467,7 +21,7 @@ "@babel/core": { "version": "7.6.4", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", - "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==", + "integrity": "sha1-br2f4Akl9sPhd7tyahiLX1eAiP8=", "dev": true, "requires": { "@babel/code-frame": "^7.5.5", @@ -20759,7 +313,7 @@ "@babel/preset-env": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.3.tgz", - "integrity": "sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==", + "integrity": "sha1-nhvwWi4taHA20kxA5GOdxGzvInE=", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", @@ -21313,7 +867,7 @@ "@gulp-sourcemaps/identity-map": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", - "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "integrity": "sha1-Hm/l2AJ7HyhdwNMXYvVmvM1z1ak=", "dev": true, "requires": { "acorn": "^5.0.3", @@ -21341,7 +895,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -21452,7 +1006,7 @@ "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "integrity": "sha1-UxvHJlF6OytB+FACHGzBXqq1B80=", "dev": true, "requires": { "mime-types": "~2.1.24", @@ -21462,7 +1016,7 @@ "accord": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/accord/-/accord-0.29.0.tgz", - "integrity": "sha512-3OOR92FTc2p5/EcOzPcXp+Cbo+3C15nV9RXHlOUBCBpHhcB+0frbSNR9ehED/o7sTcyGVtqGJpguToEdlXhD0w==", + "integrity": "sha1-t0HBdtAENcWSnUZt/oz2vukzseQ=", "dev": true, "requires": { "convert-source-map": "^1.5.0", @@ -21484,7 +1038,7 @@ "ace-builds": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.2.tgz", - "integrity": "sha512-M1JtZctO2Zg+1qeGUFZXtYKsyaRptqQtqpVzlj80I0NzGW9MF3um0DBuizIvQlrPYUlTdm+wcOPZpZoerkxQdA==" + "integrity": "sha1-avwuQ6e17/3ETYQHQ2EShSVo6A0=" }, "acorn": { "version": "7.4.1", @@ -21506,8 +1060,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "7.2.0", @@ -21574,12 +1127,12 @@ "angular-animate": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz", - "integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ==" + "integrity": "sha1-H/xsKpze4ieiunnMbNj3HsRNtdw=" }, "angular-aria": { "version": "1.7.9", "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.9.tgz", - "integrity": "sha512-luI3Jemd1AbOQW0krdzfEG3fM0IFtLY0bSSqIDEx3POE0XjKIC1MkrO8Csyq9PPgueLphyAPofzUwZ8YeZ88SA==" + "integrity": "sha1-kMYYlf+9h26VkVIisyp70AcK9+M=" }, "angular-chart.js": { "version": "1.1.1", @@ -21604,12 +1157,12 @@ "angular-cookies": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz", - "integrity": "sha512-/8xvvSl/Z9Vwu8ChRm+OQE3vmli8Icwl8uTYkHqD7j7cknJP9kNaf7SgsENlsLVtOqLE/I7TCFYrSx3bmSeNQA==" + "integrity": "sha1-HFqzwFzcQ/F3e+lQbmRYfLNUNjQ=" }, "angular-dynamic-locale": { "version": "0.1.37", "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.37.tgz", - "integrity": "sha512-m5Kyk8W8/mOZSqRxuByOwHBjv8labLBAgvl0Z3iQx2xT/tWCqb94imKUPwumudszdPDjxeopwyucQvm8Sw7ogw==", + "integrity": "sha1-fon70uxFvdaryJ82zaiJODjkk1Q=", "requires": { "@types/angular": "^1.6.25" } @@ -21617,7 +1170,7 @@ "angular-i18n": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-i18n/-/angular-i18n-1.7.5.tgz", - "integrity": "sha512-52+Jpt8HRJV2bqSbSU6fWkwOvGzj/DxbNpKXxnTuCS9heuJrlm77BS/lhrF4BA8+Uudnh7npr5/yRELobP+8Yw==" + "integrity": "sha1-Lie2Thl3qMa2sFHFHQF1xtTcglI=" }, "angular-local-storage": { "version": "0.7.1", @@ -21627,32 +1180,32 @@ "angular-messages": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.7.5.tgz", - "integrity": "sha512-YDpJpFLyrIgZjE/sIAjgww1y6r3QqXBJbNDI0QjftD37vHXLkwvAOo3A4bxPw8BikyGLcJrFrgf6hRAzntJIWA==" + "integrity": "sha1-fC/XgTFaQ6GYOLEX2gFCqYhFThQ=" }, "angular-mocks": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.5.tgz", - "integrity": "sha512-I+Ue2Bkx6R9W5178DYrNvzjIdGh4wKKoCWsgz8dc7ysH4mA70Q3M9v5xRF0RUu7r+2CZj+nDeUecvh2paxcYvg==" + "integrity": "sha1-yLq6WgbtYLk0aXAmtJIWliavOEs=" }, "angular-route": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-route/-/angular-route-1.7.5.tgz", - "integrity": "sha512-7KfyEVVOWTI+jTY/j5rUNCIHGRyeCOx7YqZI/Ci3IbDK7GIsy6xH+hS5ai0Xi0sLjzDZ0PUDO4gBn+K0dVtlOg==" + "integrity": "sha1-NKNkjEB6FKAw0HXPSFMY4zuiPw4=" }, "angular-sanitize": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.7.5.tgz", - "integrity": "sha512-wjKCJOIwrkEvfD0keTnKGi6We13gtoCAQIHcdoqyoo3gwvcgNfYymVQIS3+iCGVcjfWz0jHuS3KgB4ysRWsTTA==" + "integrity": "sha1-ddSeFQccqccFgedtIJQPJjcuJNI=" }, "angular-touch": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-touch/-/angular-touch-1.7.5.tgz", - "integrity": "sha512-XNAZNG0RA1mtdwBJheViCF1H/7wOygp4MLIfs5y1K+rne6AeaYKZcV6EJs9fvgfLKLO6ecm1+3J8hoCkdhhxQw==" + "integrity": "sha1-7SYyKmhfApmyPLauqYNMEZQk2kY=" }, "angular-ui-sortable": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/angular-ui-sortable/-/angular-ui-sortable-0.19.0.tgz", - "integrity": "sha512-u/uc981Nzg4XN1bMU9qKleMTSt7F1XjMWnyGw6gxPLIeQeLZm8jWNy7tj8y2r2HmvzXFbQVq2z6rObznFKAekQ==", + "integrity": "sha1-SsQ5H8TU3lcRDbS10xp8GY0xT9A=", "requires": { "angular": ">=1.2.x", "jquery": ">=3.1.x", @@ -21667,7 +1220,7 @@ "ansi-colors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "integrity": "sha1-Y3S03V1HGP884npnGjscrQdxMqk=", "dev": true, "requires": { "ansi-wrap": "^0.1.0" @@ -21718,7 +1271,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -21789,7 +1342,8 @@ "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true }, "argh": { "version": "0.1.4", @@ -21800,7 +1354,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", "dev": true, "requires": { "sprintf-js": "~1.0.2" @@ -21824,7 +1378,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "arr-map": { @@ -21874,7 +1428,7 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", "dev": true } } @@ -21882,7 +1436,7 @@ "array-last": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "integrity": "sha1-eqdwc/7FZd2rJJP1+IGF9ASp0zY=", "dev": true, "requires": { "is-number": "^4.0.0" @@ -21891,7 +1445,7 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", "dev": true } } @@ -21899,13 +1453,13 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", "dev": true }, "array-sort": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "integrity": "sha1-5MBTVkU/VvU1EqfR1hI/LFTAqIo=", "dev": true, "requires": { "default-compare": "^1.0.0", @@ -21934,7 +1488,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", "dev": true }, "asap": { @@ -21947,7 +1501,7 @@ "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", "requires": { "safer-buffer": "~2.1.0" } @@ -21966,13 +1520,13 @@ "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "integrity": "sha1-bIw/uCfdQ+45GPJ7gngqt2WKb9k=", "dev": true }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "integrity": "sha1-1yYl4jRKNlbjo61Pp0n6gymdgv8=", "dev": true, "requires": { "lodash": "^4.17.14" @@ -21981,7 +1535,7 @@ "async-done": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "integrity": "sha1-XhWqcplipLB0FPUoqIzfGOCykKI=", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -21993,13 +1547,13 @@ "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "integrity": "sha1-tyfb+H12UWAvBvTUrDh/R9kbDL8=", "dev": true }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + "integrity": "sha1-3TeelPDbgxCwgpH51kwyCXZmF/0=" }, "async-settle": { "version": "1.0.0", @@ -22018,13 +1572,13 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", "dev": true }, "autoprefixer": { "version": "9.6.5", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.5.tgz", - "integrity": "sha512-rGd50YV8LgwFQ2WQp4XzOTG69u1qQsXn0amww7tjqV5jJuNazgFKYEVItEBngyyvVITKOg20zr2V+9VsrXJQ2g==", + "integrity": "sha1-mPSv5+k8zPMjKHUV1CYBlhl3Xl4=", "dev": true, "requires": { "browserslist": "^4.7.0", @@ -22081,12 +1635,13 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -22435,7 +1990,7 @@ "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "integrity": "sha1-WYr+VHVbKGilMw0q/51Ou1Mgm2U=", "dev": true }, "bindings": { @@ -22462,18 +2017,19 @@ "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "integrity": "sha1-1oDu7yX4zZGtUz9bAe7UjmTK9oM=", "dev": true }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "integrity": "sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io=", "dev": true, "requires": { "bytes": "3.1.0", @@ -22491,7 +2047,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -22514,7 +2070,7 @@ "bootstrap": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", - "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==" + "integrity": "sha1-w6NH1Bniia0R9AM+PEEyuHwIHXI=" }, "bootstrap-social": { "version": "5.1.1", @@ -22528,7 +2084,8 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -22537,7 +2094,7 @@ "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", "dev": true, "requires": { "arr-flatten": "^1.1.0", @@ -22585,7 +2142,7 @@ "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", @@ -22595,7 +2152,7 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, "buffer-crc32": { @@ -22661,13 +2218,13 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "integrity": "sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY=", "dev": true }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -22778,7 +2335,7 @@ }, "callsites": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, @@ -22803,7 +2360,7 @@ "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "integrity": "sha1-Xk2Q4idJYdRikZl99Znj7QCO5MA=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -22849,7 +2406,7 @@ "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "integrity": "sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -22860,7 +2417,7 @@ "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "integrity": "sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=", "dev": true }, "chart.js": { @@ -22884,7 +2441,7 @@ "chartjs-color-string": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", - "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "integrity": "sha1-HfCWYhwOcHIKZPQTXqFx0FFAL3E=", "requires": { "color-name": "^1.0.0" } @@ -22892,7 +2449,7 @@ "chokidar": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "integrity": "sha1-gEs6e2qZNYw8XGHnHYco8EHP+Rc=", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -22912,7 +2469,7 @@ "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -22986,7 +2543,7 @@ "clean-css": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "integrity": "sha1-LUEe92uFabbQyEBo2r6FsKpeXBc=", "dev": true, "requires": { "source-map": "~0.6.0" @@ -22995,7 +2552,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -23003,7 +2560,7 @@ "cli-color": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "integrity": "sha1-fRBzj0hSaCT4/n2lGFfLD1cv4B8=", "dev": true, "requires": { "ansi-regex": "^2.1.1", @@ -23032,7 +2589,7 @@ "clipboard": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "integrity": "sha1-g22v1mzw/qXXHOXVsL9ulYAJES0=", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -23112,7 +2669,7 @@ "cloneable-readable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "integrity": "sha1-EgoAywU7+2OiIucJ+Wg+ouEdjOw=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -23123,7 +2680,7 @@ "coa": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "integrity": "sha1-Q/bCEVG07yv1cYfbDXPeIp4+fsM=", "dev": true, "requires": { "@types/q": "^1.5.1", @@ -23186,7 +2743,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=" }, "color-string": { "version": "1.6.0", @@ -23201,7 +2758,7 @@ "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "integrity": "sha1-k4NDeaHMmgxh+C9S8NBDIiUb1aI=", "dev": true }, "colornames": { @@ -23213,13 +2770,13 @@ "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "integrity": "sha1-xQSRR51MG9rtLJztMs98fcI2D3g=", "dev": true }, "colorspace": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "integrity": "sha1-4BKJUNCCuGohaFgHlqCqXWxo2MU=", "dev": true, "requires": { "color": "3.0.x", @@ -23229,7 +2786,7 @@ "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "integrity": "sha1-w9RaizT9cwYxoRCoolIGgrMdWn8=", "requires": { "delayed-stream": "~1.0.0" } @@ -23249,7 +2806,7 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "integrity": "sha1-FuQHD7qK4ptnnyIVhT7hgasuq8A=", "dev": true }, "component-inherit": { @@ -23261,12 +2818,13 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -23278,7 +2836,7 @@ "concat-with-sourcemaps": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "integrity": "sha1-1OqT8FriV5CVG5nns7CeOQikCC4=", "dev": true, "requires": { "source-map": "^0.6.1" @@ -23287,7 +2845,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -23296,6 +2854,8 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "optional": true, "requires": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -23304,7 +2864,7 @@ "connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "integrity": "sha1-XUk0iRDKpeB6AYALAw0MNfIEhPg=", "dev": true, "requires": { "debug": "2.6.9", @@ -23316,7 +2876,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -23340,7 +2900,7 @@ "consolidate": { "version": "0.15.1", "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", - "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "integrity": "sha1-IasEMjXHGgfUXZqtmFk7DbpWurc=", "dev": true, "requires": { "bluebird": "^3.1.1" @@ -23359,7 +2919,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", "dev": true }, "convert-source-map": { @@ -23414,12 +2974,13 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true }, "cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "integrity": "sha1-BA9yaAnFked6F8CjYmykW08Wixo=", "dev": true, "requires": { "import-fresh": "^2.0.0", @@ -23431,7 +2992,7 @@ "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "integrity": "sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q=", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -23444,7 +3005,7 @@ "css": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "integrity": "sha1-xkZ1XHOXHyu6amAeLPL9cbEpiSk=", "dev": true, "requires": { "inherits": "^2.0.3", @@ -23456,7 +3017,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -23470,7 +3031,7 @@ "css-declaration-sorter": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "integrity": "sha1-wZiUD2OnbX42wecQGLABchBUyyI=", "dev": true, "requires": { "postcss": "^7.0.1", @@ -23492,7 +3053,7 @@ "css-select-base-adapter": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "integrity": "sha1-Oy/0lyzDYquIVhUHqVQIoUMhNdc=", "dev": true }, "css-tree": { @@ -23528,7 +3089,7 @@ "cssnano": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "integrity": "sha1-CsQfCxPRPUZUh+ERt3jULaYxuLI=", "dev": true, "requires": { "cosmiconfig": "^5.0.0", @@ -23590,7 +3151,7 @@ "cssnano-util-raw-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "integrity": "sha1-sm1f1fcqEd/np4RvtMZyYPlr8oI=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -23599,7 +3160,7 @@ "cssnano-util-same-parent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "integrity": "sha1-V0CC+yhZ0ttDOFWDXZqEVuoYu/M=", "dev": true }, "csso": { @@ -23677,7 +3238,7 @@ "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "integrity": "sha1-hpgJU3LVjb7jRv/Qxwk/mfj561o=", "dev": true, "requires": { "es5-ext": "^0.10.50", @@ -23727,7 +3288,7 @@ "debug-fabulous": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "integrity": "sha1-r4oIYyRlIk70F0qfBjCMPCoevI4=", "dev": true, "requires": { "debug": "3.X", @@ -23761,7 +3322,8 @@ "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true }, "decompress": { "version": "4.2.1", @@ -23932,7 +3494,7 @@ "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "integrity": "sha1-y2ETGESthNhHiPto/QFoHKd4Gi8=", "dev": true, "requires": { "kind-of": "^5.0.2" @@ -23947,7 +3509,7 @@ "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=", "dev": true, "requires": { "object-keys": "^1.0.12" @@ -23956,7 +3518,7 @@ "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -23971,7 +3533,7 @@ "delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + "integrity": "sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=" }, "depd": { "version": "1.1.2", @@ -23988,7 +3550,8 @@ "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true }, "di": { "version": "0.0.1", @@ -23999,7 +3562,7 @@ "diagnostics": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "integrity": "sha1-yrasM99wydmnJ0kK5DrJladpsio=", "dev": true, "requires": { "colorspace": "1.1.x", @@ -24010,7 +3573,7 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=" }, "dir-glob": { "version": "3.0.1", @@ -24024,7 +3587,7 @@ "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "integrity": "sha1-rd6+rXKmV023g2OdyHoSF3OXOWE=", "dev": true, "requires": { "esutils": "^2.0.2" @@ -24069,7 +3632,7 @@ "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "integrity": "sha1-0EjESzew0Qp/Kj1f7j9DM9eQSB8=", "dev": true }, "domexception": { @@ -24092,7 +3655,7 @@ "domhandler": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "integrity": "sha1-iAUJfpM9ZehVRvcm1g9euItE+AM=", "dev": true, "requires": { "domelementtype": "1" @@ -24101,7 +3664,7 @@ "domutils": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "integrity": "sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo=", "dev": true, "requires": { "dom-serializer": "0", @@ -24215,7 +3778,7 @@ "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "integrity": "sha1-Kk31MX9sz9kfhtb9JdjYoQO4gwk=", "dev": true, "requires": { "end-of-stream": "^1.0.0", @@ -24227,7 +3790,7 @@ "each-props": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "integrity": "sha1-6kWkFNFt1c+kGbGoFyDVygaJIzM=", "dev": true, "requires": { "is-plain-object": "^2.0.1", @@ -24296,7 +3859,7 @@ "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "integrity": "sha1-WuZKX0UFe682JuwU2gyl5LJDHrA=", "dev": true, "requires": { "once": "^1.4.0" @@ -24441,7 +4004,7 @@ "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "integrity": "sha1-tKxAZIEH/c3PriQvQovqihTU8b8=", "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -24519,7 +4082,7 @@ "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "integrity": "sha1-TrIVlMlyvEBVPSduUQU5FD21Pgo=", "dev": true }, "es6-symbol": { @@ -24535,7 +4098,7 @@ "es6-weak-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "integrity": "sha1-ttofFswswNm+Q+a9v8Xn383zHVM=", "dev": true, "requires": { "d": "1", @@ -24696,7 +4259,7 @@ "eslint-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "integrity": "sha1-dP7HxU0Hdrb2fgJRBAtYBlZOmB8=", "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" @@ -24772,13 +4335,13 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "integrity": "sha1-OYrT88WiSUi+dyXoPRGn3ijNvR0=", "dev": true }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "integrity": "sha1-dNLrTeC42hKTcRkQ1Qd1ubcQ72Q=", "dev": true }, "event-emitter": { @@ -24916,7 +4479,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -25002,7 +4565,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", "dev": true, "requires": { "is-number": "^2.1.0", @@ -25091,7 +4654,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" }, "extend-shallow": { "version": "2.0.1", @@ -25105,7 +4668,7 @@ "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "integrity": "sha1-ywP3QL764D6k0oPK7SdBqD8zVJU=", "dev": true, "requires": { "chardet": "^0.7.0", @@ -25116,7 +4679,7 @@ "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -25148,7 +4711,7 @@ "fancy-log": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "integrity": "sha1-28GRVPVYaQFQojlToK29A1vkX8c=", "dev": true, "requires": { "ansi-gray": "^0.1.1", @@ -25281,7 +4844,7 @@ "file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "integrity": "sha1-yg9u+m3T1WEzP7FFFQZcL6/fQ5w=", "dev": true, "requires": { "flat-cache": "^2.0.1" @@ -25340,7 +4903,7 @@ "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "integrity": "sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0=", "dev": true, "requires": { "debug": "2.6.9", @@ -25355,7 +4918,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -25404,7 +4967,7 @@ "fined": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "integrity": "sha1-0AvszxqitHXRbUI7Aji3E6LEo3s=", "dev": true, "requires": { "expand-tilde": "^2.0.2", @@ -25437,13 +5000,13 @@ "flagged-respawn": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "integrity": "sha1-595vEnnd2cqarIpZcdYYYGs6q0E=", "dev": true }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "integrity": "sha1-XSltbwS9pEpGMKMBQTvbwuwIXsA=", "dev": true, "requires": { "flatted": "^2.0.0", @@ -25465,7 +5028,7 @@ "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "integrity": "sha1-jdfYc6G6vCB9lOrQwuDkQnbr8ug=", "dev": true, "requires": { "inherits": "^2.0.3", @@ -25506,7 +5069,7 @@ "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "integrity": "sha1-3M5SwF9kTymManq5Nr1yTO/786Y=", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -25570,7 +5133,7 @@ "fs-readfile-promise": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-3.0.1.tgz", - "integrity": "sha512-LsSxMeaJdYH27XrW7Dmq0Gx63mioULCRel63B5VeELYLavi1wF5s0XfsIdKDFdCL9hsfQ2qBvXJszQtQJ9h17A==", + "integrity": "sha1-0NMHt/au38kgwx+m5XEu+qKXyVg=", "dev": true, "requires": { "graceful-fs": "^4.1.11" @@ -25579,7 +5142,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "1.2.13", @@ -25595,7 +5159,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", @@ -25606,7 +5171,7 @@ "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "integrity": "sha1-+Xj6TJDR3+f/LWvtoqUV5xO9z0o=", "dev": true }, "get-intrinsic": { @@ -25777,6 +5342,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -25879,7 +5445,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -25903,7 +5469,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "integrity": "sha1-q4eVM4hooLq9hSV1gBjCp+uVxC4=", "dev": true }, "globby": { @@ -25939,7 +5505,7 @@ "glogg": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "integrity": "sha1-LX3XAr7aIus7/634gGltpthGMT8=", "dev": true, "requires": { "sparkles": "^1.0.0" @@ -25988,7 +5554,8 @@ "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true }, "growly": { "version": "1.3.0", @@ -25999,7 +5566,7 @@ "gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "integrity": "sha1-VDZRBw/Q9qsKBlDGo+b/WnywnKo=", "dev": true, "requires": { "glob-watcher": "^5.0.3", @@ -26024,7 +5591,7 @@ "gulp-babel": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", - "integrity": "sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ==", + "integrity": "sha1-4NqW9PLsSojdOjAw9HbjirISbYc=", "dev": true, "requires": { "plugin-error": "^1.0.1", @@ -26036,7 +5603,7 @@ "gulp-clean-css": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-4.2.0.tgz", - "integrity": "sha512-r4zQsSOAK2UYUL/ipkAVCTRg/2CLZ2A+oPVORopBximRksJ6qy3EX1KGrIWT4ZrHxz3Hlobb1yyJtqiut7DNjA==", + "integrity": "sha1-kV7CWNxtPmpQBD9hAGbVwurE9U4=", "dev": true, "requires": { "clean-css": "4.2.1", @@ -26048,7 +5615,7 @@ "through2": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "integrity": "sha1-OSducTwzAu3544jdnIEt07glvVo=", "dev": true, "requires": { "readable-stream": "2 || 3" @@ -26096,7 +5663,7 @@ "gulp-eslint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", - "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", + "integrity": "sha1-fUArtF+KZ2UrhoJ3ARgSBXNwqDI=", "dev": true, "requires": { "eslint": "^6.0.0", @@ -26171,7 +5738,7 @@ "gulp-less": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz", - "integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==", + "integrity": "sha1-NIwzpd3nogfFdxsdgmHRrBAhzu0=", "dev": true, "requires": { "accord": "^0.29.0", @@ -26216,7 +5783,7 @@ }, "kind-of": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", "dev": true }, @@ -26304,7 +5871,7 @@ "gulp-notify": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/gulp-notify/-/gulp-notify-3.2.0.tgz", - "integrity": "sha512-qEocs1UVoDKKUjfsxJNMNwkRla0PbsyJwsqNNXpzYWsLQ29LhxRMY3wnTGZcc4hMHtalnvah/Dwlwb4NijH/0A==", + "integrity": "sha1-KugiUAnfiB7vWb5d1aLxM3OHdk4=", "dev": true, "requires": { "ansi-colors": "^1.0.1", @@ -26371,7 +5938,7 @@ "gulp-postcss": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-8.0.0.tgz", - "integrity": "sha512-Wtl6vH7a+8IS/fU5W9IbOpcaLqKxd5L1DUOzaPmlnCbX1CrG0aWdwVnC3Spn8th0m8D59YbysV5zPUe1n/GJYg==", + "integrity": "sha1-jTdyzU0nvKVeyMtMjlduO95NxVA=", "dev": true, "requires": { "fancy-log": "^1.3.2", @@ -26384,7 +5951,7 @@ "gulp-rename": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", - "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", + "integrity": "sha1-3hxxjnxAla6GH3KW708ySGSCQL0=", "dev": true }, "gulp-sort": { @@ -26399,7 +5966,7 @@ "gulp-sourcemaps": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", - "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", + "integrity": "sha1-o/AC2HNG0sDzrsNq9+uHPyPeiuY=", "dev": true, "requires": { "@gulp-sourcemaps/identity-map": "1.X", @@ -26424,7 +5991,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -26463,7 +6030,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -26556,7 +6123,7 @@ "gulp-watch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/gulp-watch/-/gulp-watch-5.0.1.tgz", - "integrity": "sha512-HnTSBdzAOFIT4wmXYPDUn783TaYAq9bpaN05vuZNP5eni3z3aRx0NAKbjhhMYtcq76x4R1wf4oORDGdlrEjuog==", + "integrity": "sha1-g9N4dS9b+0baAj5zwX7R2nBmIV0=", "dev": true, "requires": { "ansi-colors": "1.1.0", @@ -26697,7 +6264,7 @@ "gulp-wrap": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/gulp-wrap/-/gulp-wrap-0.15.0.tgz", - "integrity": "sha512-f17zkGObA+hE/FThlg55gfA0nsXbdmHK1WqzjjB2Ytq1TuhLR7JiCBJ3K4AlMzCyoFaCjfowos+VkToUNE0WTQ==", + "integrity": "sha1-6QFMm7hkOrMQ6TjURpuFaFUaVS8=", "dev": true, "requires": { "consolidate": "^0.15.1", @@ -26784,7 +6351,8 @@ "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -26807,7 +6375,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", "dev": true, "requires": { "isarray": "2.0.1" @@ -26909,13 +6477,13 @@ "hex-color-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "integrity": "sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=", "dev": true }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "integrity": "sha1-dDKYzvTlrz4ZQWH7rcwhUdOgWOg=", "dev": true, "requires": { "parse-passwd": "^1.0.0" @@ -26924,7 +6492,8 @@ "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true }, "hsl-regex": { "version": "1.0.0", @@ -26971,7 +6540,7 @@ "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "integrity": "sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8=", "dev": true, "requires": { "depd": "~1.1.2", @@ -27020,7 +6589,7 @@ "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -27036,7 +6605,7 @@ "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "integrity": "sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=", "dev": true }, "image-size": { @@ -27146,7 +6715,8 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true }, "indent-string": { "version": "2.1.0", @@ -27180,6 +6750,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -27188,12 +6759,14 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=", + "dev": true }, "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true }, "inquirer": { "version": "7.3.3", @@ -27307,7 +6880,7 @@ "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", "dev": true, "requires": { "loose-envify": "^1.0.0" @@ -27334,13 +6907,13 @@ "is": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", + "integrity": "sha1-Yc/23TxBk9uUo9YlggcrROVkXXk=", "dev": true }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -27407,7 +6980,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", "dev": true }, "is-callable": { @@ -27434,6 +7007,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, "requires": { "has": "^1.0.3" } @@ -27682,7 +7256,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -27691,7 +7265,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", "dev": true }, "is-retry-allowed": { @@ -27750,7 +7324,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -27780,7 +7354,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", "dev": true }, "is-wsl": { @@ -27792,7 +7366,8 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isbinaryfile": { "version": "3.0.3", @@ -27806,7 +7381,8 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { "version": "3.0.1", @@ -27833,7 +7409,7 @@ "jasmine-core": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", + "integrity": "sha1-Eywj5kWvlthci8oTyHWLGEKfweQ=", "dev": true }, "jquery": { @@ -27854,13 +7430,13 @@ "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "integrity": "sha1-xs7ljrNVA3LfjeuF+tXOZs4B1Z0=", "dev": true }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "integrity": "sha1-GSA/tZmR35jjoocFDUZHzerzJJk=", "dev": true }, "js-yaml": { @@ -27924,15 +7500,14 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "dev": true, - "requires": {} + "dev": true } } }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "integrity": "sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=", "dev": true }, "json-buffer": { @@ -27945,7 +7520,8 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=", + "dev": true }, "json-schema": { "version": "0.2.3", @@ -27955,7 +7531,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -28062,7 +7638,7 @@ "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=", "dev": true, "requires": { "fill-range": "^7.0.1" @@ -28087,7 +7663,7 @@ "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -28112,7 +7688,7 @@ "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=", "dev": true, "requires": { "binary-extensions": "^2.0.0" @@ -28121,7 +7697,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=", "dev": true }, "readdirp": { @@ -28142,7 +7718,7 @@ "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", "dev": true, "requires": { "is-number": "^7.0.0" @@ -28153,7 +7729,7 @@ "karma-jasmine": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", + "integrity": "sha1-JuPjHy+vJy3YDrsOGJiRTMOhl2M=", "dev": true, "requires": { "jasmine-core": "^3.3" @@ -28163,13 +7739,12 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/karma-jsdom-launcher/-/karma-jsdom-launcher-8.0.2.tgz", "integrity": "sha512-jxO+Nf9U/XNSnHXrNpxBbbMyeYuvJH1V++bRdZv20vJ9pvaLuQ6LFNIgn4hA1WAVmzMsvW9j0P2Q2hTLMWcSvw==", - "dev": true, - "requires": {} + "dev": true }, "karma-junit-reporter": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", - "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", + "integrity": "sha1-007vfwsv0GTgiWlU6IUakM8UyPM=", "dev": true, "requires": { "path-is-absolute": "^1.0.0", @@ -28204,7 +7779,7 @@ "kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "integrity": "sha1-73x4TzbJ+24W3TFQ0VJneysCKKY=", "dev": true, "requires": { "colornames": "^1.1.1" @@ -28261,7 +7836,7 @@ "less": { "version": "3.10.3", "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", - "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", + "integrity": "sha1-QXoJddXu7MUs/0vPo8CdNXgeZ5I=", "dev": true, "requires": { "clone": "^2.1.2", @@ -28285,7 +7860,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true, "optional": true } @@ -28385,7 +7960,8 @@ "lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true }, "lodash._isiterateecall": { "version": "3.0.9", @@ -28476,7 +8052,7 @@ "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "integrity": "sha1-VYqlO0O2YeGSWgr9+japoQhf5Xo=", "dev": true }, "lodash.partialright": { @@ -28494,7 +8070,8 @@ "lodash.restparam": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true }, "lodash.template": { "version": "4.5.0", @@ -28518,7 +8095,8 @@ "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true }, "log4js": { "version": "4.5.1", @@ -28566,7 +8144,7 @@ "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "integrity": "sha1-ce5R+nvkyuwaY4OffmgtgTLTDK8=", "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -28606,7 +8184,8 @@ "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "integrity": "sha1-i75Q6oW+1ZvJ4z3KuCNe6bz0Q80=", + "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -28641,7 +8220,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -28680,7 +8259,7 @@ "marked": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "integrity": "sha1-tkIB8FHScbHtwQoE0a6bdLuOXA4=", "dev": true }, "matchdep": { @@ -28721,13 +8300,13 @@ "math-random": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "integrity": "sha1-XdaUPJOFSCZwFtTjTwV1gwgMUUw=", "dev": true }, "mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "integrity": "sha1-aZs8OKxvHXKAkaZGULZdOIUC/Vs=", "dev": true }, "media-typer": { @@ -28774,7 +8353,7 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "integrity": "sha1-UoI2KaFN0AyXcPtq1H3GMQ8sH2A=", "dev": true }, "merge2": { @@ -28786,7 +8365,7 @@ "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -28875,7 +8454,8 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -28883,12 +8463,13 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true }, "minimize": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/minimize/-/minimize-2.2.0.tgz", - "integrity": "sha512-IxR2XMbw9pXCxApkdD9BTcH2U4XlXhbeySUrv71rmMS9XDA8BVXEsIuFu24LtwCfBgfbL7Fuh8/ZzkO5DaTLlQ==", + "integrity": "sha1-ixZ28wBR2FmNdDZGvRJpCwdNpMM=", "dev": true, "requires": { "argh": "^0.1.4", @@ -28903,7 +8484,7 @@ "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "integrity": "sha1-ESC0PcNZp4Xc5ltVuC4lfM9HlWY=", "dev": true, "requires": { "for-in": "^1.0.2", @@ -28913,7 +8494,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -28934,6 +8515,7 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, "requires": { "minimist": "^1.2.5" } @@ -28958,7 +8540,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", "dev": true }, "multipipe": { @@ -28973,7 +8555,7 @@ "mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "integrity": "sha1-rLAwDrTeI6fd7sAU4+lgRLNHIzE=", "dev": true }, "mute-stream": { @@ -28992,7 +8574,7 @@ "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -29053,7 +8635,7 @@ "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "integrity": "sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs=", "dev": true }, "next-tick": { @@ -29070,7 +8652,7 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "integrity": "sha1-ozeKdpbOfSI+iPybdkvX7xCJ42Y=", "dev": true }, "node-notifier": { @@ -29095,7 +8677,7 @@ "node.extend": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", - "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", + "integrity": "sha1-tEBFJUlKzJl0DzcDxJa31Rgsxsw=", "dev": true, "requires": { "has": "^1.0.3", @@ -29105,7 +8687,8 @@ "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "integrity": "sha1-5m2xg4sgDB38IzIl0SyzZSDiNKg=", + "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -29128,18 +8711,18 @@ "normalize-url": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "integrity": "sha1-suHE3E98bVd0PfczpPWXjRhlBVk=", "dev": true }, "nouislider": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.4.0.tgz", - "integrity": "sha512-AV7UMhGhZ4Mj6ToMT812Ib8OJ4tAXR2/Um7C4l4ZvvsqujF0WpQTpqqHJ+9xt4174R7ueQOUrBR4yakJpAIPCA==" + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.5.0.tgz", + "integrity": "sha512-p0Rn0a4XzrBJ+JZRhNDYpRYr6sDPkajsjbvEQoTp/AZlNI3NirO15s1t11D25Gk3zVyvNJAzc1DO48cq/KX5Sw==" }, "now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "integrity": "sha1-jlechoV2SnzALLaAOA6U9DzLH3w=", "dev": true, "requires": { "once": "^1.3.2" @@ -29150,6 +8733,7 @@ "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.15.tgz", "integrity": "sha512-dkcQc4n+DiJAMYG2haNAMyJbmuvevjXz+WC9dCUzodw8EovwTIc6CATSsTEplCY6c0jG4OshxFGFJsrnKJguWA==", "requires": { + "JSONStream": "^1.3.5", "abbrev": "~1.1.1", "ansicolors": "~0.3.2", "ansistyles": "~0.1.3", @@ -29190,7 +8774,6 @@ "init-package-json": "^1.10.3", "is-cidr": "^3.0.0", "json-parse-better-errors": "^1.0.2", - "JSONStream": "^1.3.5", "lazy-property": "~1.0.0", "libcipm": "^4.0.8", "libnpm": "^3.0.1", @@ -29275,6 +8858,14 @@ "write-file-atomic": "^2.4.3" }, "dependencies": { + "JSONStream": { + "version": "1.3.5", + "bundled": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "abbrev": { "version": "1.1.1", "bundled": true @@ -30525,14 +10116,6 @@ "version": "1.3.1", "bundled": true }, - "JSONStream": { - "version": "1.3.5", - "bundled": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "jsprim": { "version": "1.4.1", "bundled": true, @@ -31072,9 +10655,9 @@ "version": "4.0.7", "bundled": true, "requires": { + "JSONStream": "^1.3.4", "bluebird": "^3.5.1", "figgy-pudding": "^3.4.1", - "JSONStream": "^1.3.4", "lru-cache": "^5.1.1", "make-fetch-happen": "^5.0.0", "npm-package-arg": "^6.1.0", @@ -31753,19 +11336,6 @@ "version": "2.0.0", "bundled": true }, - "string_decoder": { - "version": "1.3.0", - "bundled": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "bundled": true - } - } - }, "string-width": { "version": "2.1.1", "bundled": true, @@ -31791,6 +11361,19 @@ } } }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true + } + } + }, "stringify-package": { "version": "1.0.1", "bundled": true @@ -32280,7 +11863,7 @@ "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "integrity": "sha1-sr0pXDfj3VijvwcAN2Zjuk2c8Fw=", "dev": true, "requires": { "boolbase": "~1.0.0" @@ -32307,12 +11890,13 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=" }, "object-assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", - "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=" + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true }, "object-component": { "version": "0.0.3", @@ -32397,7 +11981,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "integrity": "sha1-HEfyct8nfzsdrwYWd9nILiMixg4=", "dev": true }, "object-visit": { @@ -32518,6 +12102,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -32604,7 +12189,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -32684,7 +12269,7 @@ "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "integrity": "sha1-aR0nCeeMefrjoVZiJFLQB2LKqqI=", "dev": true, "requires": { "callsites": "^3.0.0" @@ -32693,7 +12278,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "integrity": "sha1-s2MKvYlDQy9Us/BRkjjjPNffL3M=", "dev": true } } @@ -32751,7 +12336,7 @@ "parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "integrity": "sha1-4rXb7eAOf6m8NjYH9TMn6LBzGJs=", "dev": true }, "parse-passwd": { @@ -32787,7 +12372,7 @@ "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=", "dev": true }, "pascalcase": { @@ -32814,7 +12399,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-key": { "version": "2.0.1", @@ -32825,7 +12411,8 @@ "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "path-root": { "version": "0.1.1", @@ -32897,7 +12484,7 @@ "plugin-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "integrity": "sha1-dwFr2JGdCsN3/c3QMiMolTyleBw=", "dev": true, "requires": { "ansi-colors": "^1.0.1", @@ -32989,7 +12576,7 @@ "postcss-colormin": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "integrity": "sha1-rgYLzpPteUrHEmTwgTLVUJVr04E=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -33010,7 +12597,7 @@ "postcss-convert-values": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "integrity": "sha1-yjgT7U2g+BL51DcDWE5Enr4Ymn8=", "dev": true, "requires": { "postcss": "^7.0.0", @@ -33028,7 +12615,7 @@ "postcss-discard-comments": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "integrity": "sha1-H7q9LCRr/2qq15l7KwkY9NevQDM=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -33037,7 +12624,7 @@ "postcss-discard-duplicates": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "integrity": "sha1-P+EzzTyCKC5VD8myORdqkge3hOs=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -33046,7 +12633,7 @@ "postcss-discard-empty": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "integrity": "sha1-yMlR6fc+2UKAGUWERKAq2Qu592U=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -33055,7 +12642,7 @@ "postcss-discard-overridden": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "integrity": "sha1-ZSrvipZybwKfXj4AFG7npOdV/1c=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -33074,7 +12661,7 @@ "postcss-merge-longhand": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "integrity": "sha1-YvSaE+Sg7gTnuY9CuxYGLKJUniQ=", "dev": true, "requires": { "css-color-names": "0.0.4", @@ -33094,7 +12681,7 @@ "postcss-merge-rules": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "integrity": "sha1-NivqT/Wh+Y5AdacTxsslrv75plA=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -33121,7 +12708,7 @@ "postcss-minify-font-values": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "integrity": "sha1-zUw0TM5HQ0P6xdgiBqssvLiv1aY=", "dev": true, "requires": { "postcss": "^7.0.0", @@ -33139,7 +12726,7 @@ "postcss-minify-gradients": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "integrity": "sha1-k7KcL/UJnFNe7NpWxKpuZlpmNHE=", "dev": true, "requires": { "cssnano-util-get-arguments": "^4.0.0", @@ -33159,7 +12746,7 @@ "postcss-minify-params": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "integrity": "sha1-a5zvAwwR41Jh+V9hjJADbWgNuHQ=", "dev": true, "requires": { "alphanum-sort": "^1.0.0", @@ -33181,7 +12768,7 @@ "postcss-minify-selectors": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "integrity": "sha1-4uXrQL/uUA0M2SQ1APX46kJi+9g=", "dev": true, "requires": { "alphanum-sort": "^1.0.0", @@ -33206,7 +12793,7 @@ "postcss-normalize-charset": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "integrity": "sha1-izWt067oOhNrBHHg1ZvlilAoXdQ=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -33215,7 +12802,7 @@ "postcss-normalize-display-values": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "integrity": "sha1-Db4EpM6QY9RmftK+R2u4MMglk1o=", "dev": true, "requires": { "cssnano-util-get-match": "^4.0.0", @@ -33234,7 +12821,7 @@ "postcss-normalize-positions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "integrity": "sha1-BfdX+E8mBDc3g2ipH4ky1LECkX8=", "dev": true, "requires": { "cssnano-util-get-arguments": "^4.0.0", @@ -33254,7 +12841,7 @@ "postcss-normalize-repeat-style": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "integrity": "sha1-xOu8KJ85kaAo1EdRy90RkYsXkQw=", "dev": true, "requires": { "cssnano-util-get-arguments": "^4.0.0", @@ -33274,7 +12861,7 @@ "postcss-normalize-string": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "integrity": "sha1-zUTECrB6DHo23F6Zqs4eyk7CaQw=", "dev": true, "requires": { "has": "^1.0.0", @@ -33293,7 +12880,7 @@ "postcss-normalize-timing-functions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "integrity": "sha1-jgCcoqOUnNr4rSPmtquZy159KNk=", "dev": true, "requires": { "cssnano-util-get-match": "^4.0.0", @@ -33312,7 +12899,7 @@ "postcss-normalize-unicode": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "integrity": "sha1-hBvUj9zzAZrUuqdJOj02O1KuHPs=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -33331,7 +12918,7 @@ "postcss-normalize-url": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "integrity": "sha1-EOQ3+GvHx+WPe5ZS7YeNqqlfquE=", "dev": true, "requires": { "is-absolute-url": "^2.0.0", @@ -33351,7 +12938,7 @@ "postcss-normalize-whitespace": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "integrity": "sha1-vx1AcP5Pzqh9E0joJdjMDF+qfYI=", "dev": true, "requires": { "postcss": "^7.0.0", @@ -33369,7 +12956,7 @@ "postcss-ordered-values": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "integrity": "sha1-DPdcgg7H1cTSgBiVWeC1ceusDu4=", "dev": true, "requires": { "cssnano-util-get-arguments": "^4.0.0", @@ -33388,7 +12975,7 @@ "postcss-reduce-initial": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "integrity": "sha1-f9QuvqXpyBRgljniwuhK4nC6SN8=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -33400,7 +12987,7 @@ "postcss-reduce-transforms": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "integrity": "sha1-F++kBerMbge+NBSlyi0QdGgdTik=", "dev": true, "requires": { "cssnano-util-get-match": "^4.0.0", @@ -33449,7 +13036,7 @@ "postcss-unique-selectors": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "integrity": "sha1-lEaRHzKJv9ZMbWgPBzwDsfnuS6w=", "dev": true, "requires": { "alphanum-sort": "^1.0.0", @@ -33490,25 +13077,26 @@ }, "pretty-hrtime": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I=", + "dev": true }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "integrity": "sha1-foz42PW48jnBvGi+tOt4Vn1XLvg=", "dev": true }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "dev": true, "optional": true, "requires": { @@ -33518,7 +13106,9 @@ "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true, + "optional": true }, "prr": { "version": "1.0.1", @@ -33530,7 +13120,8 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true }, "psl": { "version": "1.8.0", @@ -33551,7 +13142,7 @@ "pumpify": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=", "dev": true, "requires": { "duplexify": "^3.6.0", @@ -33574,7 +13165,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" }, "q": { "version": "1.5.1", @@ -33585,7 +13176,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", "dev": true }, "qs": { @@ -33598,6 +13189,8 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "optional": true, "requires": { "decode-uri-component": "^0.2.0", "object-assign": "^4.1.0", @@ -33618,7 +13211,7 @@ "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "integrity": "sha1-t3bvxZN1mE42xTey9RofCv8Noe0=", "dev": true, "requires": { "is-number": "^4.0.0", @@ -33629,7 +13222,7 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", "dev": true }, "kind-of": { @@ -33643,13 +13236,13 @@ "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "integrity": "sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=", "dev": true }, "raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "integrity": "sha1-oc5vucm8NWylLoklarWQWeE9AzI=", "dev": true, "requires": { "bytes": "3.1.0", @@ -33702,6 +13295,7 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -33715,7 +13309,7 @@ "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=", "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -33776,7 +13370,7 @@ "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", "dev": true, "requires": { "is-equal-shallow": "^0.1.3" @@ -33785,7 +13379,7 @@ "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", "dev": true, "requires": { "extend-shallow": "^3.0.2", @@ -33825,7 +13419,7 @@ "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "integrity": "sha1-jRnTHPYySCtYkEn4KB+T28uk0H8=", "dev": true }, "regexpu-core": { @@ -33868,7 +13462,7 @@ "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "integrity": "sha1-wr8eN3Ug0yT2I4kuM8EMrCwlK1M=", "dev": true, "requires": { "is-buffer": "^1.1.5", @@ -34027,6 +13621,7 @@ "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, "requires": { "is-core-module": "^2.2.0", "path-parse": "^1.0.6" @@ -34086,7 +13681,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", "dev": true }, "reusify": { @@ -34125,7 +13720,8 @@ "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "integrity": "sha1-stEE/g2Psnz54KHNqCYt04M8bKs=", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -34148,7 +13744,7 @@ "run-sequence": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", - "integrity": "sha512-qkzZnQWMZjcKbh3CNly2srtrkaO/2H/SI5f2eliMCapdRD3UhMrwjfOAZJAnZ2H8Ju4aBzFZkBGXUqFs9V0yxw==", + "integrity": "sha1-HOZD2jb9jH6n4akynaM/wriJhJU=", "dev": true, "requires": { "chalk": "^1.1.3", @@ -34186,7 +13782,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -34208,7 +13804,7 @@ }, "kind-of": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", "dev": true }, @@ -34254,7 +13850,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" }, "safe-regex": { "version": "1.1.0", @@ -34268,12 +13864,12 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=", "dev": true }, "saxes": { @@ -34303,7 +13899,8 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=", + "dev": true }, "semver-greatest-satisfied-range": { "version": "1.1.0", @@ -34340,7 +13937,7 @@ "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "integrity": "sha1-oY1AUw5vB95CKMfe/kInr4ytAFs=", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -34363,7 +13960,7 @@ "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "integrity": "sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM=", "dev": true }, "shebang-command": { @@ -34384,7 +13981,7 @@ "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "integrity": "sha1-1rkYHBpI05cyTISHHvvPxz/AZUs=", "dev": true }, "side-channel": { @@ -34416,7 +14013,7 @@ "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "integrity": "sha1-RXSirlb3qyBolvtDHq7tBm/fjwM=", "dev": true } } @@ -34430,7 +14027,7 @@ "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "integrity": "sha1-ys12k0YaY3pXiNkqfdT7oGjoFjY=", "dev": true, "requires": { "ansi-styles": "^3.2.0", @@ -34449,7 +14046,7 @@ "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", "dev": true, "requires": { "base": "^0.11.1", @@ -34465,7 +14062,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -34542,7 +14139,7 @@ "snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", "dev": true, "requires": { "define-property": "^1.0.0", @@ -34564,7 +14161,7 @@ "snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", "dev": true, "requires": { "kind-of": "^3.2.0" @@ -34769,13 +14366,14 @@ "sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "integrity": "sha1-AI22XtzmxQ7sDF4ijhlFBh3QQ3w=", "dev": true }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -34784,12 +14382,14 @@ "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true }, "spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -34798,7 +14398,8 @@ "spdx-license-ids": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==" + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "dev": true }, "spectrum-colorpicker2": { "version": "2.0.8", @@ -34808,7 +14409,7 @@ "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", "dev": true, "requires": { "extend-shallow": "^3.0.0" @@ -34871,7 +14472,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "optional": true, @@ -34905,7 +14506,7 @@ "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "integrity": "sha1-+2YcC+8ps520B2nuOfpwCT1vaHc=", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -34921,7 +14522,7 @@ "stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "integrity": "sha1-g26zyDgv4pNv6vVEYxAXzn1Ho88=", "dev": true }, "stack-trace": { @@ -35017,7 +14618,7 @@ "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "integrity": "sha1-rNrI2lnvK8HheiwMz2wyDRIOVV0=", "dev": true }, "stream-shift": { @@ -35053,15 +14654,9 @@ "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true, + "optional": true }, "string-width": { "version": "4.2.3", @@ -35111,6 +14706,15 @@ "define-properties": "^1.1.3" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -35213,7 +14817,7 @@ "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "integrity": "sha1-Zxj8r00eB9ihMYaQiB6NlnJqcdU=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -35237,7 +14841,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -35283,7 +14887,7 @@ "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "integrity": "sha1-EpLRlQDOP4YFOwXw6Ofko7shB54=", "dev": true, "requires": { "ajv": "^6.10.2", @@ -35307,7 +14911,7 @@ "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "integrity": "sha1-InZ74htirxCBV0MG9prFG2IgOWE=", "dev": true, "requires": { "emoji-regex": "^7.0.1", @@ -35373,13 +14977,14 @@ "text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "integrity": "sha1-adycGxdEbueakr9biEu0uRJ1BvU=", "dev": true }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true }, "through": { "version": "2.3.8", @@ -35390,7 +14995,7 @@ "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", "dev": true, "requires": { "readable-stream": "~2.3.6", @@ -35409,7 +15014,7 @@ "through2-filter": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "integrity": "sha1-cA54bfI2fCyIzYqlvkz5weeDElQ=", "dev": true, "requires": { "through2": "~2.0.0", @@ -35432,7 +15037,7 @@ "timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "integrity": "sha1-b1ethXjgej+5+R2Th9ZWR1VeJcY=", "dev": true, "requires": { "es5-ext": "~0.10.46", @@ -35448,7 +15053,7 @@ "tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + "integrity": "sha1-HRpW7fxRxD6GPLtTgqcjMONVVCM=" }, "tinymce": { "version": "4.9.11", @@ -35458,7 +15063,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -35516,7 +15121,7 @@ "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", "dev": true, "requires": { "define-property": "^2.0.2", @@ -35577,7 +15182,7 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "integrity": "sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM=", "dev": true }, "tough-cookie": { @@ -35645,7 +15250,7 @@ "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "integrity": "sha1-hI3XaY2vo+VKbEeedZxLw/GIR6A=", "dev": true }, "type-check": { @@ -35666,7 +15271,7 @@ "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "integrity": "sha1-TlUs0F3wlGfcvE73Od6J8s83wTE=", "dev": true, "requires": { "media-typer": "0.3.0", @@ -35845,7 +15450,7 @@ "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "integrity": "sha1-C2/nuDWuzaYcbqTU8CwUIh4QmEc=", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -35869,7 +15474,7 @@ "unique-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "integrity": "sha1-xl0RDppK35psWUiygFPZqNBMvqw=", "dev": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", @@ -35879,13 +15484,14 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=", "dev": true }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true }, "unquote": { "version": "1.1.1", @@ -35936,7 +15542,7 @@ "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "integrity": "sha1-j2bbzVWog6za5ECK+LA1pQRMGJQ=", "dev": true }, "uri-js": { @@ -35982,7 +15588,7 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", "dev": true }, "useragent": { @@ -35998,7 +15604,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "util.promisify": { "version": "1.0.1", @@ -36041,7 +15648,8 @@ "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "integrity": "sha1-/JH2uce6FchX9MssXe/uw51PQQo=", + "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -36153,7 +15761,7 @@ "vinyl-fs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "integrity": "sha1-yFhJQF9nQo/qu71cXb3WT0fTG8c=", "dev": true, "requires": { "fs-mkdirp-stream": "^1.0.0", @@ -36275,7 +15883,8 @@ "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -36324,7 +15933,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -36366,12 +15975,13 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "integrity": "sha1-CADhRSO5I6OH5BUSPIZWFqrg9cM=", "dev": true, "requires": { "mkdirp": "^0.5.1" @@ -36394,7 +16004,7 @@ "xmlbuilder": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", - "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", + "integrity": "sha1-4u1nXgaDSgid37hNuW4sKwP3jBo=", "dev": true }, "xmlchars": { @@ -36412,7 +16022,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "integrity": "sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q=", "dev": true }, "y18n": { @@ -36424,7 +16034,8 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true }, "yargs": { "version": "7.1.2", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index ec4ead689c..43d7a3cecd 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -44,7 +44,7 @@ "lazyload-js": "1.0.0", "moment": "2.22.2", "ng-file-upload": "12.2.13", - "nouislider": "15.4.0", + "nouislider": "15.5.0", "npm": "^6.14.7", "spectrum-colorpicker2": "2.0.8", "tinymce": "4.9.11", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 4a1988cc27..50a32e0b05 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -200,6 +200,10 @@ } })); + evts.push(eventsService.on("rte.shortcut.saveAndPublish", function () { + $scope.saveAndPublish(); + })); + evts.push(eventsService.on("content.saved", function () { // Clear out localstorage keys that start with tinymce__ // When we save/perist a content node diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index 0ac285c094..1b63dde26c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -251,6 +251,7 @@ Use this directive to construct a header inside the main editor window. var iconPicker = { icon: scope.icon.split(' ')[0], color: scope.icon.split(' ')[1], + size: "medium", submit: function (model) { if (model.icon) { if (model.color) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js index e1639dde26..9bd1909960 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js @@ -35,9 +35,6 @@ // List of elements that can be focusable within the focus lock var focusableElementsSelector = '[role="button"], a[href]:not([disabled]):not(.ng-hide), button:not([disabled]):not(.ng-hide), textarea:not([disabled]):not(.ng-hide), input:not([disabled]):not(.ng-hide), select:not([disabled]):not(.ng-hide)'; - // Grab the body element so we can add the tabbing class on it when needed - var bodyElement = document.querySelector('body'); - function getDomNodes(){ infiniteEditorsWrapper = document.querySelector('.umb-editors'); if(infiniteEditorsWrapper) { @@ -47,7 +44,10 @@ function getFocusableElements(targetElm) { var elm = targetElm ? targetElm : target; - focusableElements = elm.querySelectorAll(focusableElementsSelector); + + // Filter out elements that are children of parents with the .ng-hide class + focusableElements = [...elm.querySelectorAll(focusableElementsSelector)].filter(elm => !elm.closest('.ng-hide')); + // Set first and last focusable elements firstFocusableElement = focusableElements[0]; lastFocusableElement = focusableElements[focusableElements.length - 1]; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js index e957d78660..2262da4c16 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js @@ -1,5 +1,4 @@ -angular.module("umbraco.directives") - +angular.module('umbraco.directives') /** * @ngdoc directive * @name umbraco.directives.directive:localize @@ -8,12 +7,12 @@ angular.module("umbraco.directives") * @description *
* Component
- * Localize a specific token to put into the HTML as an item + * Localize a specific token to put into the HTML as an item. *
*
* Attribute
- * Add a HTML attribute to an element containing the HTML attribute name you wish to localise - * Using the format of '@section_key' or 'section_key' + * Add an HTML attribute to an element containing the HTML attribute name you wish to localize, + * using the format of '@section_key' or 'section_key'. *
* ##Usage *
@@ -36,12 +35,11 @@ angular.module("umbraco.directives")
                 watchTokens: '@'
             },
             replace: true,
-
             link: function (scope, element, attrs) {
                 var key = scope.key;
-                scope.text = "";
+                scope.text = '';
 
-                // A render function to be able to update tokens as values update.
+                // A render function to be able to update tokens as values update
                 function render() {
                     element.html(localizationService.tokenReplace(scope.text, scope.tokens || null));
                 }
@@ -50,26 +48,28 @@ angular.module("umbraco.directives")
                     scope.text = value;
                     render();
                 });
+
                 if (scope.watchTokens === 'true') {
                     scope.$watch("tokens", render, true);
                 }
             }
         };
     })
-
     .directive('localize', function ($log, localizationService) {
         return {
             restrict: 'A',
             link: function (scope, element, attrs) {
-                //Support one or more attribute properties to update
+                // Support one or more attribute properties to update
                 var keys = attrs.localize.split(',');
 
                 Utilities.forEach(keys, (value, key) => {
                     var attr = element.attr(value);
-
                     if (attr) {
+                        // Localizing is done async, so make sure the key isn't visible
+                        element.removeAttr(value);
+                        
                         if (attr[0] === '@') {
-                            //If the translation key starts with @ then remove it
+                            // If the translation key starts with @ then remove it
                             attr = attr.substring(1);
                         }
 
@@ -82,5 +82,4 @@ angular.module("umbraco.directives")
                 });
             }
         };
-
     });
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js
index 47441326d7..7870267995 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js
@@ -118,7 +118,7 @@ Use this directive to render a ui component for selecting child items to a paren
 (function() {
     'use strict';
 
-    function ChildSelectorDirective() {
+    function ChildSelectorDirective(overlayService, localizationService) {
 
         function link(scope, el, attr, ctrl) {
 
@@ -126,10 +126,30 @@ Use this directive to render a ui component for selecting child items to a paren
             scope.dialogModel = {};
             scope.showDialog = false;
 
-            scope.removeChild = (selectedChild, $index) => {
-               if (scope.onRemove) {
-                  scope.onRemove(selectedChild, $index);
-               }
+            scope.removeChild = (selectedChild, $index, event) => {
+               const dialog = {
+                    view: "views/components/overlays/umb-template-remove-confirm.html",
+                    layout: selectedChild,
+                    submitButtonLabelKey: "defaultdialogs_yesRemove",
+                    submitButtonStyle: "danger",
+                    submit: function () {
+                        if(scope.onRemove) {
+                            scope.onRemove(selectedChild, $index);
+                            overlayService.close();
+                        }
+                    },
+                    close: function () {
+                        overlayService.close();
+                    }
+                };
+
+                localizationService.localize("general_delete").then(value => {
+                    dialog.title = value;
+                    overlayService.open(dialog);
+                });
+
+                event.preventDefault();
+                event.stopPropagation();
             };
 
             scope.addChild = $event => {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js
index bf03749faa..4c628391cb 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js
@@ -1,9 +1,9 @@
 (function () {
     'use strict';
 
-    function GridSelector($location, overlayService, editorService) {
+    function GridSelector(overlayService, editorService) {
 
-        function link(scope, el, attr, ctrl) {
+        function link(scope) {
 
             var eventBindings = [];
             scope.dialogModel = {};
@@ -33,26 +33,30 @@
             };
 
             scope.openItemPicker = function ($event) {
-                var dialogModel = {
-                    view: "itempicker",
-                    title: "Choose " + scope.itemLabel,
-                    availableItems: scope.availableItems,
-                    selectedItems: scope.selectedItems,
-                    position: "target",
-                    event: $event,
-                    submit: function (model) {
-                        scope.selectedItems.push(model.selectedItem);
-                        // if no default item - set item as default
-                        if (scope.defaultItem === null) {
-                            scope.setAsDefaultItem(model.selectedItem);
+                if (scope.itemPicker) {
+                    scope.itemPicker();
+                } else {
+                    var dialogModel = {
+                        view: "itempicker",
+                        title: "Choose " + scope.itemLabel,
+                        availableItems: scope.availableItems,
+                        selectedItems: scope.selectedItems,
+                        position: "target",
+                        event: $event,
+                        submit: function (model) {
+                            scope.selectedItems.push(model.selectedItem);
+                            // if no default item - set item as default
+                            if (scope.defaultItem === null) {
+                                scope.setAsDefaultItem(model.selectedItem);
+                            }
+                            overlayService.close();
+                        },
+                        close: function () {
+                            overlayService.close();
                         }
-                        overlayService.close();
-                    },
-                    close: function() {
-                      overlayService.close();
-                    }
-                };
-                overlayService.open(dialogModel);
+                    };
+                    overlayService.open(dialogModel);
+                }
             };
 
             scope.openTemplate = function (selectedItem) {
@@ -156,7 +160,8 @@
                 availableItems: "=",
                 defaultItem: "=",
                 itemName: "@",
-                updatePlaceholder: "="
+                updatePlaceholder: "=",
+                itemPicker: "="
             },
             link: link
         };
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js
index 0fd6bba091..44d45263da 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js
@@ -933,12 +933,12 @@
                 }
             };
 
-            scope.deleteProperty = (properties, { id, label }) => {
-                const propertyName = label || "";
+            scope.deleteProperty = (properties, property) => {
+                const propertyName = property.label || "";
 
                 const localizeMany = localizationService.localizeMany(['general_delete']);
-                const localize =  localizationService.localize('contentTypeEditor_confirmDeletePropertyMessage',  [propertyName]);
-                
+                const localize = localizationService.localize('contentTypeEditor_confirmDeletePropertyMessage', [propertyName]);
+
                 $q.all([localizeMany, localize]).then(values => {
                     const translations = values[0];
                     const message = values[1];
@@ -948,7 +948,7 @@
                         content: message,
                         submitButtonLabelKey: 'actions_delete',
                         submit: () => {
-                            const index = properties.findIndex(property => property.id === id);
+                            const index = properties.findIndex(p => property.id ? p.id === property.id : p === property);
                             properties.splice(index, 1);
                             notifyChanged();
 
diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js
index 870e497541..05f83f96aa 100644
--- a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js
+++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js
@@ -4,17 +4,20 @@
  * @namespace umbCmsJoinArray
  * 
  * param {array} array of string or objects, if an object use the third argument to specify which prop to list.
- * param {seperator} string containing the seperator to add between joined values.
+ * param {separator} string containing the separator to add between joined values.
  * param {prop} string used if joining an array of objects, set the name of properties to join.
  * 
  * @description
- * Join an array of string or an array of objects, with a costum seperator.
- * 
+ * Join an array of string or an array of objects, with a custom separator.
+ * If the array is null or empty, returns an empty string
+ * If the array is not actually an array (ie a string or number), returns the value of the array
  */
 angular.module("umbraco.filters").filter('umbCmsJoinArray', function () {
     return function join(array, separator, prop) {
-        return (!Utilities.isUndefined(prop) ? array.map(function (item) {
-            return item[prop];
-        }) : array).join(separator || '');
+        if (typeof array !== 'object' || !array) {
+            return array || '';
+        }
+        separator = separator || '';
+        return (!Utilities.isUndefined(prop) ? array.map(item => item[prop]) : array).join(separator);
     };
 });
diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js
index 2c2007dd91..05594115e1 100644
--- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js
+++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js
@@ -34,6 +34,15 @@ angular.module('umbraco.mocks').
           return [200, nodes, null];
       }
 
+      function returnUrlsbyUdis(status, data, headers) {
+
+          if (!mocksUtils.checkAuth()) {
+              return [401, null, null];
+          }
+
+          return [200, {}, null];
+      }
+
       function returnEntitybyIdsPost(method, url, data, headers) {
 
           if (!mocksUtils.checkAuth()) {
@@ -73,6 +82,10 @@ angular.module('umbraco.mocks').
                   .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetByIds'))
                   .respond(returnEntitybyIdsPost);
 
+              $httpBackend
+                  .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetUrlsByUdis'))
+                  .respond(returnUrlsbyUdis);
+
               $httpBackend
                   .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetAncestors'))
                   .respond(returnEntitybyIds);
diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js
index 6bc7855528..3d2c77f2b4 100644
--- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js
+++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js
@@ -28,7 +28,7 @@ angular.module('umbraco.mocks').
 
               { separator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} },
           
-                { separator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyrecyclebin", metaData: {} }
+                { separator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyRecycleBin", metaData: {} }
           ];
 
           var result = {
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js
index a836f8db3d..b6f45ead0e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js
@@ -1234,6 +1234,186 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
                 ),
                 "Failed to roll back content item with id " + contentId
             );
+        },
+
+        /**
+          * @ngdoc method
+          * @name umbraco.resources.contentResource#getPublicAccess
+          * @methodOf umbraco.resources.contentResource
+          *
+          * @description
+          * Returns the public access protection for a content item
+          *
+          * ##usage
+          * 
+          * contentResource.getPublicAccess(contentId)
+          *    .then(function(publicAccess) {
+          *        // do your thing
+          *    });
+          * 
+ * + * @param {Int} contentId The content Id + * @returns {Promise} resourcePromise object containing the public access protection + * + */ + getPublicAccess: function (contentId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetPublicAccess", { + contentId: contentId + }) + ), + "Failed to get public access for content item with id " + contentId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#updatePublicAccess + * @methodOf umbraco.resources.contentResource + * + * @description + * Sets or updates the public access protection for a content item + * + * ##usage + *
+          * contentResource.updatePublicAccess(contentId, userName, password, roles, loginPageId, errorPageId)
+          *    .then(function() {
+          *        // do your thing
+          *    });
+          * 
+ * + * @param {Int} contentId The content Id + * @param {Array} groups The names of the groups that should have access (if using group based protection) + * @param {Array} usernames The usernames of the members that should have access (if using member based protection) + * @param {Int} loginPageId The Id of the login page + * @param {Int} errorPageId The Id of the error page + * @returns {Promise} resourcePromise object containing the public access protection + * + */ + updatePublicAccess: function (contentId, groups, usernames, loginPageId, errorPageId) { + var publicAccess = { + contentId: contentId, + loginPageId: loginPageId, + errorPageId: errorPageId + }; + if (Utilities.isArray(groups) && groups.length) { + publicAccess.groups = groups; + } + else if (Utilities.isArray(usernames) && usernames.length) { + publicAccess.usernames = usernames; + } + else { + throw "must supply either userName/password or roles"; + } + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostPublicAccess", publicAccess) + ), + "Failed to update public access for content item with id " + contentId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#removePublicAccess + * @methodOf umbraco.resources.contentResource + * + * @description + * Removes the public access protection for a content item + * + * ##usage + *
+          * contentResource.removePublicAccess(contentId)
+          *    .then(function() {
+          *        // do your thing
+          *    });
+          * 
+ * + * @param {Int} contentId The content Id + * @returns {Promise} resourcePromise object that's resolved once the public access has been removed + * + */ + removePublicAccess: function (contentId) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "RemovePublicAccess", { + contentId: contentId + }) + ), + "Failed to remove public access for content item with id " + contentId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getPagedContentVersions + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a paged array of previous version id's, given a node id, pageNumber, pageSize and a culture + * + * ##usage + *
+          * contentResource.getPagedContentVersions(id, pageNumber, pageSize, culture)
+          *    .then(function(versions) {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id Id of node + * @param {Int} pageNumber page number + * @param {Int} pageSize page size + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object containing the versions + * + */ + getPagedContentVersions: function (contentId, pageNumber, pageSize, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetPagedContentVersions", { + contentId: contentId, + pageNumber: pageNumber, + pageSize: pageSize, + culture: culture + }) + ), + "Failed to get versions for content item with id " + contentId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#contentVersionPreventCleanup + * @methodOf umbraco.resources.contentResource + * + * @description + * Enables or disabled clean up of a version + * + * ##usage + *
+          * contentResource.contentVersionPreventCleanup(contentId, versionId, preventCleanup)
+          *    .then(function() {
+          *        // do your thing
+          *    });
+          * 
+ * + * @param {Int} contentId Id of node + * @param {Int} versionId Id of version + * @param {Int} preventCleanup Boolean to toggle clean up prevention + * + */ + contentVersionPreventCleanup: function (contentId, versionId, preventCleanup) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSetContentVersionPreventCleanup", { + contentId: contentId, + versionId: versionId, + preventCleanup: preventCleanup + }) + ), + "Failed to toggle prevent cleanup of version with id " + versionId + ); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 2dc6aeb6a6..c6dc313bc7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -127,6 +127,21 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve url for id:' + id); }, + getUrlsByUdis: function(udis, culture) { + var query = "culture=" + (culture || ""); + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrlsByUdis", + query), + { + udis: udis + }), + 'Failed to retrieve url map for udis ' + udis); + }, + getUrlByUdi: function (udi, culture) { if (!udi) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js index cb06218618..4f5f47fb81 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js @@ -99,20 +99,21 @@ } } - /** * Generate label for Block, uses either the labelInterpolator or falls back to the contentTypeName. * @param {Object} blockObject BlockObject to recive data values from. */ function getBlockLabel(blockObject) { if (blockObject.labelInterpolator !== undefined) { - var labelVars = Object.assign({"$settings": blockObject.settingsData || {}, "$layout": blockObject.layout || {}, "$index": (blockObject.index || 0)+1 }, blockObject.data); - return blockObject.labelInterpolator(labelVars); + var labelVars = Object.assign({"$contentTypeName": blockObject.content.contentTypeName, "$settings": blockObject.settingsData || {}, "$layout": blockObject.layout || {}, "$index": (blockObject.index || 0)+1 }, blockObject.data); + var label = blockObject.labelInterpolator(labelVars); + if (label) { + return label; + } } return blockObject.content.contentTypeName; } - /** * Used to add watchers on all properties in a content or settings model */ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js index c638841066..bff0746339 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js @@ -96,7 +96,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje group.convertingToTab = true; group.type = this.TYPE_TAB; - + const newAlias = this.generateLocalAlias(group.name); // when checking for alias uniqueness we need to exclude the current group or the alias would get a + 1 const otherGroups = [...groups].filter(groupCopy => !groupCopy.convertingToTab); @@ -445,6 +445,12 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje // The saved content type might have updated values (eg. new IDs/keys), so make sure the view model is updated contentType.ModelState = savedContentType.ModelState; contentType.id = savedContentType.id; + + // Prevent rebinding if there was an error: https://github.com/umbraco/Umbraco-CMS/pull/11257 + if (savedContentType.ModelState) { + return; + } + contentType.groups.forEach(function (group) { if (!group.alias) return; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index b11e30f8e0..b146f471e0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -179,7 +179,7 @@ When building a custom infinite editor view you can use the same components as a } else { focus(); } - }); + }); /** * @ngdoc method @@ -214,17 +214,18 @@ When building a custom infinite editor view you can use the same components as a * Method to tell editors that they are begin blurred. */ function blur() { - - /* keyboard shortcuts will be overwritten by the new infinite editor + if (isEnabled === true) { + /* keyboard shortcuts will be overwritten by the new infinite editor so we need to store the shortcuts for the current editor so they can be rebound when the infinite editor closes */ - unbindKeyboardShortcuts(); - isEnabled = false; + unbindKeyboardShortcuts(); + isEnabled = false; + } } /** * @ngdoc method - * @name umbraco.services.editorService#blur + * @name umbraco.services.editorService#focus * @methodOf umbraco.services.editorService * * @description @@ -526,7 +527,7 @@ When building a custom infinite editor view you can use the same components as a */ function rollback(editor) { editor.view = "views/common/infiniteeditors/rollback/rollback.html"; - if (!editor.size) editor.size = "medium"; + if (!editor.size) editor.size = ""; open(editor); } @@ -971,6 +972,28 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#templatePicker + * @methodOf umbraco.services.editorService + * + * @description + * Opens a template picker in infinite editing, the submit callback returns an array of selected items. + * + * @param {object} editor rendering options. + * @param {boolean} editor.multiPicker Pick one or multiple items. + * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. + * @returns {object} editor object. + */ + function templatePicker(editor) { + editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; + if (!editor.size) editor.size = "small"; + editor.section = "settings"; + editor.treeAlias = "templates"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#macroPicker @@ -1133,6 +1156,7 @@ When building a custom infinite editor view you can use the same components as a templateSections: templateSections, userPicker: userPicker, itemPicker: itemPicker, + templatePicker: templatePicker, macroPicker: macroPicker, memberGroupPicker: memberGroupPicker, memberPicker: memberPicker, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index 8a965f2c78..113b26d74c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -12,6 +12,58 @@ var currentOverlay = null; + /** + * @ngdoc method + * @name umbraco.services.overlayService#open + * @methodOf umbraco.services.overlayService + * + * @description + * Opens a new overlay. + * + * @param {object} overlay The rendering options for the overlay. + * @param {string=} overlay.view The URL to the view. Defaults to `views/common/overlays/default/default.html` if nothing is specified. + * @param {string=} overlay.position The alias of the position of the overlay. Defaults to `center`. + * + * Custom positions can be added by adding a CSS rule for the the underlying CSS rule. Eg. for the position `center`, the corresponding `umb-overlay-center` CSS rule is defined as: + * + *
+         * .umb-overlay.umb-overlay-center {
+         *     position: absolute;
+         *     width: 600px;
+         *     height: auto;
+         *     top: 50%;
+         *     left: 50%;
+         *     transform: translate(-50%,-50%);
+         *     border-radius: 3px;
+         * }
+         * 
+ * @param {string=} overlay.size Sets an alias for the size of the overlay to be opened. If set to `small` (default), an `umb-overlay--small` class name will be appended the the class list of the main overlay element in the DOM. + * + * Umbraco does not support any more sizes by default, but if you wish to introduce a `medium` size, you could do so by adding a CSS rule simlar to: + * + *
+         * .umb-overlay-center.umb-overlay--medium {
+         *     width: 800px;
+         * }
+         * 
+ * @param {booean=} overlay.disableBackdropClick A boolean value indicating whether the click event on the backdrop should be disabled. + * @param {string=} overlay.title The overall title of the overlay. The title will be omitted if not specified. + * @param {string=} overlay.subtitle The sub title of the overlay. The sub title will be omitted if not specified. + * @param {object=} overlay.itemDetails An item that will replace the header of the overlay. + * @param {string=} overlay.itemDetails.icon The icon of the item - eg. `icon-book`. + * @param {string=} overlay.itemDetails.title The title of the item. + * @param {string=} overlay.itemDetails.description Sets the description of the item. * + * @param {string=} overlay.submitButtonLabel The label of the submit button. To support localized values, it's recommended to use the `submitButtonLabelKey` instead. + * @param {string=} overlay.submitButtonLabelKey The key to be used for the submit button label. Defaults to `general_submit` if not specified. + * @param {string=} overlay.submitButtonState The state of the submit button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `init`, `busy", `success`, `error`. + * @param {string=} overlay.submitButtonStyle The styling of the submit button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `primary`, `info`, `success`, `warning`, `danger`, `inverse`, `link` and `block`. Defaults to `success` if not specified specified. + * @param {string=} overlay.hideSubmitButton A boolean value indicating whether the submit button should be hidden. Default is `false`. + * @param {string=} overlay.disableSubmitButton A boolean value indicating whether the submit button should be disabled, preventing the user from submitting the overlay. Default is `false`. + * @param {string=} overlay.closeButtonLabel The label of the close button. To support localized values, it's recommended to use the `closeButtonLabelKey` instead. + * @param {string=} overlay.closeButtonLabelKey The key to be used for the close button label. Defaults to `general_close` if not specified. + * @param {string=} overlay.submit A callback function that is invoked when the user submits the overlay. + * @param {string=} overlay.close A callback function that is invoked when the user closes the overlay. + */ function open(newOverlay) { // prevent two open overlays at the same time @@ -49,6 +101,14 @@ eventsService.emit("appState.overlay", overlay); } + /** + * @ngdoc method + * @name umbraco.services.overlayService#close + * @methodOf umbraco.services.overlayService + * + * @description + * Closes the current overlay. + */ function close() { focusLockService.removeInertAttribute(); @@ -61,6 +121,16 @@ eventsService.emit("appState.overlay", null); } + /** + * @ngdoc method + * @name umbraco.services.overlayService#ysod + * @methodOf umbraco.services.overlayService + * + * @description + * Opens a new overlay with an error message. + * + * @param {object} error The error to be shown. + */ function ysod(error) { const overlay = { view: "views/common/overlays/ysod/ysod.html", @@ -72,6 +142,36 @@ open(overlay); } + /** + * @ngdoc method + * @name umbraco.services.overlayService#confirm + * @methodOf umbraco.services.overlayService + * + * @description + * Opens a new overlay prompting the user to confirm the overlay. + * + * @param {object} overlay The options for the overlay. + * @param {string=} overlay.confirmType The type of the confirm dialog, which helps define standard styling and labels of the overlay. Supported values are `delete` and `remove`. + * @param {string=} overlay.closeButtonLabelKey The key to be used for the cancel button label. Defaults to `general_cancel` if not specified. + * @param {string=} overlay.view The URL to the view. Defaults to `views/common/overlays/confirm/confirm.html` if nothing is specified. + * @param {string=} overlay.confirmMessageStyle The styling of the confirm message. If `overlay.confirmType` is `delete`, the fallback value is `danger` - otherwise a message style isn't explicitly specified. + * @param {string=} overlay.submitButtonStyle The styling of the confirm button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `primary`, `info`, `success`, `warning`, `danger`, `inverse`, `link` and `block`. + * + * If not specified, the fallback value depends on the value specified for the `overlay.confirmType` parameter: + * + * - `delete`: fallback key is `danger` + * - `remove`: fallback key is `primary` + * - anything else: no fallback AKA default button style + * @param {string=} overlay.submitButtonLabelKey The key to be used for the confirm button label. + * + * If not specified, the fallback value depends on the value specified for the `overlay.confirmType` parameter: + * + * - `delete`: fallback key is `actions_delete` + * - `remove`: fallback key is `actions_remove` + * - anything else: fallback is `general_confirm` + * @param {function=} overlay.close A callback function that is invoked when the user closes the overlay. + * @param {function=} overlay.submit A callback function that is invoked when the user confirms the overlay. + */ function confirm(overlay) { if (!overlay.closeButtonLabelKey) overlay.closeButtonLabelKey = "general_cancel"; @@ -99,11 +199,45 @@ open(overlay); } + /** + * @ngdoc method + * @name umbraco.services.overlayService#confirmDelete + * @methodOf umbraco.services.overlayService + * + * @description + * Opens a new overlay prompting the user to confirm the overlay. The overlay will have styling and labels useful for when the user needs to confirm a delete action. + * + * @param {object} overlay The options for the overlay. + * @param {string=} overlay.closeButtonLabelKey The key to be used for the cancel button label. Defaults to `general_cancel` if not specified. + * @param {string=} overlay.view The URL to the view. Defaults to `views/common/overlays/confirm/confirm.html` if nothing is specified. + * @param {string=} overlay.confirmMessageStyle The styling of the confirm message. Defaults to `delete` if not specified specified. + * @param {string=} overlay.submitButtonStyle The styling of the confirm button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `primary`, `info`, `success`, `warning`, `danger`, `inverse`, `link` and `block`. Defaults to `danger` if not specified specified. + * @param {string=} overlay.submitButtonLabelKey The key to be used for the confirm button label. Defaults to `actions_delete` if not specified. + * @param {function=} overlay.close A callback function that is invoked when the user closes the overlay. + * @param {function=} overlay.submit A callback function that is invoked when the user confirms the overlay. + */ function confirmDelete(overlay) { overlay.confirmType = "delete"; confirm(overlay); } + /** + * @ngdoc method + * @name umbraco.services.overlayService#confirmRemove + * @methodOf umbraco.services.overlayService + * + * @description + * Opens a new overlay prompting the user to confirm the overlay. The overlay will have styling and labels useful for when the user needs to confirm a remove action. + * + * @param {object} overlay The options for the overlay. + * @param {string=} overlay.closeButtonLabelKey The key to be used for the cancel button label. Defaults to `general_cancel` if not specified. + * @param {string=} overlay.view The URL to the view. Defaults to `views/common/overlays/confirm/confirm.html` if nothing is specified. + * @param {string=} overlay.confirmMessageStyle The styling of the confirm message - eg. `danger`. + * @param {string=} overlay.submitButtonStyle The styling of the confirm button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `primary`, `info`, `success`, `warning`, `danger`, `inverse`, `link` and `block`. Defaults to `primary` if not specified specified. + * @param {string=} overlay.submitButtonLabelKey The key to be used for the confirm button label. Defaults to `actions_remove` if not specified. + * @param {function=} overlay.close A callback function that is invoked when the user closes the overlay. + * @param {function=} overlay.submit A callback function that is invoked when the user confirms the overlay. + */ function confirmRemove(overlay) { overlay.confirmType = "remove"; confirm(overlay); 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 4c3901e63c..e6eb430201 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 @@ -1226,6 +1226,12 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }); }); + editor.addShortcut('Ctrl+P', '', function () { + angularHelper.safeApply($rootScope, function () { + eventsService.emit("rte.shortcut.saveAndPublish"); + }); + }); + }, insertLinkInEditor: function (editor, target, anchorElm) { @@ -1272,11 +1278,22 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s function insertLink() { if (anchorElm) { editor.dom.setAttribs(anchorElm, createElemAttributes()); - editor.selection.select(anchorElm); editor.execCommand('mceEndTyping'); } else { - editor.execCommand('mceInsertLink', false, createElemAttributes()); + var selectedContent = editor.selection.getContent(); + // If there is no selected content, we can't insert a link + // as TinyMCE needs selected content for this, so instead we + // create a new dom element and insert it, using the chosen + // link name as the content. + if (selectedContent !== "") { + editor.execCommand('mceInsertLink', false, createElemAttributes()); + } else { + // Using the target url as a fallback, as href might be confusing with a local link + var linkContent = typeof target.name !== "undefined" && target.name !== "" ? target.name : target.url + var domElement = editor.dom.createHTML("a", createElemAttributes(), linkContent); + editor.execCommand('mceInsertContent', false, domElement); + } } } @@ -1496,6 +1513,19 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }); } + + if(Umbraco.Sys.ServerVariables.umbracoSettings.sanitizeTinyMce === true){ + /** prevent injecting arbitrary JavaScript execution in on-attributes. */ + const allNodes = Array.prototype.slice.call(args.editor.dom.doc.getElementsByTagName("*")); + allNodes.forEach(node => { + for (var i = 0; i < node.attributes.length; i++) { + if(node.attributes[i].name.indexOf("on") === 0) { + node.removeAttribute(node.attributes[i].name) + } + } + }); + } + }); args.editor.on('init', function (e) { @@ -1507,6 +1537,60 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //enable browser based spell checking args.editor.getBody().setAttribute('spellcheck', true); + + /** Setup sanitization for preventing injecting arbitrary JavaScript execution in attributes: + * https://github.com/advisories/GHSA-w7jx-j77m-wp65 + * https://github.com/advisories/GHSA-5vm8-hhgr-jcjp + */ + const uriAttributesToSanitize = ['src', 'href', 'data', 'background', 'action', 'formaction', 'poster', 'xlink:href']; + const parseUri = function() { + // Encapsulated JS logic. + const safeSvgDataUrlElements = [ 'img', 'video' ]; + const scriptUriRegExp = /((java|vb)script|mhtml):/i; + const trimRegExp = /[\s\u0000-\u001F]+/g; + const isInvalidUri = (uri, tagName) => { + if (/^data:image\//i.test(uri)) { + return safeSvgDataUrlElements.indexOf(tagName) !== -1 && /^data:image\/svg\+xml/i.test(uri); + } else { + return /^data:/i.test(uri); + } + }; + + return function parseUri(uri, tagName) { + uri = uri.replace(trimRegExp, ''); + try { + // Might throw malformed URI sequence + uri = decodeURIComponent(uri); + } catch (ex) { + // Fallback to non UTF-8 decoder + uri = unescape(uri); + } + + if (scriptUriRegExp.test(uri)) { + return; + } + + if (isInvalidUri(uri, tagName)) { + return; + } + + return uri; + } + }(); + + if(Umbraco.Sys.ServerVariables.umbracoSettings.sanitizeTinyMce === true){ + args.editor.serializer.addAttributeFilter(uriAttributesToSanitize, function (nodes) { + nodes.forEach(function(node) { + node.attributes.forEach(function(attr) { + const attrName = attr.name.toLowerCase(); + if(uriAttributesToSanitize.indexOf(attrName) !== -1) { + attr.value = parseUri(attr.value, node.name); + } + }); + }); + }); + } + //start watching the value startWatch(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index 40266f7ac5..4db5af883e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -63,7 +63,7 @@ var saveModel = _.pick(displayModel, 'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes', 'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed', - 'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'allowSegmentVariant', 'isElement'); + 'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'allowSegmentVariant', 'isElement', 'historyCleanup'); saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; }); saveModel.defaultTemplate = displayModel.defaultTemplate ? displayModel.defaultTemplate.alias : null; diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html index 4dd8afd512..e314a16319 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -1,4 +1,4 @@ -
+

Install Umbraco

Enter your name, email and password to install Umbraco with its default settings, alternatively you can customize your installation

@@ -59,7 +59,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 6f95608d7a..ba4df33aa0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -203,6 +203,8 @@ @import "components/umbemailmarketing.less"; +// Editors +@import "../views/common/infiniteeditors/rollback/rollback.less"; // Property Editors @import "../views/components/blockcard/umb-block-card-grid.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less index b6800aba65..88e172db0c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -91,6 +91,11 @@ color: @white; } +.umb-button__success.-black, +.umb-button__error.-black { + color: @black; +} + .umb-button__overlay { position: absolute; width: 100%; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index bdfc55f648..c3af6d4342 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -22,6 +22,16 @@ box-shadow: 0 1px 3px fade(@black, 12%), 0 1px 2px fade(@black, 24%); } + &.umb-color-box--xs { + width: 27px; + height: 27px; + } + + &.umb-color-box--s { + width: 30px; + height: 30px; + } + &.umb-color-box--m { width: 40px; height: 40px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index d0427cad0a..6c1e5058d2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -55,6 +55,11 @@ input.umb-table__input { color: @ui-disabled-type; } +.umb-table-head__icon { + position: relative; + top: 2px; +} + .umb-table-head__link { background: transparent; border: 0 none; @@ -111,7 +116,7 @@ input.umb-table__input { .umb-table-body .umb-table-row.-selectable { cursor: pointer; } -.umb-table-row.-selected, +.umb-table-row.-selected, .umb-table-body .umb-table-row.-selectable:hover { &::before { content: ""; @@ -226,7 +231,7 @@ input.umb-table__input { &.umb-table-body__checkicon { display: inline-block; } - } + } } // Table Row Styles @@ -309,8 +314,8 @@ input.umb-table__input { .umb-table__loading-overlay { position: absolute; - width: 100%; - height: 100%; + width: 100%; + height: 100%; background-color: rgba(255, 255, 255, 0.7); z-index: 1; } @@ -330,7 +335,7 @@ input.umb-table__input { } .umb-table--condensed { - + .umb-table-cell:first-of-type:not(.not-fixed) { padding-top: 10px; padding-bottom: 10px; diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less index 7b842c40ad..533760149f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less @@ -13,8 +13,45 @@ } } + .umb-panel-group__details-status-content{ + width:50%; // this is to fix flexbox not making the content too wide + } + .umb-panel-group__details-status-action{ background-color:transparent; padding-left:0; } + + .result-table { + overflow-x:auto; + border:1px solid @tableBorder; + + > table { + margin-bottom:0; + border:0; + } + + th:first-child, + td:first-child { + position:sticky; + left:0; + background-color:@white; + border-right:1px solid @tableBorder; + border-left:0; + + + td, + + th { + border-left:0; + } + } + + th:last-child, + td:last-child { + position:sticky; + right:0; + background-color:@white; + border-left:1px solid @tableBorder; + } + } } + diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index 80f13fbf1f..83071a0d93 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -621,7 +621,6 @@ } .preview-rows { - display: inline-block; position: relative; box-sizing: border-box; @@ -675,9 +674,9 @@ } .preview-rows.columns { - min-height: 18px; + min-height: 16px; line-height: 11px; - padding: 1px; + padding: 0 1px 1px 1px; &.prevalues-rows { min-height: 30px; diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index bc14fc2840..fd699b79d0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -607,6 +607,7 @@ box-sizing: border-box; line-height: 0; contain: content; + position: relative; .checkeredBackground(); &:focus, &:focus-within { @@ -621,6 +622,12 @@ justify-content: center; align-items: center; + > a { + display: block; + width: 100%; + height: 100%; + } + img { display: block; max-width: 100%; diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less index beff81d80c..070078fc19 100644 --- a/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less @@ -2,6 +2,12 @@ TEXT ALIGN */ -.tl { text-align: left; } -.tr { text-align: right; } -.tc { text-align: center; } +.tl, +.table td.tl, +.table th.tl { text-align: left; } +.tr, +.table td.tr, +.table th.tr { text-align: right; } +.tc, +.table td.tc, +.table th.tc { text-align: center; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js index d7d5153956..439af50273 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js @@ -50,14 +50,17 @@ function DashboardController($scope, $q, $routeParams, $location, dashboardResou // Check the query parameter for a dashboard alias const dashboardAlias = $location.search()[DASHBOARD_QUERY_PARAM]; const dashboardIndex = $scope.dashboard.tabs.findIndex(tab => tab.alias === dashboardAlias); + const showDefaultDashboard = dashboardIndex === -1; // Set the first dashboard to active if there is no query parameter or we can't find a matching dashboard for the alias - const activeIndex = dashboardIndex !== -1 ? dashboardIndex : 0; + const activeIndex = showDefaultDashboard ? 0 : dashboardIndex; const tab = $scope.dashboard.tabs[activeIndex]; tab.active = true; - $location.search(DASHBOARD_QUERY_PARAM, tab.alias); + if (!showDefaultDashboard) { + $location.search(DASHBOARD_QUERY_PARAM, tab.alias); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html index 7ec69018b6..9ebb9d4e45 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html @@ -30,21 +30,22 @@
- + Inherit tabs and properties from an existing Document Type. New tabs will be added to the current Document Type or + merged if a tab with an identical name exists.
- + There are no Content Types available to use as a composition. - + This Content Type is used in a composition, and therefore cannot be composed itself.
-
-

+
Where is this composition used?
+

This composition is currently used in the composition of the following Content Types:

  • diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/overlays/confirmremove.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/overlays/confirmremove.html index 6fc3bd826d..33f48b8c1e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/overlays/confirmremove.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/overlays/confirmremove.html @@ -1,9 +1,10 @@
    - + Removing a composition will delete all the associated property data. Once you save the Document Type there's no way + back.
    - + Are you sure?
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html index a4fef28740..d3c6a3538d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html @@ -23,22 +23,22 @@
    - using this editor will get updated with the new settings. + All Document Types using this editor will get updated with the new settings.
    - +
    - +
    -
    +
    Configuration
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 41bf92b27e..837aa43cc3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -28,12 +28,12 @@
    -
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html index 49d40efffa..0e936e52c4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.html @@ -28,12 +28,12 @@

    • - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index 052bad86f2..f38360809a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -90,7 +90,7 @@ class="umb-breadcrumbs__add-ancestor" ng-show="model.showFolderInput" ng-model="model.newFolderName" - ng-keydown="enterSubmitFolder($event)" + ng-keydown="vm.enterSubmitFolder($event)" ng-blur="vm.submitFolder()" focus-when="{{model.showFolderInput}}" />
  • @@ -138,7 +138,7 @@
- + Sorry, we can not find what you are looking for.
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html index 99f4fc04e7..9a01d3329d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html @@ -12,21 +12,21 @@
- + URL
- + Alternative text (optional)
- + Caption (optional)
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 8379062807..564f0911fe 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -55,9 +55,11 @@
@@ -87,7 +89,7 @@
-
+
Validation
- -
+ + + + + + + -
+ -
Current version
-

{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})

+
+
Language
+ +
-
- -
+
+
+ Current version: + {{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}}) +
-
+
-
Changes
- +
+
+
+
+
+
{{ version.displayValue }}
+
{{version.username}}
+
+ Current version + Current version +
+
+
+
+ + +
+
+
- - - - - - - - - - - -
Name - - {{part.value}} - {{part.value}} - {{part.value}} - -
{{property.label}} - - {{part.value}} - {{part.value}} - {{part.value}} - -
+
+
-
+ + - -
+ + + + + + + + + + + + + + + +
+ + + + This shows the differences between the current version and the selected version
Red text will be + removed in the selected version, green text will be added +
+
+ + + + + + + + + + + + +
+ Name + + + {{part.value}} + {{part.value}} + {{part.value}} + +
{{property.label}} + + {{part.value}} + {{part.value}} + {{part.value}} + +
+ +
+ +
+
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.less b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.less new file mode 100644 index 0000000000..e7953a4fea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.less @@ -0,0 +1,32 @@ +.editor-rollback { + + .main-content { + display: flex; + gap: 20px; + } + + .side-panel { + flex: 0 0 33%; + } + + .compare-panel { + flex: 1 1 auto; + position: relative; + } + + .current-version { + background: @gray-10; + padding: 15px; + margin-bottom: 12px; + } + + .culture-select { + margin-bottom: 12px; + } + + .version-details { + color: @gray-5; + font-size: 13px; + } + +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html index 1fdfdfb145..5e656bc260 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html @@ -20,33 +20,35 @@
- -
- + +
Render child template
+
- + Renders the contents of a child template, by inserting a @RenderBody() placeholder.
- +
- +
-
+
Render a named section
- + Renders a named area of a child template, by inserting a @RenderSection(name) placeholder. + This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. +
- +
- + - + Required
- +
- + If mandatory, the child template must contain a @section definition, otherwise an error is shown.
- +
- +
- +
-
+
Define a named section
- + Defines a part of your template as a named section by wrapping it in @section { ... }. + This can be rendered in a specific area of the parent of this template, by using @RenderSection. +
- +
- + - + Required
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index ed5c4096bc..72eb504c60 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -141,6 +141,9 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", }); } } + else if (vm.treeAlias === "templates") { + vm.entityType = "Template"; + } // TODO: Seems odd this logic is here, i don't think it needs to be and should just exist on the property editor using this if ($scope.model.minNumber) { 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 index 28dcedd677..a98eacd702 100644 --- 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 @@ -182,6 +182,12 @@ angular.module("umbraco") $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 index 8f2627795f..fa85785868 100644 --- 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 @@ -1,141 +1,143 @@
-
+
-
+
+ Your profile +
- + + - - + + - - + + - - +
+ +
+ +
+ External login providers +
+ +
+ +
+ +
+
+ + +
+ + +
-
+
-
- External login providers -
-
+
+
+ Your recent history +
+ +
-
+
-
-
- - -
+
- -
- -
+ + + + + + + + + + +
+ +
+
+
{{tab.label}}
+
+
+
- - -
-
- -
- -
- -
- Change password -
- -
- - - - - - - - - - -
- -
- -
-
-
{{tab.label}}
-
-
-
-
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index 3ad4ebc188..e0fb4aeb77 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -10,7 +10,7 @@
  • -
  • -
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html index 66687336e0..f8e4b1c566 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html @@ -58,7 +58,7 @@ - + No content can be added for this item diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html index 510623f761..16b5a56038 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html @@ -45,7 +45,7 @@ - + No content can be added for this item
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-group.html b/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-group.html index e75f30d7eb..a8e32821a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-group.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-group.html @@ -3,7 +3,10 @@
- + +
{{groupNameForm.groupName.errorMsg}}
-
+
Required
- - : {{ vm.group.inheritedFromName }} + + Inherited from: {{ vm.group.inheritedFromName }} , @@ -43,16 +46,16 @@ -
+
Required
-
+
Required
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-property.html b/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-property.html index 5724aa4e2e..14f647e761 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-property.html @@ -28,7 +28,7 @@
{{propertyTypeForm.groupName.errorMsg}}
-
+
Required label
@@ -46,7 +46,7 @@
- + {{ vm.property.label }} ({{ vm.property.alias }}) @@ -61,37 +61,37 @@ {{vm.property.dataTypeName}} - + Preview
- * - + * + Mandatory
- - + + Show on member profile
- - + + Member can edit
- - + + Is sensitive data
- - + + Vary by culture
- - + + Vary by segments
@@ -99,14 +99,14 @@
- - + + Inherited from {{vm.property.contentTypeName}}
- - + + Locked
@@ -119,7 +119,7 @@ - + @@ -130,15 +130,15 @@
-
-
@@ -146,4 +146,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-tab.html b/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-tab.html index ada6273217..b62e3f17d9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-tab.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-tab.html @@ -1,6 +1,6 @@ -
- : {{ vm.tab.inheritedFromName }} + Inherited from: {{ vm.tab.inheritedFromName }} ,
- - + + + +
{{ vm.tab.name }}
{{tabNameForm.tabName.errorMsg}}
-
+
Required
@@ -55,9 +61,9 @@ -
+
Required
-
+
Required
@@ -65,7 +71,7 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html index c61110adb2..091d3f6a45 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html @@ -60,8 +60,8 @@ - - - - + - Mandatory + - Default diff --git a/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html index e88ede37cf..a9b8516812 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html @@ -60,7 +60,7 @@ - + No content can be added for this item diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html index 681d4f953a..1e72950410 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html @@ -12,7 +12,7 @@ - + This media item has no link diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html index f56a6c8656..0ab66c964e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html @@ -27,7 +27,7 @@
- +
@@ -36,32 +36,37 @@ + placeholder="Enter XPath query">
  • - +

    - Use Xpath query to set a root node on the tree, either based on a search from the root of the content tree, or by using a context-aware placeholder. + Use an XPath query to set a root node on the tree, either based on a search from the root of the content tree, or by using a context-aware placeholder.

    - Placeholders finds the nearest published ID and runs its query from there, so for instance: - -

    $parent/newsArticle
    - - Will try to get the parent if available, but will then fall back to the nearest ancestor and query for all news articles there. + A placeholder finds the nearest published ID and runs its query from there, so for instance: +

    + +
    $parent/newsArticle
    + +

    + Will try to get the parent if available, but will then fall back to the nearest ancestor and query for all news article children there.

    Available placeholders:
    - $current: current page or closest found ancestor
    - $parent: parent page or closest found ancestor
    - $root: root of the content tree
    - $site: Ancestor node at level 1
    + $current: Current page or closest found ancestor
    + $parent: Parent page or closest found ancestor
    + $root: Root of the content tree
    + $site: Ancestor node at level 1
    +

    +

    + Note: The placeholder can only be used at the beginning of the query.

  • diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.less index 47b1d00ca2..11d70f4397 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.less @@ -79,7 +79,6 @@ ng-form.ng-invalid-val-server-match-settings > .umb-block-list__block > .umb-blo } .umb-block-list__block--actions { position: absolute; - z-index:999999999;// We always want to be on top of custom view, but we need to make sure we still are behind relevant Umbraco CMS UI. ToDo: Needs further testing. top: 10px; right: 10px; font-size: 0; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js index 7334fbeadf..11ef37029c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js @@ -65,6 +65,9 @@ vm.layout = []; // The layout object specific to this Block Editor, will be a direct reference from Property Model. vm.availableBlockTypes = []; // Available block entries of this property editor. vm.labels = {}; + vm.options = { + createFlow: false + }; localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function (data) { vm.labels.grid_addElement = data[0]; @@ -380,7 +383,7 @@ function editBlock(blockObject, openSettings, blockIndex, parentForm, options) { - options = options || {}; + options = options || vm.options; // this must be set if (blockIndex === undefined) { @@ -560,7 +563,9 @@ if (inlineEditing === true) { blockObject.activate(); } else if (inlineEditing === false && blockObject.hideContentInOverlay !== true) { + vm.options.createFlow = true; blockObject.edit(); + vm.options.createFlow = false; } } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index d8c7b3e76a..1ecd6bdf26 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -44,14 +44,15 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso function validate() { if ($scope.contentPickerForm) { //Validate! - if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { + var hasItemsOrMandatory = $scope.renderModel.length !== 0 || ($scope.model.validation && $scope.model.validation.mandatory); + if (hasItemsOrMandatory && $scope.minNumberOfItems && $scope.minNumberOfItems > $scope.renderModel.length) { $scope.contentPickerForm.minCount.$setValidity("minCount", false); } else { $scope.contentPickerForm.minCount.$setValidity("minCount", true); } - if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { + if ($scope.maxNumberOfItems && $scope.maxNumberOfItems < $scope.renderModel.length) { $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); } else { @@ -145,6 +146,10 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso $scope.umbProperty.setPropertyActions(propertyActions); } + + // use these to avoid the nested property lookups/null-checks + $scope.minNumberOfItems = $scope.model.config.minNumber ? parseInt($scope.model.config.minNumber) : 0; + $scope.maxNumberOfItems = $scope.model.config.maxNumber ? parseInt($scope.model.config.maxNumber) : 0; } //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! @@ -194,7 +199,7 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso dialogOptions.dataTypeKey = $scope.model.dataTypeKey; // if we can't pick more than one item, explicitly disable multiPicker in the dialog options - if ($scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) === 1) { + if ($scope.maxNumberOfItems === 1) { dialogOptions.multiPicker = false; } @@ -413,8 +418,13 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso var missingIds = _.difference(valueIds, renderModelIds); if (missingIds.length > 0) { - return entityResource.getByIds(missingIds, entityType).then(function (data) { + var requests = [ + entityResource.getByIds(missingIds, entityType), + entityResource.getUrlsByUdis(missingIds) + ]; + + return $q.all(requests).then(function ([data, urlMap]) { _.each(valueIds, function (id, i) { var entity = _.find(data, function (d) { @@ -422,7 +432,12 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso }); if (entity) { - addSelectedItem(entity); + + entity.url = entity.trashed + ? vm.labels.general_recycleBin + : urlMap[id]; + + addSelectedItem(entity); } }); @@ -469,26 +484,6 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso } - function setEntityUrl(entity) { - - // get url for content and media items - if (entityType !== "Member") { - entityResource.getUrl(entity.id, entityType).then(function (data) { - // update url - $scope.renderModel.forEach(function (item) { - if (item.id === entity.id) { - if (entity.trashed) { - item.url = vm.labels.general_recycleBin; - } else { - item.url = data; - } - } - }); - }); - } - - } - function addSelectedItem(item) { // set icon @@ -523,8 +518,6 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true // only content supports published/unpublished content so we set everything else to published so the UI looks correct }); - - setEntityUrl(item); } function setSortingState(items) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index 8ebbaae91f..373814310b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -1,7 +1,7 @@
    -

    -

    +

    You have picked a content item currently deleted or in the recycle bin

    +

    You have picked content items currently deleted or in the recycle bin

    @@ -19,7 +19,7 @@
    - -
    +
    - - Add between {{model.config.minNumber}} and {{model.config.maxNumber}} items - - You can only have {{model.config.maxNumber}} items selected + + Add between {{minNumberOfItems}} and {{maxNumberOfItems}} items + + You can only have {{maxNumberOfItems}} items selected - - Add {{model.config.minNumber - renderModel.length}} item(s) - - You can only have {{model.config.maxNumber}} items selected + + Add {{minNumberOfItems - renderModel.length}} item(s) + + You can only have {{maxNumberOfItems}} items selected - - Add up to {{model.config.maxNumber}} items - - You can only have {{model.config.maxNumber}} items selected + + Add up to {{maxNumberOfItems}} items + + You can only have {{maxNumberOfItems}} items selected - - Add at least {{model.config.minNumber}} item(s) + + Add at least {{minNumberOfItems}} item(s)
    @@ -70,12 +70,12 @@
    - You need to add at least {{model.config.minNumber}} items + You need to add at least {{minNumberOfItems}} items
    - You can only have {{model.config.maxNumber}} items selected + You can only have {{maxNumberOfItems}} items selected
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index f5a6191ad1..698ff6daeb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -18,12 +18,14 @@
    -

    +

    Adjust the row by setting cell widths and adding additional cells

    -

    Modifying a row configuration name will result in loss of - data for any existing content that is based on this configuration.

    -

    Modifying only the label will not result in data loss.

    + +

    Modifying a row configuration name will result in loss of + data for any existing content that is based on this configuration.

    +

    Modifying only the label will not result in data loss.

    +
    @@ -70,10 +72,16 @@
    {{currentCell.grid}}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js index c0bc493cd3..0dc5b68d09 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js @@ -125,8 +125,29 @@ angular.module("umbraco") editorService.open(layoutConfigOverlay); } - function deleteTemplate(index) { - $scope.model.value.templates.splice(index, 1); + function deleteTemplate(template, index, event) { + + const dialog = { + view: "views/propertyEditors/grid/overlays/layoutdeleteconfirm.html", + layout: template, + submitButtonLabelKey: "contentTypeEditor_yesDelete", + submitButtonStyle: "danger", + submit: function (model) { + $scope.model.value.templates.splice(index, 1); + overlayService.close(); + }, + close: function () { + overlayService.close(); + } + }; + + localizationService.localize("general_delete").then(value => { + dialog.title = value; + overlayService.open(dialog); + }); + + event.preventDefault(); + event.stopPropagation(); } /**************** @@ -167,7 +188,6 @@ angular.module("umbraco") } function deleteLayout(layout, index, event) { - const dialog = { view: "views/propertyeditors/grid/overlays/rowdeleteconfirm.html", layout: layout, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html index 993b57b23e..9fcd43d834 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html @@ -31,7 +31,7 @@

    {{template.name}}

    - diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/layoutdeleteconfirm.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/layoutdeleteconfirm.html new file mode 100644 index 0000000000..4fedf61865 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/layoutdeleteconfirm.html @@ -0,0 +1,14 @@ +
    +
    + You are deleting the layout {{model.layout.name}}. +
    + +

    + + Modifying layout will result in loss of data for any existing content that is based on this configuration. + +

    + + Are you sure you want to delete? + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/rowdeleteconfirm.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/rowdeleteconfirm.html index 2ba56a5b88..ded2e5b940 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/rowdeleteconfirm.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/overlays/rowdeleteconfirm.html @@ -11,6 +11,6 @@

    - ? + Are you sure you want to delete?
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/icon.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/icon.prevalues.controller.js index fd5c3dbf12..4ad2935981 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/icon.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/icon.prevalues.controller.js @@ -12,6 +12,7 @@ function iconPreValsController($scope, editorService) { var iconPicker = { icon: $scope.icon, color: $scope.color, + size: "medium", submit: function (model) { if (model.icon) { if (model.color) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html index 5db348dbff..9e022d0e0f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html @@ -18,8 +18,8 @@ - Alias - Header + Alias + Header Template @@ -32,14 +32,16 @@
    - (system field) + (systemField)
    - Required + + Required + @@ -53,8 +55,8 @@ - @@ -63,5 +65,4 @@
    - -
    + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js index acfb114307..db79c31cfd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js @@ -61,6 +61,7 @@ var iconPicker = { icon: layout.icon.split(' ')[0], color: layout.icon.split(' ')[1], + size: "medium", submit: function (model) { if (model.icon) { if (model.color) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index 07d5215793..8bbcb2204c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -17,12 +17,12 @@ - + Sorry, we can not find what you are looking for. - + Your recycle bin is empty @@ -69,12 +69,12 @@ - + Your recycle bin is empty - + Sorry, we can not find what you are looking for. diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html index 4cfa8c7984..69dc12d13b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html @@ -62,13 +62,13 @@ - + Sorry, we can not find what you are looking for. - + Your recycle bin is empty diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 76e6759a47..c66ff1a461 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -380,7 +380,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time }); } - $scope.delete = function () { + $scope.delete = function (numberOfItems, totalItems) { const dialog = { view: "views/propertyeditors/listview/overlays/delete.html", @@ -394,7 +394,9 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time }, close: function () { overlayService.close(); - } + }, + numberOfItems: numberOfItems, + totalItems: totalItems }; localizationService.localize("general_delete").then(value => { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 92ad56b045..7fad01fe6c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -190,7 +190,7 @@ button-style="white" label-key="actions_delete" icon="icon-trash" - action="delete()" + action="delete(selection.length, listViewResultSet.items.length)" disabled="actionInProgress" size="xs" add-ellipsis="true"> diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/delete.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/delete.html index 1c99aa594e..f8b2b008d3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/delete.html @@ -1,7 +1,7 @@

    - ? + Are you sure you want to delete?

    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewpublish.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewpublish.html index c6f7c85c0b..b6933dffc0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewpublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewpublish.html @@ -2,7 +2,7 @@
    -

    +

    Publishing will make the selected items visible on the site.

    @@ -13,13 +13,13 @@
    -

    +

    What languages would you like to publish?

    - + Languages
    @@ -34,7 +34,7 @@
    - + Mandatory language
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.html index 5806bb8f02..9a6af50f3e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.html @@ -2,7 +2,7 @@
    -

    +

    Unpublishing will remove the selected items and all their descendants from the site.

    @@ -13,13 +13,13 @@
    -

    +

    Select the languages to unpublish. Unpublishing a mandatory language will unpublish all languages.

    - + Languages
    @@ -35,7 +35,7 @@
    - + Mandatory language
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umb-media-picker3-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umb-media-picker3-property-editor.html index daf9566e2d..f40c6bc437 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umb-media-picker3-property-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umb-media-picker3-property-editor.html @@ -69,4 +69,8 @@
    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js index 95cf369369..36a7c61620 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js @@ -137,6 +137,10 @@ if (vm.propertyForm) { vm.propertyForm.$setDirty(); } + + if (vm.modelValueForm) { + vm.modelValueForm.modelValue.$setDirty(); + } } function addMediaAt(createIndex, $event) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js index 42ce10c519..40aa7eb5a8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js @@ -26,6 +26,29 @@ } } + $scope.paste = function (event, index) { + event.preventDefault(); + var text = (event.clipboardData || window.clipboardData || event.originalEvent.clipboardData).getData('text'); + var lines = text.split(/\r?\n/).map(line => { return { value: line, hasFocus: false } }); + + if (lines.length > 0) { + // merge with the current text + var currentText = $scope.model.value[index].value; + lines[0].value = currentText.substring(0, event.target.selectionStart) + lines[0].value; + lines[lines.length - 1].value = lines[lines.length - 1].value + currentText.substring(event.target.selectionEnd); + + // clear selection + event.target.selectionEnd = event.target.selectionStart; + + // remove focus from existing values + $scope.model.value.forEach(value => value.hasFocus = false); + + // add all the lines to the value + lines[lines.length - 1].hasFocus = true; + $scope.model.value.splice(index, 1, ...lines); + } + } + $scope.addRemoveOnKeyDown = function (event, index) { var txtBoxValue = $scope.model.value[index]; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html index 9e90666eac..d0d4749c22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html @@ -6,6 +6,7 @@ class="umb-property-editor umb-textstring textstring flx-i" ng-trim="false" ng-keyup="addRemoveOnKeyDown($event, $index)" + ng-paste="paste($event, $index)" focus-when="{{item.hasFocus}}" />
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html index fa146f12f0..f429c04f1d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html @@ -22,7 +22,7 @@ - {{ph = placeholder(config);""}} + {{ph = placeholder(config);hasTabsOrFirstRender = (elemTypeTabs[config.ncAlias].length || config.ncAlias=='');""}} - + + + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html index e11c51068e..f1adb6072c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html @@ -1,52 +1,62 @@ -
    +
    -
public AutoMoqDataAttribute() - : base(() => AutoMockCustomizations.Default) + : base(UmbracoAutoMoqFixtureFactory.CreateDefaultFixture) { } - - internal static class AutoMockCustomizations - { - public static IFixture Default => new Fixture().Customize(new UmbracoCustomization()); - - private class UmbracoCustomization : ICustomization - { - public void Customize(IFixture fixture) - { - fixture.Customize( - u => u.FromFactory( - (a, b, c) => BackOfficeIdentityUser.CreateNew(new GlobalSettings(), a, b, c))); - fixture - .Customize(new ConstructorCustomization(typeof(UsersController), new GreedyConstructorQuery())) - .Customize(new ConstructorCustomization(typeof(InstallController), new GreedyConstructorQuery())) - .Customize(new ConstructorCustomization(typeof(PreviewController), new GreedyConstructorQuery())) - .Customize(new ConstructorCustomization(typeof(MemberController), new GreedyConstructorQuery())) - .Customize(new ConstructorCustomization(typeof(BackOfficeController), new GreedyConstructorQuery())) - .Customize(new ConstructorCustomization(typeof(BackOfficeUserManager), new GreedyConstructorQuery())) - .Customize(new ConstructorCustomization(typeof(MemberManager), new GreedyConstructorQuery())); - - fixture.Customize(new AutoMoqCustomization()); - - // When requesting an IUserStore ensure we actually uses a IUserLockoutStore - fixture.Customize>(cc => cc.FromFactory(() => Mock.Of>())); - - fixture.Customize( - u => u.FromFactory( - (a, b, c) => new ConfigConnectionString(a, b, c))); - - fixture.Customize( - u => u.FromFactory( - () => new UmbracoVersion())); - - fixture.Customize(u => u.FromFactory( - () => new BackOfficeAreaRoutes( - Options.Create(new GlobalSettings()), - Mock.Of(x => x.ToAbsolute(It.IsAny()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), - Mock.Of(x => x.Level == RuntimeLevel.Run), - new UmbracoApiControllerTypeCollection(() => Enumerable.Empty())))); - - fixture.Customize(u => u.FromFactory( - () => new PreviewRoutes( - Options.Create(new GlobalSettings()), - Mock.Of(x => x.ToAbsolute(It.IsAny()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), - Mock.Of(x => x.Level == RuntimeLevel.Run)))); - - var connectionStrings = new ConnectionStrings(); - fixture.Customize(x => x.FromFactory(() => connectionStrings)); - - var httpContextAccessor = new HttpContextAccessor { HttpContext = new DefaultHttpContext() }; - fixture.Customize(x => x.FromFactory(() => httpContextAccessor.HttpContext)); - fixture.Customize(x => x.FromFactory(() => httpContextAccessor)); - } - } - } } } diff --git a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/OmitRecursionCustomization.cs b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/OmitRecursionCustomization.cs new file mode 100644 index 0000000000..8a799c2401 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/OmitRecursionCustomization.cs @@ -0,0 +1,10 @@ +using AutoFixture; + +namespace Umbraco.Cms.Tests.UnitTests.AutoFixture.Customizations +{ + internal class OmitRecursionCustomization : ICustomization + { + public void Customize(IFixture fixture) => + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs new file mode 100644 index 0000000000..672bbd0862 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using AutoFixture; +using AutoFixture.Kernel; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Moq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.BackOffice.Install; +using Umbraco.Cms.Web.BackOffice.Routing; +using Umbraco.Cms.Web.Common.Security; + +namespace Umbraco.Cms.Tests.UnitTests.AutoFixture.Customizations +{ + internal class UmbracoCustomizations : ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customize( + u => u.FromFactory( + (a, b, c) => BackOfficeIdentityUser.CreateNew(new GlobalSettings(), a, b, c))); + + fixture + .Customize(new ConstructorCustomization(typeof(UsersController), new GreedyConstructorQuery())) + .Customize(new ConstructorCustomization(typeof(InstallController), new GreedyConstructorQuery())) + .Customize(new ConstructorCustomization(typeof(PreviewController), new GreedyConstructorQuery())) + .Customize(new ConstructorCustomization(typeof(MemberController), new GreedyConstructorQuery())) + .Customize(new ConstructorCustomization(typeof(BackOfficeController), new GreedyConstructorQuery())) + .Customize(new ConstructorCustomization(typeof(BackOfficeUserManager), new GreedyConstructorQuery())) + .Customize(new ConstructorCustomization(typeof(MemberManager), new GreedyConstructorQuery())); + + // When requesting an IUserStore ensure we actually uses a IUserLockoutStore + fixture.Customize>(cc => cc.FromFactory(Mock.Of>)); + + fixture.Customize( + u => u.FromFactory( + (a, b, c) => new ConfigConnectionString(a, b, c))); + + fixture.Customize( + u => u.FromFactory( + () => new UmbracoVersion())); + + fixture.Customize(x => + x.With(settings => settings.ApplicationVirtualPath, string.Empty)); + + fixture.Customize(u => u.FromFactory( + () => new BackOfficeAreaRoutes( + Options.Create(new GlobalSettings()), + Mock.Of(x => x.ToAbsolute(It.IsAny()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), + Mock.Of(x => x.Level == RuntimeLevel.Run), + new UmbracoApiControllerTypeCollection(Enumerable.Empty)))); + + fixture.Customize(u => u.FromFactory( + () => new PreviewRoutes( + Options.Create(new GlobalSettings()), + Mock.Of(x => x.ToAbsolute(It.IsAny()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), + Mock.Of(x => x.Level == RuntimeLevel.Run)))); + + var configConnectionString = new ConfigConnectionString( + "ss", + "Data Source=(localdb)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\Umbraco.mdf;Integrated Security=True"); + fixture.Customize(x => x.FromFactory(() => configConnectionString)); + + var httpContextAccessor = new HttpContextAccessor { HttpContext = new DefaultHttpContext() }; + fixture.Customize(x => x.FromFactory(() => httpContextAccessor.HttpContext)); + fixture.Customize(x => x.FromFactory(() => httpContextAccessor)); + + fixture.Customize(x => x.With(settings => settings.UmbracoApplicationUrl, "http://localhost:5000")); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/AutoFixture/InlineAutoMoqDataAttribute.cs b/tests/Umbraco.Tests.UnitTests/AutoFixture/InlineAutoMoqDataAttribute.cs index c06ff1a0f3..a4a28477b1 100644 --- a/tests/Umbraco.Tests.UnitTests/AutoFixture/InlineAutoMoqDataAttribute.cs +++ b/tests/Umbraco.Tests.UnitTests/AutoFixture/InlineAutoMoqDataAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -15,7 +15,7 @@ namespace Umbraco.Cms.Tests.UnitTests.AutoFixture /// [Frozen] can be used to ensure the same variable is injected and available as parameter for the test ///
public InlineAutoMoqDataAttribute(params object[] arguments) - : base(() => AutoMoqDataAttribute.AutoMockCustomizations.Default, arguments) + : base(UmbracoAutoMoqFixtureFactory.CreateDefaultFixture, arguments) { } } diff --git a/tests/Umbraco.Tests.UnitTests/AutoFixture/UmbracoAutoMoqFixtureFactory.cs b/tests/Umbraco.Tests.UnitTests/AutoFixture/UmbracoAutoMoqFixtureFactory.cs new file mode 100644 index 0000000000..114cef063c --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/AutoFixture/UmbracoAutoMoqFixtureFactory.cs @@ -0,0 +1,15 @@ +using AutoFixture; +using AutoFixture.AutoMoq; +using Umbraco.Cms.Tests.UnitTests.AutoFixture.Customizations; + +namespace Umbraco.Cms.Tests.UnitTests.AutoFixture +{ + internal static class UmbracoAutoMoqFixtureFactory + { + internal static IFixture CreateDefaultFixture() => + new Fixture() + .Customize(new AutoMoqCustomization { ConfigureMembers = true }) + .Customize(new OmitRecursionCustomization()) + .Customize(new UmbracoCustomizations()); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs new file mode 100644 index 0000000000..4cf6ffdc9e --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Infrastructure.Serialization; +using Umbraco.Cms.Tests.Common; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.TestHelpers +{ + [TestFixture] + public class PublishedSnapshotServiceTestBase + { + [SetUp] + public virtual void Setup() + { + VariationContextAccessor = new TestVariationContextAccessor(); + PublishedSnapshotAccessor = new TestPublishedSnapshotAccessor(); + } + + [TearDown] + public void Teardown() => SnapshotService?.Dispose(); + + protected IShortStringHelper ShortStringHelper { get; } = TestHelper.ShortStringHelper; + protected virtual IPublishedModelFactory PublishedModelFactory { get; } = new NoopPublishedModelFactory(); + protected IContentTypeService ContentTypeService { get; private set; } + protected IMediaTypeService MediaTypeService { get; private set; } + protected IDataTypeService DataTypeService { get; private set; } + protected IDomainService DomainService { get; private set; } + protected IPublishedValueFallback PublishedValueFallback { get; private set; } + protected IPublishedSnapshotService SnapshotService { get; private set; } + protected IVariationContextAccessor VariationContextAccessor { get; private set; } + protected TestPublishedSnapshotAccessor PublishedSnapshotAccessor { get; private set; } + protected TestNuCacheContentService NuCacheContentService { get; private set; } + protected PublishedContentTypeFactory PublishedContentTypeFactory { get; private set; } + protected GlobalSettings GlobalSettings { get; } = new(); + + protected virtual PropertyValueConverterCollection PropertyValueConverterCollection => + new(() => new[] { new TestSimpleTinyMceValueConverter() }); + + protected IPublishedContent GetContent(int id) + { + IPublishedSnapshot snapshot = GetPublishedSnapshot(); + IPublishedContent doc = snapshot.Content.GetById(id); + Assert.IsNotNull(doc); + return doc; + } + + protected IPublishedContent GetMedia(int id) + { + IPublishedSnapshot snapshot = GetPublishedSnapshot(); + IPublishedContent doc = snapshot.Media.GetById(id); + Assert.IsNotNull(doc); + return doc; + } + + protected UrlProvider GetUrlProvider( + IUmbracoContextAccessor umbracoContextAccessor, + RequestHandlerSettings requestHandlerSettings, + WebRoutingSettings webRoutingSettings, + out UriUtility uriUtility) + { + uriUtility = new UriUtility(Mock.Of()); + var urlProvider = new DefaultUrlProvider( + Mock.Of>(x=> x.CurrentValue == requestHandlerSettings), + Mock.Of>(), + new SiteDomainMapper(), + umbracoContextAccessor, + uriUtility, + Mock.Of(x=>x.GetDefaultLanguageIsoCode() == GlobalSettings.DefaultUILanguage) + ); + + var publishedUrlProvider = new UrlProvider( + umbracoContextAccessor, + Options.Create(webRoutingSettings), + new UrlProviderCollection(() => new[] { urlProvider }), + new MediaUrlProviderCollection(() => Enumerable.Empty()), + Mock.Of()); + + return publishedUrlProvider; + } + + protected static PublishedRouter CreatePublishedRouter( + IUmbracoContextAccessor umbracoContextAccessor, + IEnumerable contentFinders = null, + IPublishedUrlProvider publishedUrlProvider = null) => new(Mock.Of>(x=> x.CurrentValue == new WebRoutingSettings()), + new ContentFinderCollection(() => contentFinders ?? Enumerable.Empty()), + new TestLastChanceFinder(), new TestVariationContextAccessor(), Mock.Of(), + Mock.Of>(), publishedUrlProvider ?? Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of(), + Mock.Of(), umbracoContextAccessor, Mock.Of()); + + protected IUmbracoContextAccessor GetUmbracoContextAccessor(string urlAsString) + { + IPublishedSnapshot snapshot = GetPublishedSnapshot(); + + var uri = new Uri(urlAsString.Contains(Uri.SchemeDelimiter) + ? urlAsString + : $"http://example.com{urlAsString}"); + + IUmbracoContext umbracoContext = Mock.Of( + x => x.CleanedUmbracoUrl == uri + && x.Content == snapshot.Content + && x.PublishedSnapshot == snapshot); + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); + return umbracoContextAccessor; + } + + /// + /// Used as a property editor for any test property that has an editor alias called "Umbraco.Void.RTE" + /// + private class TestSimpleTinyMceValueConverter : SimpleTinyMceValueConverter + { + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias == "Umbraco.Void.RTE"; + } + + protected static DataType[] GetDefaultDataTypes() + { + var serializer = new ConfigurationEditorJsonSerializer(); + + // create data types, property types and content types + var dataType = + new DataType(new VoidEditor("Editor", Mock.Of()), serializer) { Id = 3 }; + + return new[] { dataType }; + } + + protected virtual ServiceContext CreateServiceContext(IContentType[] contentTypes, IMediaType[] mediaTypes, + IDataType[] dataTypes) + { + var contentTypeService = new Mock(); + contentTypeService.Setup(x => x.GetAll()).Returns(contentTypes); + contentTypeService.Setup(x => x.GetAll(It.IsAny())).Returns(contentTypes); + contentTypeService.Setup(x => x.Get(It.IsAny())) + .Returns((string alias) => contentTypes.FirstOrDefault(x => x.Alias.InvariantEquals(alias))); + + var mediaTypeService = new Mock(); + mediaTypeService.Setup(x => x.GetAll()).Returns(mediaTypes); + mediaTypeService.Setup(x => x.GetAll(It.IsAny())).Returns(mediaTypes); + mediaTypeService.Setup(x => x.Get(It.IsAny())) + .Returns((string alias) => mediaTypes.FirstOrDefault(x => x.Alias.InvariantEquals(alias))); + + var contentTypeServiceBaseFactory = new Mock(); + contentTypeServiceBaseFactory.Setup(x => x.For(It.IsAny())) + .Returns(contentTypeService.Object); + + var dataTypeServiceMock = new Mock(); + dataTypeServiceMock.Setup(x => x.GetAll()).Returns(dataTypes); + + return ServiceContext.CreatePartial( + dataTypeService: dataTypeServiceMock.Object, + memberTypeService: Mock.Of(), + memberService: Mock.Of(), + contentTypeService: contentTypeService.Object, + mediaTypeService: mediaTypeService.Object, + localizationService: Mock.Of(), + domainService: Mock.Of(), + fileService: Mock.Of() + ); + } + + /// + /// Creates a published snapshot and set the accessor to resolve the created one + /// + /// + protected IPublishedSnapshot GetPublishedSnapshot() + { + IPublishedSnapshot snapshot = SnapshotService.CreatePublishedSnapshot(null); + PublishedSnapshotAccessor.SetCurrent(snapshot); + return snapshot; + } + + /// + /// Initializes the with a source of data + /// + /// + /// + protected void InitializedCache( + IEnumerable contentNodeKits, + IContentType[] contentTypes, + IDataType[] dataTypes = null, + IEnumerable mediaNodeKits = null, + IMediaType[] mediaTypes = null) + { + // create a data source for NuCache + NuCacheContentService = new TestNuCacheContentService(contentNodeKits, mediaNodeKits); + + IRuntimeState runtime = Mock.Of(); + Mock.Get(runtime).Setup(x => x.Level).Returns(RuntimeLevel.Run); + + // create a service context + ServiceContext serviceContext = CreateServiceContext( + contentTypes ?? Array.Empty(), + mediaTypes ?? Array.Empty(), + dataTypes ?? GetDefaultDataTypes()); + + DataTypeService = serviceContext.DataTypeService; + ContentTypeService = serviceContext.ContentTypeService; + MediaTypeService = serviceContext.MediaTypeService; + DomainService = serviceContext.DomainService; + + // create a scope provider + IScopeProvider scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.CreateScope( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Mock.Of); + + // create a published content type factory + PublishedContentTypeFactory = new PublishedContentTypeFactory( + PublishedModelFactory, + PropertyValueConverterCollection, + DataTypeService); + + ITypeFinder typeFinder = TestHelper.GetTypeFinder(); + + var nuCacheSettings = new NuCacheSettings(); + + // at last, create the complete NuCache snapshot service! + var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; + SnapshotService = new PublishedSnapshotService( + options, + Mock.Of(x => x.GetSyncBootState() == SyncBootState.WarmBoot), + new SimpleMainDom(), + serviceContext, + PublishedContentTypeFactory, + PublishedSnapshotAccessor, + VariationContextAccessor, + Mock.Of(), + NullLoggerFactory.Instance, + scopeProvider, + NuCacheContentService, + new TestDefaultCultureAccessor(), + Options.Create(GlobalSettings), + PublishedModelFactory, + TestHelper.GetHostingEnvironment(), + Options.Create(nuCacheSettings), + //ContentNestedDataSerializerFactory, + new ContentDataSerializer(new DictionaryOfPropertyDataSerializer())); + + // invariant is the current default + VariationContextAccessor.VariationContext = new VariationContext(); + + PublishedValueFallback = new PublishedValueFallback(serviceContext, VariationContextAccessor); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestNuCacheContentService.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestNuCacheContentService.cs new file mode 100644 index 0000000000..25542c5203 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestNuCacheContentService.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; + +namespace Umbraco.Cms.Tests.UnitTests.TestHelpers +{ + public class TestNuCacheContentService : INuCacheContentService + { + private IPublishedModelFactory PublishedModelFactory { get; } = new NoopPublishedModelFactory(); + + public TestNuCacheContentService(params ContentNodeKit[] kits) + : this((IEnumerable)kits) + { } + + public TestNuCacheContentService(IEnumerable contentKits, IEnumerable mediaKits = null) + { + ContentKits = contentKits?.ToDictionary(x => x.Node.Id, x => x) ?? new Dictionary(); + MediaKits = mediaKits?.ToDictionary(x => x.Node.Id, x => x) ?? new Dictionary(); + } + + public Dictionary ContentKits { get; } + public Dictionary MediaKits { get; } + + // note: it is important to clone the returned kits, as the inner + // ContentNode is directly reused and modified by the snapshot service + public ContentNodeKit GetContentSource(int id) + => ContentKits.TryGetValue(id, out ContentNodeKit kit) ? kit.Clone(PublishedModelFactory) : default; + + public IEnumerable GetAllContentSources() + => ContentKits.Values + .OrderBy(x => x.Node.Level) + .ThenBy(x => x.Node.ParentContentId) + .ThenBy(x => x.Node.SortOrder) + .Select(x => x.Clone(PublishedModelFactory)); + + public IEnumerable GetBranchContentSources(int id) + => ContentKits.Values + .Where(x => x.Node.Path.EndsWith("," + id) || x.Node.Path.Contains("," + id + ",")) + .OrderBy(x => x.Node.Level) + .ThenBy(x => x.Node.ParentContentId) + .ThenBy(x => x.Node.SortOrder) + .Select(x => x.Clone(PublishedModelFactory)); + + public IEnumerable GetTypeContentSources(IEnumerable ids) + => ContentKits.Values + .Where(x => ids.Contains(x.ContentTypeId)) + .OrderBy(x => x.Node.Level) + .ThenBy(x => x.Node.ParentContentId) + .ThenBy(x => x.Node.SortOrder) + .Select(x => x.Clone(PublishedModelFactory)); + + public ContentNodeKit GetMediaSource(int id) + => MediaKits.TryGetValue(id, out ContentNodeKit kit) ? kit.Clone(PublishedModelFactory) : default; + + public IEnumerable GetAllMediaSources() + => MediaKits.Values + .OrderBy(x => x.Node.Level) + .ThenBy(x => x.Node.ParentContentId) + .ThenBy(x => x.Node.SortOrder) + .Select(x => x.Clone(PublishedModelFactory)); + + public IEnumerable GetBranchMediaSources(int id) + => MediaKits.Values + .Where(x => x.Node.Path.EndsWith("," + id) || x.Node.Path.Contains("," + id + ",")) + .OrderBy(x => x.Node.Level) + .ThenBy(x => x.Node.ParentContentId) + .ThenBy(x => x.Node.SortOrder) + .Select(x => x.Clone(PublishedModelFactory)); + + public IEnumerable GetTypeMediaSources(IEnumerable ids) + => MediaKits.Values + .Where(x => ids.Contains(x.ContentTypeId)) + .OrderBy(x => x.Node.Level) + .ThenBy(x => x.Node.ParentContentId) + .ThenBy(x => x.Node.SortOrder) + .Select(x => x.Clone(PublishedModelFactory)); + + public void DeleteContentItem(IContentBase item) => throw new NotImplementedException(); + public void DeleteContentItems(IEnumerable items) => throw new NotImplementedException(); + public void RefreshContent(IContent content) => throw new NotImplementedException(); + + public void RebuildDatabaseCacheIfSerializerChanged() => throw new NotImplementedException(); + public void RefreshMedia(IMedia media) => throw new NotImplementedException(); + public void RefreshMember(IMember member) => throw new NotImplementedException(); + public void Rebuild(IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, IReadOnlyCollection memberTypeIds = null) => throw new NotImplementedException(); + + public bool VerifyContentDbCache() => throw new NotImplementedException(); + public bool VerifyMediaDbCache() => throw new NotImplementedException(); + public bool VerifyMemberDbCache() => throw new NotImplementedException(); + } +} diff --git a/tests/Umbraco.Tests/Collections/StackQueueTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/StackQueueTests.cs similarity index 86% rename from tests/Umbraco.Tests/Collections/StackQueueTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/StackQueueTests.cs index 52d0370f0d..6419463ca5 100644 --- a/tests/Umbraco.Tests/Collections/StackQueueTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/StackQueueTests.cs @@ -1,7 +1,7 @@ -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Core.Collections; -namespace Umbraco.Tests.Collections +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Collections { [TestFixture] public class StackQueueTests @@ -10,13 +10,13 @@ namespace Umbraco.Tests.Collections public void Queue() { var sq = new StackQueue(); - for (int i = 0; i < 3; i++) + for (var i = 0; i < 3; i++) { sq.Enqueue(i); } var expected = 0; - while(sq.Count > 0) + while (sq.Count > 0) { var next = sq.Dequeue(); Assert.AreEqual(expected, next); @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Collections public void Stack() { var sq = new StackQueue(); - for (int i = 0; i < 3; i++) + for (var i = 0; i < 3; i++) { sq.Push(i); } @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Collections public void Stack_And_Queue() { var sq = new StackQueue(); - for (int i = 0; i < 5; i++) + for (var i = 0; i < 5; i++) { if (i % 2 == 0) { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/LanguageXmlTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/LanguageXmlTests.cs new file mode 100644 index 0000000000..623d207687 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/LanguageXmlTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using NUnit.Framework; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configurations +{ + [TestFixture] + public class LanguageXmlTests + { + [Test] + [Platform("Win")] //TODO figure out why Path.GetFullPath("/mnt/c/...") is not considered an absolute path on linux + mac + public void Can_Load_Language_Xml_Files() + { + var languageDirectoryPath = GetLanguageDirectory(); + var readFilesCount = 0; + var xmlDocument = new XmlDocument(); + + var directoryInfo = new DirectoryInfo(languageDirectoryPath); + + foreach (var languageFile in directoryInfo.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) + { + // Load will throw an exception if the XML isn't valid. + xmlDocument.Load(languageFile.FullName); + readFilesCount++; + } + + // Ensure that at least one file was read. + Assert.AreNotEqual(0, readFilesCount); + } + + private static string GetLanguageDirectory() + { + var testDirectoryPathParts = Path.GetDirectoryName(TestContext.CurrentContext.TestDirectory) + .Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); + + var solutionDirectoryPathParts = testDirectoryPathParts + .Take(Array.IndexOf(testDirectoryPathParts, "tests")); + var languageFolderPathParts = new List(solutionDirectoryPathParts); + var additionalPathParts = new[] { "Umbraco.Web.UI", "umbraco", "config", "lang" }; + languageFolderPathParts.AddRange(additionalPathParts); + + // Hack for build-server - when this path is generated in that envrionment it's missing the "src" folder. + // Not sure why, but if it's missing we'll add it in the right place. + if (!languageFolderPathParts.Contains("src")) + { + languageFolderPathParts.Insert(languageFolderPathParts.Count - additionalPathParts.Length, "src"); + } + + return string.Join(Path.DirectorySeparatorChar.ToString(), languageFolderPathParts); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasTests.cs new file mode 100644 index 0000000000..0cdbce3da8 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasTests.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; +using Constants = Umbraco.Cms.Core.Constants; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing +{ + // TODO: We should be able to decouple this from the base db tests since we're just mocking the services now + + [TestFixture] + public class ContentFinderByAliasTests : UrlRoutingTestBase + { + + [TestCase("/this/is/my/alias", 1001)] + [TestCase("/anotheralias", 1001)] + [TestCase("/page2/alias", 10011)] + [TestCase("/2ndpagealias", 10011)] + [TestCase("/only/one/alias", 100111)] + [TestCase("/ONLY/one/Alias", 100111)] + [TestCase("/alias43", 100121)] + public async Task Lookup_By_Url_Alias(string urlAsString, int nodeMatch) + { + var umbracoContextAccessor = GetUmbracoContextAccessor(urlAsString); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + var lookup = + new ContentFinderByUrlAlias(Mock.Of>(), Mock.Of(), VariationContextAccessor, umbracoContextAccessor); + + var result = lookup.TryFindContent(frequest); + + Assert.IsTrue(result); + Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch); + } + } +} diff --git a/tests/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasWithDomainsTests.cs similarity index 52% rename from tests/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasWithDomainsTests.cs index 5870000107..8b77c48599 100644 --- a/tests/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasWithDomainsTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -8,40 +9,18 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; -namespace Umbraco.Tests.Routing +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing { [TestFixture] - public class ContentFinderByAliasWithDomainsTests : ContentFinderByAliasTests + public class ContentFinderByAliasWithDomainsTests : UrlRoutingTestBase { - private PublishedContentType _publishedContentType; - protected override void Initialize() - { - base.Initialize(); - - var properties = new[] - { - new PublishedPropertyType( - propertyTypeAlias:"umbracoUrlAlias", - dataTypeId: Constants.DataTypes.Textbox, - isUserProperty:false, - variations: ContentVariation.Nothing, - propertyValueConverters:new PropertyValueConverterCollection(Enumerable.Empty()), - contentType:Mock.Of(), - publishedModelFactory:Mock.Of(), - factory:Mock.Of() - ) - }; - _publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); - } - - protected override PublishedContentType GetPublishedContentTypeByAlias(string alias) - { - if (alias == "Doc") return _publishedContentType; - return null; - } [TestCase("http://domain1.com/this/is/my/alias", "de-DE", -1001)] // alias to domain's page fails - no alias on domain's home [TestCase("http://domain1.com/page2/alias", "de-DE", 10011)] // alias to sub-page works @@ -56,10 +35,11 @@ namespace Umbraco.Tests.Routing public async Task Lookup_By_Url_Alias_And_Domain(string inputUrl, string expectedCulture, int expectedNode) { //SetDomains1(); + var umbracoContextAccessor = GetUmbracoContextAccessor(inputUrl); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); - var umbracoContext = GetUmbracoContext(inputUrl); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var request = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // must lookup domain publishedRouter.FindDomain(request); @@ -68,7 +48,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(expectedCulture, request.Culture); } - var finder = new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext)); + var finder = new ContentFinderByUrlAlias(Mock.Of>(), Mock.Of(), VariationContextAccessor, umbracoContextAccessor); var result = finder.TryFindContent(request); if (expectedNode > 0) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByIdTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByIdTests.cs new file mode 100644 index 0000000000..d84f9cb6fb --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByIdTests.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class ContentFinderByIdTests : PublishedSnapshotServiceTestBase + { + [SetUp] + public override void Setup() + { + base.Setup(); + + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + } + + [TestCase("/1046", 1046)] + public async Task Lookup_By_Id(string urlAsString, int nodeMatch) + { + var umbracoContextAccessor = GetUmbracoContextAccessor(urlAsString); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + var webRoutingSettings = new WebRoutingSettings(); + var lookup = new ContentFinderByIdPath( + Mock.Of>(x=>x.CurrentValue == webRoutingSettings), + Mock.Of>(), Mock.Of(), umbracoContextAccessor); + + + var result = lookup.TryFindContent(frequest); + + Assert.IsTrue(result); + Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByPageIdQueryTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByPageIdQueryTests.cs new file mode 100644 index 0000000000..9aceaf766a --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByPageIdQueryTests.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class ContentFinderByPageIdQueryTests : PublishedSnapshotServiceTestBase + { + [SetUp] + public override void Setup() + { + base.Setup(); + + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + } + + [TestCase("/?umbPageId=1046", 1046)] + [TestCase("/?UMBPAGEID=1046", 1046)] + [TestCase("/default.aspx?umbPageId=1046", 1046)] // TODO: Should this match?? + [TestCase("/some/other/page?umbPageId=1046", 1046)] // TODO: Should this match?? + [TestCase("/some/other/page.aspx?umbPageId=1046", 1046)] // TODO: Should this match?? + public async Task Lookup_By_Page_Id(string urlAsString, int nodeMatch) + { + var umbracoContextAccessor = GetUmbracoContextAccessor(urlAsString); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + + var queryStrings = HttpUtility.ParseQueryString(umbracoContext.CleanedUmbracoUrl.Query); + + var mockRequestAccessor = new Mock(); + mockRequestAccessor.Setup(x => x.GetRequestValue("umbPageID")) + .Returns(queryStrings["umbPageID"]); + + var lookup = new ContentFinderByPageIdQuery(mockRequestAccessor.Object, umbracoContextAccessor); + + var result = lookup.TryFindContent(frequest); + + Assert.IsTrue(result); + Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlAndTemplateTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlAndTemplateTests.cs new file mode 100644 index 0000000000..146d1c026e --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlAndTemplateTests.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class ContentFinderByUrlAndTemplateTests : PublishedSnapshotServiceTestBase + { + private IFileService _fileService; + + [SetUp] + public override void Setup() + { + base.Setup(); + + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + } + + protected override ServiceContext CreateServiceContext(IContentType[] contentTypes, IMediaType[] mediaTypes, IDataType[] dataTypes) + { + var serviceContext = base.CreateServiceContext(contentTypes, mediaTypes, dataTypes); + + var fileService = Mock.Get(serviceContext.FileService); + fileService.Setup(x => x.GetTemplate(It.IsAny())) + .Returns((string alias) => new Template(ShortStringHelper, alias, alias)); + + _fileService = fileService.Object; + + return serviceContext; + } + + [TestCase("/blah")] + [TestCase("/home/Sub1/blah")] + [TestCase("/Home/Sub1/Blah")] //different cases + public async Task Match_Document_By_Url_With_Template(string urlAsString) + { + GlobalSettings.HideTopLevelNodeFromPath = false; + + var umbracoContextAccessor = GetUmbracoContextAccessor(urlAsString); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + + var webRoutingSettings = new WebRoutingSettings(); + var lookup = new ContentFinderByUrlAndTemplate( + Mock.Of>(), + _fileService, + ContentTypeService, + umbracoContextAccessor, + Mock.Of>(x=>x.CurrentValue == webRoutingSettings)); + + var result = lookup.TryFindContent(frequest); + + IPublishedRequest request = frequest.Build(); + + Assert.IsTrue(result); + Assert.IsNotNull(frequest.PublishedContent); + var templateAlias = request.GetTemplateAlias(); + Assert.IsNotNull(templateAlias); + Assert.AreEqual("blah".ToUpperInvariant(), templateAlias.ToUpperInvariant()); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlTests.cs new file mode 100644 index 0000000000..556eed9622 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlTests.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class ContentFinderByUrlTests : PublishedSnapshotServiceTestBase + { + private async Task<(ContentFinderByUrl finder, IPublishedRequestBuilder frequest)> GetContentFinder(string urlString) + { + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var umbracoContextAccessor = GetUmbracoContextAccessor(urlString); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + var lookup = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); + return (lookup, frequest); + } + + [TestCase("/", 1046)] + [TestCase("/Sub1", 1173)] + [TestCase("/sub1", 1173)] + [TestCase("/home/sub1", -1)] // should fail + + // these two are special. getNiceUrl(1046) returns "/" but getNiceUrl(1172) cannot also return "/" so + // we've made it return "/test-page" => we have to support that URL back in the lookup... + [TestCase("/home", 1046)] + [TestCase("/test-page", 1172)] + public async Task Match_Document_By_Url_Hide_Top_Level(string urlString, int expectedId) + { + GlobalSettings.HideTopLevelNodeFromPath = true; + + var lookup = await GetContentFinder(urlString); + + Assert.IsTrue(GlobalSettings.HideTopLevelNodeFromPath); + + // FIXME: debugging - going further down, the routes cache is NOT empty?! + if (urlString == "/home/sub1") + System.Diagnostics.Debugger.Break(); + + var result = lookup.finder.TryFindContent(lookup.frequest); + + if (expectedId > 0) + { + Assert.IsTrue(result); + Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id); + } + else + { + Assert.IsFalse(result); + } + } + + [TestCase("/", 1046)] + [TestCase("/home", 1046)] + [TestCase("/home/Sub1", 1173)] + [TestCase("/Home/Sub1", 1173)] //different cases + public async Task Match_Document_By_Url(string urlString, int expectedId) + { + GlobalSettings.HideTopLevelNodeFromPath = false; + + var lookup = await GetContentFinder(urlString); + + Assert.IsFalse(GlobalSettings.HideTopLevelNodeFromPath); + + var result = lookup.finder.TryFindContent(lookup.frequest); + + Assert.IsTrue(result); + Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id); + } + /// + /// This test handles requests with special characters in the URL. + /// + /// + /// + [TestCase("/", 1046)] + [TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)] + [TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)] + public async Task Match_Document_By_Url_With_Special_Characters(string urlString, int expectedId) + { + GlobalSettings.HideTopLevelNodeFromPath = false; + + var lookup = await GetContentFinder(urlString); + + var result = lookup.finder.TryFindContent(lookup.frequest); + + Assert.IsTrue(result); + Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id); + } + + /// + /// This test handles requests with a hostname associated. + /// The logic for handling this goes through the DomainHelper and is a bit different + /// from what happens in a normal request - so it has a separate test with a mocked + /// hostname added. + /// + /// + /// + [TestCase("/", 1046)] + [TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)] + [TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)] + public async Task Match_Document_By_Url_With_Special_Characters_Using_Hostname(string urlString, int expectedId) + { + GlobalSettings.HideTopLevelNodeFromPath = false; + + var lookup = await GetContentFinder(urlString); + + lookup.frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite", -1, "en-US", false), new Uri("http://mysite/"))); + + var result = lookup.finder.TryFindContent(lookup.frequest); + + Assert.IsTrue(result); + Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id); + } + + /// + /// This test handles requests with a hostname with special characters associated. + /// The logic for handling this goes through the DomainHelper and is a bit different + /// from what happens in a normal request - so it has a separate test with a mocked + /// hostname added. + /// + /// + /// + [TestCase("/æøå/", 1046)] + [TestCase("/æøå/home/sub1", 1173)] + [TestCase("/æøå/home/sub1/custom-sub-3-with-accént-character", 1179)] + [TestCase("/æøå/home/sub1/custom-sub-4-with-æøå", 1180)] + public async Task Match_Document_By_Url_With_Special_Characters_In_Hostname(string urlString, int expectedId) + { + GlobalSettings.HideTopLevelNodeFromPath = false; + + var lookup = await GetContentFinder(urlString); + + lookup.frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite/æøå", -1, "en-US", false), new Uri("http://mysite/æøå"))); + + var result = lookup.finder.TryFindContent(lookup.frequest); + + Assert.IsTrue(result); + Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id); + } + } +} diff --git a/tests/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlWithDomainsTests.cs similarity index 83% rename from tests/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlWithDomainsTests.cs index f9ea04a288..d992140386 100644 --- a/tests/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -1,42 +1,47 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Routing; +using Umbraco.Extensions; -namespace Umbraco.Tests.Routing +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing { [TestFixture] public class ContentFinderByUrlWithDomainsTests : UrlRoutingTestBase { - void SetDomains3() + private void SetDomains3() { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangDeId, RootContentId = 1001, LanguageIsoCode = "de-DE"} - }); + var domainService = Mock.Get(DomainService); + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangDeId, RootContentId = 1001, LanguageIsoCode = "de-DE"} + }); } - void SetDomains4() + private void SetDomains4() { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"}, - new UmbracoDomain("domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, - new UmbracoDomain("domain1.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, - new UmbracoDomain("http://domain3.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1003, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain3.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain3.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"} - }); + var domainService = Mock.Get(DomainService); + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"}, + new UmbracoDomain("domain1.com/en") {Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, + new UmbracoDomain("domain1.com/fr") {Id = 3, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, + new UmbracoDomain("http://domain3.com/") {Id = 4, LanguageId = LangEngId, RootContentId = 1003, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain3.com/en") {Id = 5, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain3.com/fr") {Id = 6, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"} + }); } protected override string GetXmlContent(int templateId) - { - return @" + => @" @@ -115,7 +120,6 @@ namespace Umbraco.Tests.Routing "; - } [TestCase("http://domain1.com/", 1001)] [TestCase("http://domain1.com/1001-1", 10011)] @@ -125,16 +129,17 @@ namespace Umbraco.Tests.Routing { SetDomains3(); - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true }; + GlobalSettings.HideTopLevelNodeFromPath = true; - var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var umbracoContextAccessor = GetUmbracoContextAccessor(url); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); + var lookup = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); Assert.AreEqual(expectedId, frequest.PublishedContent.Id); @@ -164,19 +169,20 @@ namespace Umbraco.Tests.Routing SetDomains4(); // defaults depend on test environment - expectedCulture = expectedCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + expectedCulture ??= System.Threading.Thread.CurrentThread.CurrentUICulture.Name; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true }; + GlobalSettings.HideTopLevelNodeFromPath = true; - var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + var umbracoContextAccessor = GetUmbracoContextAccessor(url); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); Assert.AreEqual(expectedCulture, frequest.Culture); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); + var lookup = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); Assert.AreEqual(expectedId, frequest.PublishedContent.Id); diff --git a/tests/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/DomainsAndCulturesTests.cs similarity index 72% rename from tests/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/DomainsAndCulturesTests.cs index 73a88abfab..ccaeee9322 100644 --- a/tests/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/DomainsAndCulturesTests.cs @@ -1,124 +1,151 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Routing; +using Umbraco.Extensions; -namespace Umbraco.Tests.Routing +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing { [TestFixture] - internal class DomainsAndCulturesTests : UrlRoutingTestBase + public class DomainsAndCulturesTests : UrlRoutingTestBase { - protected override void Compose() - { - base.Compose(); - - Builder.Services.AddTransient(); - } - private void SetDomains1() { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("domain1.com/") + var domainService = Mock.Get(DomainService); + + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] { - Id = 1, - LanguageId = LangDeId, - RootContentId = 1001, - LanguageIsoCode = "de-DE" - }, - new UmbracoDomain("domain1.com/en") - { - Id = 1, - LanguageId = LangEngId, - RootContentId = 10011, - LanguageIsoCode = "en-US" - }, - new UmbracoDomain("domain1.com/fr") - { - Id = 1, - LanguageId = LangFrId, - RootContentId = 10012, - LanguageIsoCode = "fr-FR" - } - }); + new UmbracoDomain("domain1.com/") + { + Id = 1, + LanguageId = LangDeId, + RootContentId = 1001, + LanguageIsoCode = "de-DE" + }, + new UmbracoDomain("domain1.com/en") + { + Id = 2, + LanguageId = LangEngId, + RootContentId = 10011, + LanguageIsoCode = "en-US" + }, + new UmbracoDomain("domain1.com/fr") + { + Id = 3, + LanguageId = LangFrId, + RootContentId = 10012, + LanguageIsoCode = "fr-FR" + } + }); } private void SetDomains2() { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("domain1.com/") + var domainService = Mock.Get(DomainService); + + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] { - Id = 1, - LanguageId = LangDeId, - RootContentId = 1001, - LanguageIsoCode = "de-DE" - }, - new UmbracoDomain("domain1.com/en") + new UmbracoDomain("domain1.com/") + { + Id = 1, + LanguageId = LangDeId, + RootContentId = 1001, + LanguageIsoCode = "de-DE" + }, + new UmbracoDomain("domain1.com/en") + { + Id = 2, + LanguageId = LangEngId, + RootContentId = 10011, + LanguageIsoCode = "en-US" + }, + new UmbracoDomain("domain1.com/fr") + { + Id = 3, + LanguageId = LangFrId, + RootContentId = 10012, + LanguageIsoCode = "fr-FR" + }, + new UmbracoDomain("*1001") + { + Id = 4, + LanguageId = LangDeId, + RootContentId = 1001, + LanguageIsoCode = "de-DE" + }, + new UmbracoDomain("*10011") + { + Id = 5, + LanguageId = LangCzId, + RootContentId = 10011, + LanguageIsoCode = "cs-CZ" + }, + new UmbracoDomain("*100112") + { + Id = 6, + LanguageId = LangNlId, + RootContentId = 100112, + LanguageIsoCode = "nl-NL" + }, + new UmbracoDomain("*1001122") + { + Id = 7, + LanguageId = LangDkId, + RootContentId = 1001122, + LanguageIsoCode = "da-DK" + }, + new UmbracoDomain("*10012") + { + Id = 8, + LanguageId = LangNlId, + RootContentId = 10012, + LanguageIsoCode = "nl-NL" + }, + new UmbracoDomain("*10031") + { + Id = 9, + LanguageId = LangNlId, + RootContentId =10031, + LanguageIsoCode = "nl-NL" + } + }); + } + + // domains such as "/en" are natively supported, and when instanciating + // DomainAndUri for them, the host will come from the current request + // + private void SetDomains3() + { + var domainService = Mock.Get(DomainService); + + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] { - Id = 1, - LanguageId = LangEngId, - RootContentId = 10011, - LanguageIsoCode = "en-US" - }, - new UmbracoDomain("domain1.com/fr") - { - Id = 1, - LanguageId = LangFrId, - RootContentId = 10012, - LanguageIsoCode = "fr-FR" - }, - new UmbracoDomain("*1001") - { - Id = 1, - LanguageId = LangDeId, - RootContentId = 1001, - LanguageIsoCode = "de-DE" - }, - new UmbracoDomain("*10011") - { - Id = 1, - LanguageId = LangCzId, - RootContentId = 10011, - LanguageIsoCode = "cs-CZ" - }, - new UmbracoDomain("*100112") - { - Id = 1, - LanguageId = LangNlId, - RootContentId = 100112, - LanguageIsoCode = "nl-NL" - }, - new UmbracoDomain("*1001122") - { - Id = 1, - LanguageId = LangDkId, - RootContentId = 1001122, - LanguageIsoCode = "da-DK" - }, - new UmbracoDomain("*10012") - { - Id = 1, - LanguageId = LangNlId, - RootContentId = 10012, - LanguageIsoCode = "nl-NL" - }, - new UmbracoDomain("*10031") - { - Id = 1, - LanguageId = LangNlId, - RootContentId =10031, - LanguageIsoCode = "nl-NL" - } - }); + new UmbracoDomain("/en") + { + Id = 1, + LanguageId = LangEngId, + RootContentId = 10011, + LanguageIsoCode = "en-US" + }, + new UmbracoDomain("/fr") + { + Id = 2, + LanguageId = LangFrId, + RootContentId = 10012, + LanguageIsoCode = "fr-FR" + } + }); } protected override string GetXmlContent(int templateId) - { - return @" + => @" @@ -250,7 +277,6 @@ namespace Umbraco.Tests.Routing "; - } #region Cases [TestCase("http://domain1.com/", "de-DE", 1001)] @@ -265,18 +291,19 @@ namespace Umbraco.Tests.Routing { SetDomains1(); - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; + GlobalSettings.HideTopLevelNodeFromPath = false; - var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + var umbracoContextAccessor = GetUmbracoContextAccessor(inputUrl); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); Assert.AreEqual(expectedCulture, frequest.Culture); - var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); + var finder = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); var result = finder.TryFindContent(frequest); Assert.IsTrue(result); @@ -311,19 +338,20 @@ namespace Umbraco.Tests.Routing SetDomains2(); // defaults depend on test environment - expectedCulture = expectedCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + expectedCulture ??= System.Threading.Thread.CurrentThread.CurrentUICulture.Name; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; + GlobalSettings.HideTopLevelNodeFromPath = false; - var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + var umbracoContextAccessor = GetUmbracoContextAccessor(inputUrl); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); // find document - var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); + var finder = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); var result = finder.TryFindContent(frequest); // apply wildcard domain @@ -333,29 +361,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(expectedCulture, frequest.Culture); Assert.AreEqual(frequest.PublishedContent.Id, expectedNode); } - // domains such as "/en" are natively supported, and when instanciating - // DomainAndUri for them, the host will come from the current request - // - private void SetDomains3() - { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("/en") - { - Id = 1, - LanguageId = LangEngId, - RootContentId = 10011, - LanguageIsoCode = "en-US" - }, - new UmbracoDomain("/fr") - { - Id = 1, - LanguageId = LangFrId, - RootContentId = 10012, - LanguageIsoCode = "fr-FR" - } - }); - } + #region Cases [TestCase("http://domain1.com/en", "en-US", 10011)] @@ -367,10 +373,13 @@ namespace Umbraco.Tests.Routing { SetDomains3(); - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + GlobalSettings.HideTopLevelNodeFromPath = false; + + + var umbracoContextAccessor = GetUmbracoContextAccessor(inputUrl); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); @@ -378,7 +387,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(expectedCulture, frequest.Culture); - var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); + var finder = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); var result = finder.TryFindContent(frequest); Assert.IsTrue(result); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/GetContentUrlsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/GetContentUrlsTests.cs new file mode 100644 index 0000000000..f54442e467 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/GetContentUrlsTests.cs @@ -0,0 +1,216 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class GetContentUrlsTests : PublishedSnapshotServiceTestBase + { + private WebRoutingSettings _webRoutingSettings; + private RequestHandlerSettings _requestHandlerSettings; + + [SetUp] + public override void Setup() + { + base.Setup(); + + _webRoutingSettings = new WebRoutingSettings(); + _requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + + GlobalSettings.HideTopLevelNodeFromPath = false; + + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + } + + private ILocalizedTextService GetTextService() + { + var textService = new Mock(); + textService.Setup(x => x.Localize( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>() + )) + .Returns((string key, string alias, CultureInfo culture, IDictionary args) + => $"{key}/{alias}"); + + return textService.Object; + } + + private ILocalizationService GetLangService(params string[] isoCodes) + { + var allLangs = isoCodes + .Select(CultureInfo.GetCultureInfo) + .Select(culture => new Language(GlobalSettings, culture.Name) + { + CultureName = culture.DisplayName, + IsDefault = true, + IsMandatory = true + }).ToArray(); + + + var langServiceMock = new Mock(); + langServiceMock.Setup(x => x.GetAllLanguages()).Returns(allLangs); + langServiceMock.Setup(x => x.GetDefaultLanguageIsoCode()).Returns(allLangs.First(x=>x.IsDefault).IsoCode); + + return langServiceMock.Object; + } + + [Test] + public async Task Content_Not_Published() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + var content = ContentBuilder.CreateBasicContent(contentType); + content.Id = 1046; // FIXME: we are using this ID only because it's built into the test XML published cache + content.Path = "-1,1046"; + + var umbracoContextAccessor = GetUmbracoContextAccessor("http://localhost:8000"); + var publishedRouter = CreatePublishedRouter( + umbracoContextAccessor, + new[] { new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor) }); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, _requestHandlerSettings, _webRoutingSettings, out UriUtility uriUtility); + + var urls = (await content.GetContentUrlsAsync( + publishedRouter, + umbracoContext, + GetLangService("en-US", "fr-FR"), + GetTextService(), + Mock.Of(), + VariationContextAccessor, + Mock.Of>(), + uriUtility, + urlProvider)).ToList(); + + Assert.AreEqual(1, urls.Count); + Assert.AreEqual("content/itemNotPublished", urls[0].Text); + Assert.IsFalse(urls[0].IsUrl); + } + + [Test] + public async Task Invariant_Root_Content_Published_No_Domains() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + var content = ContentBuilder.CreateBasicContent(contentType); + content.Id = 1046; // FIXME: we are using this ID only because it's built into the test XML published cache + content.Path = "-1,1046"; + content.Published = true; + + var umbracoContextAccessor = GetUmbracoContextAccessor("http://localhost:8000"); + var publishedRouter = CreatePublishedRouter( + umbracoContextAccessor, + new[] { new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor) }); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, _requestHandlerSettings, _webRoutingSettings, out UriUtility uriUtility); + + var urls = (await content.GetContentUrlsAsync( + publishedRouter, + umbracoContext, + GetLangService("en-US", "fr-FR"), + GetTextService(), + Mock.Of(), + VariationContextAccessor, + Mock.Of>(), + uriUtility, + urlProvider)).ToList(); + + + Assert.AreEqual(2, urls.Count); + + var enUrl = urls.First(x => x.Culture == "en-US"); + + Assert.AreEqual("/home/", enUrl.Text); + Assert.AreEqual("en-US", enUrl.Culture); + Assert.IsTrue(enUrl.IsUrl); + + var frUrl = urls.First(x => x.Culture == "fr-FR"); + + Assert.IsFalse(frUrl.IsUrl); + } + + [Test] + public async Task Invariant_Child_Content_Published_No_Domains() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + var parent = ContentBuilder.CreateBasicContent(contentType); + parent.Id = 1046; // FIXME: we are using this ID only because it's built into the test XML published cache + parent.Name = "home"; + parent.Path = "-1,1046"; + parent.Published = true; + var child = ContentBuilder.CreateBasicContent(contentType); + child.Name = "sub1"; + child.Id = 1173; // FIXME: we are using this ID only because it's built into the test XML published cache + child.Path = "-1,1046,1173"; + child.Published = true; + + + var umbracoContextAccessor = GetUmbracoContextAccessor("http://localhost:8000"); + var publishedRouter = CreatePublishedRouter( + umbracoContextAccessor, + new[] { new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor) }); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + + + var localizationService = GetLangService("en-US", "fr-FR"); + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, _requestHandlerSettings, _webRoutingSettings, out UriUtility uriUtility); + + var urls = (await child.GetContentUrlsAsync( + publishedRouter, + umbracoContext, + localizationService, + GetTextService(), + Mock.Of(), + VariationContextAccessor, + Mock.Of>(), + uriUtility, + urlProvider)).ToList(); + + Assert.AreEqual(2, urls.Count); + + var enUrl = urls.First(x => x.Culture == "en-US"); + + Assert.AreEqual("/home/sub1/", enUrl.Text); + Assert.AreEqual("en-US", enUrl.Culture); + Assert.IsTrue(enUrl.IsUrl); + + var frUrl = urls.First(x => x.Culture == "fr-FR"); + + Assert.IsFalse(frUrl.IsUrl); + } + + // TODO: We need a lot of tests here, the above was just to get started with being able to unit test this method + // * variant URLs without domains assigned, what happens? + // * variant URLs with domains assigned, but also having more languages installed than there are domains/cultures assigned + // * variant URLs with an ancestor culture unpublished + // * invariant URLs with ancestors as variants + // * ... probably a lot more + + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/PublishedRouterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/PublishedRouterTests.cs new file mode 100644 index 0000000000..94de25dadb --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/PublishedRouterTests.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Tests.Common; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class PublishedRouterTests + { + private PublishedRouter CreatePublishedRouter(IUmbracoContextAccessor umbracoContextAccessor) + => new PublishedRouter( + Mock.Of>(x=>x.CurrentValue == new WebRoutingSettings()), + new ContentFinderCollection(() => Enumerable.Empty()), + new TestLastChanceFinder(), + new TestVariationContextAccessor(), + Mock.Of(), + Mock.Of>(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + umbracoContextAccessor, + Mock.Of()); + + private IUmbracoContextAccessor GetUmbracoContextAccessor() + { + var uri = new Uri("http://example.com"); + var umbracoContext = Mock.Of(x => x.CleanedUmbracoUrl == uri); + var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); + return umbracoContextAccessor; + } + + [Test] + public async Task ConfigureRequest_Returns_False_Without_HasPublishedContent() + { + var umbracoContextAccessor = GetUmbracoContextAccessor(); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var request = await publishedRouter.CreateRequestAsync(umbracoContextAccessor.GetRequiredUmbracoContext().CleanedUmbracoUrl); + var result = publishedRouter.BuildRequest(request); + + Assert.IsFalse(result.Success()); + } + + [Test] + public async Task ConfigureRequest_Returns_False_When_IsRedirect() + { + var umbracoContextAccessor = GetUmbracoContextAccessor(); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var request = await publishedRouter.CreateRequestAsync(umbracoContextAccessor.GetRequiredUmbracoContext().CleanedUmbracoUrl); + var content = GetPublishedContentMock(); + request.SetPublishedContent(content.Object); + request.SetCulture("en-AU"); + request.SetRedirect("/hello"); + var result = publishedRouter.BuildRequest(request); + + Assert.IsFalse(result.Success()); + } + + private Mock GetPublishedContentMock() + { + var pc = new Mock(); + pc.Setup(content => content.Id).Returns(1); + pc.Setup(content => content.Name).Returns("test"); + pc.Setup(content => content.CreateDate).Returns(DateTime.Now); + pc.Setup(content => content.UpdateDate).Returns(DateTime.Now); + pc.Setup(content => content.Path).Returns("-1,1"); + pc.Setup(content => content.Parent).Returns(() => null); + pc.Setup(content => content.Properties).Returns(new Collection()); + pc.Setup(content => content.ContentType).Returns(new PublishedContentType(Guid.NewGuid(), 22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing)); + return pc; + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs new file mode 100644 index 0000000000..59884882bf --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class UrlProviderWithHideTopLevelNodeFromPathTests : PublishedSnapshotServiceTestBase + { + [SetUp] + public override void Setup() + { + base.Setup(); + + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + GlobalSettings.HideTopLevelNodeFromPath = true; + } + + [TestCase(1046, "/")] + [TestCase(1173, "/sub1/")] + [TestCase(1174, "/sub1/sub2/")] + [TestCase(1176, "/sub1/sub-3/")] + [TestCase(1177, "/sub1/custom-sub-1/")] + [TestCase(1178, "/sub1/custom-sub-2/")] + [TestCase(1175, "/sub-2/")] + [TestCase(1172, "/test-page/")] // not hidden because not first root + public void Get_Url_Hiding_Top_Level(int nodeId, string niceUrlMatch) + { + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + + var umbracoContextAccessor = GetUmbracoContextAccessor("/test"); + + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + var result = urlProvider.GetUrl(nodeId); + Assert.AreEqual(niceUrlMatch, result); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs new file mode 100644 index 0000000000..8c68aa5c36 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs @@ -0,0 +1,336 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Tests.Common; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class UrlProviderWithoutHideTopLevelNodeFromPathTests : PublishedSnapshotServiceTestBase + { + private const string CacheKeyPrefix = "NuCache.ContentCache.RouteByContent"; + + [SetUp] + public override void Setup() + { + base.Setup(); + + + + GlobalSettings.HideTopLevelNodeFromPath = false; + } + + private void PopulateCache(string culture = "fr-FR") + { + var dataTypes = GetDefaultDataTypes(); + var propertyDataTypes = new Dictionary + { + // we only have one data type for this test which will be resolved with string empty. + [string.Empty] = dataTypes[0] + }; + var contentType1 = new ContentType(ShortStringHelper, -1); + + ContentData rootData = new ContentDataBuilder() + .WithName("Page" + Guid.NewGuid()) + .WithCultureInfos(new Dictionary + { + [culture] = new CultureVariation + { + Name = "root", + IsDraft = true, + Date = DateTime.Now, + UrlSegment = "root" + }, + }) + .Build(ShortStringHelper, propertyDataTypes, contentType1, "alias"); + + ContentNodeKit root = ContentNodeKitBuilder.CreateWithContent( + contentType1.Id, + 9876, $"-1,9876", + draftData: rootData, + publishedData: rootData); + + ContentData parentData = new ContentDataBuilder() + .WithName("Page" + Guid.NewGuid()) + .WithCultureInfos(new Dictionary + { + [culture] = new CultureVariation + { + Name = "home", + IsDraft = true, + Date = DateTime.Now, + UrlSegment = "home" + }, + }) + .Build(); + + ContentNodeKit parent = ContentNodeKitBuilder.CreateWithContent( + contentType1.Id, + 5432, $"-1,9876,5432", + parentContentId: 9876, + draftData: parentData, + publishedData: parentData); + + ContentData contentData = new ContentDataBuilder() + .WithName("Page" + Guid.NewGuid()) + .WithCultureInfos(new Dictionary + { + [culture] = new CultureVariation + { + Name = "name-fr2", + IsDraft = true, + Date = DateTime.Now, + UrlSegment = "test-fr" + }, + }) + .Build(); + + ContentNodeKit content = ContentNodeKitBuilder.CreateWithContent( + contentType1.Id, + 1234, $"-1,9876,5432,1234", + parentContentId: 5432, + draftData: contentData, + publishedData: contentData); + + InitializedCache(new[] { root, parent, content }, new[] { contentType1 }, dataTypes: dataTypes); + } + + private void SetDomains1() + { + var domainService = Mock.Get(DomainService); + + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("http://example.us/") {Id = 1, RootContentId = 9876, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://example.fr/") {Id = 2, RootContentId = 9876, LanguageIsoCode = "fr-FR"} + }); + } + + /// + /// This checks that when we retrieve a NiceUrl for multiple items that there are no issues with cache overlap + /// and that they are all cached correctly. + /// + [Test] + public void Ensure_Cache_Is_Correct() + { + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = false }; + + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var umbracoContextAccessor = GetUmbracoContextAccessor("/test"); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + var samples = new Dictionary { + { 1046, "/home" }, + { 1173, "/home/sub1" }, + { 1174, "/home/sub1/sub2" }, + { 1176, "/home/sub1/sub-3" }, + { 1177, "/home/sub1/custom-sub-1" }, + { 1178, "/home/sub1/custom-sub-2" }, + { 1175, "/home/sub-2" }, + { 1172, "/test-page" } + }; + + foreach (var sample in samples) + { + var result = urlProvider.GetUrl(sample.Key); + Assert.AreEqual(sample.Value, result); + } + + var randomSample = new KeyValuePair(1177, "/home/sub1/custom-sub-1"); + for (int i = 0; i < 5; i++) + { + var result = urlProvider.GetUrl(randomSample.Key); + Assert.AreEqual(randomSample.Value, result); + } + + + var cache = (FastDictionaryAppCache)umbracoContext.PublishedSnapshot.ElementsCache; + var cachedRoutes = cache.Keys.Where(x => x.StartsWith(CacheKeyPrefix)).ToList(); + Assert.AreEqual(8, cachedRoutes.Count); + + foreach (var sample in samples) + { + var cacheKey = $"{CacheKeyPrefix}[P:{sample.Key}]"; + var found = (string)cache.Get(cacheKey); + Assert.IsNotNull(found); + Assert.AreEqual(sample.Value, found); + } + } + + [TestCase(1046, "/home/")] + [TestCase(1173, "/home/sub1/")] + [TestCase(1174, "/home/sub1/sub2/")] + [TestCase(1176, "/home/sub1/sub-3/")] + [TestCase(1177, "/home/sub1/custom-sub-1/")] + [TestCase(1178, "/home/sub1/custom-sub-2/")] + [TestCase(1175, "/home/sub-2/")] + [TestCase(1172, "/test-page/")] + public void Get_Url_Not_Hiding_Top_Level(int nodeId, string niceUrlMatch) + { + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var umbracoContextAccessor = GetUmbracoContextAccessor("/test"); + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + var result = urlProvider.GetUrl(nodeId); + Assert.AreEqual(niceUrlMatch, result); + } + + [Test] + [TestCase("fr-FR", ExpectedResult = "#")] // Non default cultures cannot return urls + [TestCase("en-US", ExpectedResult = "/root/home/test-fr/")] // Default culture can return urls + public string Get_Url_For_Culture_Variant_Without_Domains_Non_Current_Url(string culture) + { + const string currentUri = "http://example.us/test"; + + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + + PopulateCache(culture); + + var umbracoContextAccessor = GetUmbracoContextAccessor(currentUri); + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + + //even though we are asking for a specific culture URL, there are no domains assigned so all that can be returned is a normal relative URL. + var url = urlProvider.GetUrl(1234, culture: culture); + + return url; + } + + /// + /// This tests DefaultUrlProvider.GetUrl with a specific culture when the current URL is the culture specific domain + /// + [Test] + public void Get_Url_For_Culture_Variant_With_Current_Url() + { + const string currentUri = "http://example.fr/test"; + + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + + PopulateCache(); + + SetDomains1(); + + var umbracoContextAccessor = GetUmbracoContextAccessor(currentUri); + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + var url = urlProvider.GetUrl(1234, culture: "fr-FR"); + + Assert.AreEqual("/home/test-fr/", url); + } + + /// + /// This tests DefaultUrlProvider.GetUrl with a specific culture when the current URL is not the culture specific domain + /// + [Test] + public void Get_Url_For_Culture_Variant_Non_Current_Url() + { + const string currentUri = "http://example.us/test"; + + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + + PopulateCache(); + + SetDomains1(); + + var umbracoContextAccessor = GetUmbracoContextAccessor(currentUri); + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + var url = urlProvider.GetUrl(1234, culture: "fr-FR"); + + //the current uri is not the culture specific domain we want, so the result is an absolute path to the culture specific domain + Assert.AreEqual("http://example.fr/home/test-fr/", url); + } + + [Test] + public void Get_Url_Relative_Or_Absolute() + { + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var umbracoContextAccessor = GetUmbracoContextAccessor("http://example.com/test"); + + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + Assert.AreEqual("/home/sub1/custom-sub-1/", urlProvider.GetUrl(1177)); + + urlProvider.Mode = UrlMode.Absolute; + Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", urlProvider.GetUrl(1177)); + } + + [Test] + public void Get_Url_Unpublished() + { + var requestHandlerSettings = new RequestHandlerSettings(); + + string xml = PublishedContentXml.BaseWebTestXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var umbracoContextAccessor = GetUmbracoContextAccessor("http://example.com/test"); + + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + //mock the Umbraco settings that we need + + Assert.AreEqual("#", urlProvider.GetUrl(999999)); + + urlProvider.Mode = UrlMode.Absolute; + + Assert.AreEqual("#", urlProvider.GetUrl(999999)); + } + } +} diff --git a/tests/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlRoutingTestBase.cs similarity index 81% rename from tests/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlRoutingTestBase.cs index 82dea51d1c..65eea64f75 100644 --- a/tests/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlRoutingTestBase.cs @@ -1,68 +1,55 @@ -using System; +using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Routing; -using Constants = Umbraco.Cms.Core.Constants; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; -namespace Umbraco.Tests.Routing +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing { - // TODO: We should be able to decouple this from the base db tests since we're just mocking the services now - [TestFixture] - public class ContentFinderByAliasTests : UrlRoutingTestBase + public abstract class UrlRoutingTestBase : PublishedSnapshotServiceTestBase { - private PublishedContentType _publishedContentType; - - protected override void Initialize() + [SetUp] + public override void Setup() { - base.Initialize(); + base.Setup(); + + string xml = GetXmlContent(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); - var properties = new[] - { - new PublishedPropertyType("umbracoUrlAlias", Constants.DataTypes.Textbox, false, ContentVariation.Nothing, - new PropertyValueConverterCollection(Enumerable.Empty()), - Mock.Of(), - Mock.Of()), - }; - _publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); } - protected override PublishedContentType GetPublishedContentTypeByAlias(string alias) + // Sets up the mock domain service + protected override ServiceContext CreateServiceContext(IContentType[] contentTypes, IMediaType[] mediaTypes, IDataType[] dataTypes) { - if (alias == "Doc") return _publishedContentType; - return null; + var serviceContext = base.CreateServiceContext(contentTypes, mediaTypes, dataTypes); + + //setup mock domain service + var domainService = Mock.Get(serviceContext.DomainService); + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("domain1.com/"){Id = 1, LanguageId = LangDeId, RootContentId = 1001, LanguageIsoCode = "de-DE"}, + new UmbracoDomain("domain1.com/en"){Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, + new UmbracoDomain("domain1.com/fr"){Id = 3, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"} + }); + + return serviceContext; } - [TestCase("/this/is/my/alias", 1001)] - [TestCase("/anotheralias", 1001)] - [TestCase("/page2/alias", 10011)] - [TestCase("/2ndpagealias", 10011)] - [TestCase("/only/one/alias", 100111)] - [TestCase("/ONLY/one/Alias", 100111)] - [TestCase("/alias43", 100121)] - public async Task Lookup_By_Url_Alias(string urlAsString, int nodeMatch) - { - var umbracoContext = GetUmbracoContext(urlAsString); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var lookup = - new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext)); - - var result = lookup.TryFindContent(frequest); - - Assert.IsTrue(result); - Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch); - } - - protected override string GetXmlContent(int templateId) - { - return @" + protected virtual string GetXmlContent(int templateId) + => @" @@ -147,7 +134,12 @@ namespace Umbraco.Tests.Routing "; - } + public const int LangDeId = 333; + public const int LangEngId = 334; + public const int LangFrId = 335; + public const int LangCzId = 336; + public const int LangNlId = 337; + public const int LangDkId = 338; } } diff --git a/tests/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsProviderWithDomainsTests.cs similarity index 56% rename from tests/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsProviderWithDomainsTests.cs index 4f9fdef8e0..28c8dd7194 100644 --- a/tests/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsProviderWithDomainsTests.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; @@ -13,77 +14,83 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Tests.Common; using Umbraco.Extensions; -using Umbraco.Tests.LegacyXmlPublishedCache; -namespace Umbraco.Tests.Routing +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing { [TestFixture] public class UrlsProviderWithDomainsTests : UrlRoutingTestBase { - private IUmbracoContextAccessor UmbracoContextAccessor { get; } = new TestUmbracoContextAccessor(); - protected override void Compose() - { - base.Compose(); + private const string CacheKeyPrefix = "NuCache.ContentCache.RouteByContent"; - Builder.Services.AddUnique(Mock.Of()); - Builder.Services.AddTransient(); + private void SetDomains1() + { + var domainService = Mock.Get(DomainService); + + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("domain1.com") {Id = 1, LanguageId = LangFrId, RootContentId = 1001, LanguageIsoCode = "fr-FR"} + }); } - void SetDomains1() + private void SetDomains2() { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("domain1.com") {Id = 1, LanguageId = LangFrId, RootContentId = 1001, LanguageIsoCode = "fr-FR"} - }); + var domainService = Mock.Get(DomainService); + + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("http://domain1.com/foo") {Id = 1, LanguageId = LangFrId, RootContentId = 1001, LanguageIsoCode = "fr-FR"} + }); } - void SetDomains2() + private void SetDomains3() { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("http://domain1.com/foo") {Id = 1, LanguageId = LangFrId, RootContentId = 1001, LanguageIsoCode = "fr-FR"} - }); + var domainService = Mock.Get(DomainService); + + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangFrId, RootContentId = 10011, LanguageIsoCode = "fr-FR"} + }); } - void SetDomains3() + private void SetDomains4() { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangFrId, RootContentId = 10011, LanguageIsoCode = "fr-FR"} - }); + var domainService = Mock.Get(DomainService); + + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain1.com/en") {Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain1.com/fr") {Id = 3, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, + new UmbracoDomain("http://domain3.com/") {Id = 4, LanguageId = LangEngId, RootContentId = 1003, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain3.com/en") {Id = 5, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain3.com/fr") {Id = 6, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"} + }); } - void SetDomains4() + private void SetDomains5() { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain1.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, - new UmbracoDomain("http://domain3.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1003, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain3.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain3.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"} - }); - } + var domainService = Mock.Get(DomainService); - void SetDomains5() - { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("http://domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain1a.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain1b.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain1.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, - new UmbracoDomain("http://domain1a.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, - new UmbracoDomain("http://domain1b.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, - new UmbracoDomain("http://domain3.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain3.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"} - }); + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("http://domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain1a.com/en") {Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain1b.com/en") {Id = 3, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain1.com/fr") {Id = 4, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, + new UmbracoDomain("http://domain1a.com/fr") {Id = 5, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, + new UmbracoDomain("http://domain1b.com/fr") {Id = 6, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}, + new UmbracoDomain("http://domain3.com/en") {Id = 7, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain3.com/fr") {Id = 8, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"} + }); } protected override string GetXmlContent(int templateId) - { - return @" + => @" @@ -162,7 +169,6 @@ namespace Umbraco.Tests.Routing "; - } // with one simple domain "domain1.com" // basic tests @@ -179,21 +185,18 @@ namespace Umbraco.Tests.Routing [TestCase(10011, "https://domain1.com", false, "/1001-1/")] public void Get_Url_SimpleDomain(int nodeId, string currentUrl, bool absolute, string expected) { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider(Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - SetDomains1(); + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + GlobalSettings.HideTopLevelNodeFromPath = false; + + var umbracoContextAccessor = GetUmbracoContextAccessor("/test"); + + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + var currentUri = new Uri(currentUrl); var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; - var result = publishedUrlProvider.GetUrl(nodeId, mode, current: currentUri); + var result = urlProvider.GetUrl(nodeId, mode, current: currentUri); Assert.AreEqual(expected, result); } @@ -212,21 +215,18 @@ namespace Umbraco.Tests.Routing [TestCase(10011, "https://domain1.com", false, "http://domain1.com/foo/1001-1/")] public void Get_Url_SimpleWithSchemeAndPath(int nodeId, string currentUrl, bool absolute, string expected) { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider(Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - SetDomains2(); + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + GlobalSettings.HideTopLevelNodeFromPath = false; + + var umbracoContextAccessor = GetUmbracoContextAccessor("/test"); + + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + var currentUri = new Uri(currentUrl); var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; - var result = publishedUrlProvider.GetUrl(nodeId, mode, current : currentUri); + var result = urlProvider.GetUrl(nodeId, mode, current: currentUri); Assert.AreEqual(expected, result); } @@ -237,21 +237,18 @@ namespace Umbraco.Tests.Routing [TestCase(1002, "http://domain1.com", false, "/1002/")] public void Get_Url_DeepDomain(int nodeId, string currentUrl, bool absolute, string expected) { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider(Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - SetDomains3(); + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + GlobalSettings.HideTopLevelNodeFromPath = false; + + var umbracoContextAccessor = GetUmbracoContextAccessor("/test"); + + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + var currentUri = new Uri(currentUrl); var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; - var result = publishedUrlProvider.GetUrl(nodeId, mode, current : currentUri); + var result = urlProvider.GetUrl(nodeId, mode, current: currentUri); Assert.AreEqual(expected, result); } @@ -268,152 +265,130 @@ namespace Umbraco.Tests.Routing [TestCase(100321, "http://domain3.com", false, "/fr/1003-2-1/")] public void Get_Url_NestedDomains(int nodeId, string currentUrl, bool absolute, string expected) { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - SetDomains4(); + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + GlobalSettings.HideTopLevelNodeFromPath = false; + + var umbracoContextAccessor = GetUmbracoContextAccessor("/test"); + + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + var currentUri = new Uri(currentUrl); var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; - var result = publishedUrlProvider.GetUrl(nodeId, mode, current : currentUri); + var result = urlProvider.GetUrl(nodeId, mode, current: currentUri); Assert.AreEqual(expected, result); } [Test] public void Get_Url_DomainsAndCache() { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - SetDomains4(); - string ignore; - ignore = publishedUrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com")); - ignore = publishedUrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com")); - ignore = publishedUrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com")); - ignore = publishedUrlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com")); - ignore = publishedUrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com")); - ignore = publishedUrlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com")); - ignore = publishedUrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com")); - ignore = publishedUrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain2.com")); - ignore = publishedUrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain2.com")); - ignore = publishedUrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain2.com")); - ignore = publishedUrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain2.com")); + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + GlobalSettings.HideTopLevelNodeFromPath = false; - var cache = umbracoContext.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); - var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); + var umbracoContextAccessor = GetUmbracoContextAccessor("/test"); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + + + string ignore; + ignore = urlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = urlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = urlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = urlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = urlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = urlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = urlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = urlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain2.com")); + ignore = urlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain2.com")); + ignore = urlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain2.com")); + ignore = urlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain2.com")); + + + var cache = (FastDictionaryAppCache)umbracoContext.PublishedSnapshot.ElementsCache; + var cachedRoutes = cache.Keys.Where(x => x.StartsWith(CacheKeyPrefix)).ToList(); Assert.AreEqual(7, cachedRoutes.Count); - var cachedIds = cache.RoutesCache.GetCachedIds(); - Assert.AreEqual(0, cachedIds.Count); + //var cachedIds = cache.RoutesCache.GetCachedIds(); + //Assert.AreEqual(0, cachedIds.Count); - CheckRoute(cachedRoutes, cachedIds, 1001, "1001/"); - CheckRoute(cachedRoutes, cachedIds, 10011, "10011/"); - CheckRoute(cachedRoutes, cachedIds, 100111, "10011/1001-1-1"); - CheckRoute(cachedRoutes, cachedIds, 10012, "10012/"); - CheckRoute(cachedRoutes, cachedIds, 100121, "10012/1001-2-1"); - CheckRoute(cachedRoutes, cachedIds, 10013, "1001/1001-3"); - CheckRoute(cachedRoutes, cachedIds, 1002, "/1002"); + CheckRoute(cache, 1001, "1001/"); + CheckRoute(cache, 10011, "10011/"); + CheckRoute(cache, 100111, "10011/1001-1-1"); + CheckRoute(cache, 10012, "10012/"); + CheckRoute(cache, 100121, "10012/1001-2-1"); + CheckRoute(cache, 10013, "1001/1001-3"); + CheckRoute(cache, 1002, "/1002"); // use the cache - Assert.AreEqual("/", publishedUrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/en/", publishedUrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/en/1001-1-1/", publishedUrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/fr/", publishedUrlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/fr/1001-2-1/", publishedUrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/1001-3/", publishedUrlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/1002/", publishedUrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/", urlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/en/", urlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/en/1001-1-1/", urlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/fr/", urlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/fr/1001-2-1/", urlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/1001-3/", urlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/1002/", urlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com"))); - Assert.AreEqual("http://domain1.com/fr/1001-2-1/", publishedUrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain2.com"))); + Assert.AreEqual("http://domain1.com/fr/1001-2-1/", urlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain2.com"))); } - private static void CheckRoute(IDictionary routes, IDictionary ids, int id, string route) + private static void CheckRoute(FastDictionaryAppCache routes, int id, string route) { - Assert.IsTrue(routes.ContainsKey(id)); - Assert.AreEqual(route, routes[id]); - Assert.IsFalse(ids.ContainsKey(route)); + var cacheKey = $"{CacheKeyPrefix}[P:{id}]"; + var found = (string)routes.Get(cacheKey); + Assert.IsNotNull(found); + Assert.AreEqual(route, found); } [Test] public void Get_Url_Relative_Or_Absolute() { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext("http://domain1.com/test", 1111, globalSettings: globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - SetDomains4(); - Assert.AreEqual("/en/1001-1-1/", publishedUrlProvider.GetUrl(100111)); - Assert.AreEqual("http://domain3.com/en/1003-1-1/", publishedUrlProvider.GetUrl(100311)); + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + GlobalSettings.HideTopLevelNodeFromPath = false; - publishedUrlProvider.Mode = UrlMode.Absolute; + var umbracoContextAccessor = GetUmbracoContextAccessor("http://domain1.com/test"); + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); - Assert.AreEqual("http://domain1.com/en/1001-1-1/", publishedUrlProvider.GetUrl(100111)); - Assert.AreEqual("http://domain3.com/en/1003-1-1/", publishedUrlProvider.GetUrl(100311)); + + Assert.AreEqual("/en/1001-1-1/", urlProvider.GetUrl(100111)); + Assert.AreEqual("http://domain3.com/en/1003-1-1/", urlProvider.GetUrl(100311)); + + urlProvider.Mode = UrlMode.Absolute; + + Assert.AreEqual("http://domain1.com/en/1001-1-1/", urlProvider.GetUrl(100111)); + Assert.AreEqual("http://domain3.com/en/1003-1-1/", urlProvider.GetUrl(100311)); } [Test] public void Get_Url_Alternate() { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext("http://domain1.com/en/test", 1111, globalSettings: globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - SetDomains5(); - var url = publishedUrlProvider.GetUrl(100111, UrlMode.Absolute); + var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; + GlobalSettings.HideTopLevelNodeFromPath = false; + + var umbracoContextAccessor = GetUmbracoContextAccessor("http://domain1.com/en/test"); + UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility); + + + var url = urlProvider.GetUrl(100111, UrlMode.Absolute); Assert.AreEqual("http://domain1.com/en/1001-1-1/", url); - var result = publishedUrlProvider.GetOtherUrls(100111).ToArray(); + var result = urlProvider.GetOtherUrls(100111).ToArray(); - foreach (var x in result) Console.WriteLine(x); + foreach (var x in result) + Console.WriteLine(x); Assert.AreEqual(2, result.Length); Assert.AreEqual(result[0].Text, "http://domain1b.com/en/1001-1-1/"); Assert.AreEqual(result[1].Text, "http://domain1a.com/en/1001-1-1/"); } - private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, DefaultUrlProvider urlProvider) - { - var webRoutingSettings = new WebRoutingSettings(); - return new UrlProvider( - new TestUmbracoContextAccessor(umbracoContext), - Microsoft.Extensions.Options.Options.Create(webRoutingSettings), - new UrlProviderCollection(new []{urlProvider}), - new MediaUrlProviderCollection(Enumerable.Empty()), - Mock.Of() - ); - } } } diff --git a/tests/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsWithNestedDomains.cs similarity index 84% rename from tests/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsWithNestedDomains.cs index ab9058bc9c..1c96f0c0bc 100644 --- a/tests/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsWithNestedDomains.cs @@ -3,19 +3,22 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Tests.Common; using Umbraco.Extensions; -using Umbraco.Tests.LegacyXmlPublishedCache; -namespace Umbraco.Tests.Routing +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing { [TestFixture] public class UrlsWithNestedDomains : UrlRoutingTestBase @@ -25,61 +28,60 @@ namespace Umbraco.Tests.Routing // using the closest domain to the node - here we test that if we request // a non-canonical route, it is not cached / the cache is not polluted - protected override void Compose() - { - base.Compose(); - Builder.Services.AddUnique(Mock.Of()); - Builder.Services.AddTransient(); - } - [Test] public async Task DoNotPolluteCache() { var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; + GlobalSettings.HideTopLevelNodeFromPath = false; SetDomains1(); const string url = "http://domain1.com/1001-1/1001-1-1"; // get the nice URL for 100111 - var umbracoContext = GetUmbracoContext(url, 9999, globalSettings: globalSettings); - var umbracoContextAccessor = GetUmbracoContextAccessor(umbracoContext); + var umbracoContextAccessor = GetUmbracoContextAccessor(url); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); + Mock.Of>(x=>x.CurrentValue == requestHandlerSettings), + Mock.Of>(), + new SiteDomainMapper(), + umbracoContextAccessor, + new UriUtility(Mock.Of()), + Mock.Of()); var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - Assert.AreEqual("http://domain2.com/1001-1-1/", publishedUrlProvider.GetUrl(100111, UrlMode.Absolute)); + string absUrl = publishedUrlProvider.GetUrl(100111, UrlMode.Absolute); + Assert.AreEqual("http://domain2.com/1001-1-1/", absUrl); + + const string cacheKeyPrefix = "NuCache.ContentCache.RouteByContent"; // check that the proper route has been cached - var cache = umbracoContext.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); - var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); - Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); + var cache = (FastDictionaryAppCache)umbracoContext.PublishedSnapshot.ElementsCache; + + var cachedRoutes = cache.Keys.Where(x => x.StartsWith(cacheKeyPrefix)).ToList(); + var cacheKey = $"{cacheKeyPrefix}[P:100111]"; + Assert.AreEqual("10011/1001-1-1", cache.Get(cacheKey)); // route a rogue URL var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); publishedRouter.FindDomain(frequest); Assert.IsTrue(frequest.HasDomain()); // check that it's been routed - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); + var lookup = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); Assert.AreEqual(100111, frequest.PublishedContent.Id); // has the cache been polluted? - cachedRoutes = cache.RoutesCache.GetCachedRoutes(); - Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); // no - //Assert.AreEqual("1001/1001-1/1001-1-1", cachedRoutes[100111]); // yes + cachedRoutes = cache.Keys.Where(x => x.StartsWith(cacheKeyPrefix)).ToList(); + Assert.AreEqual("10011/1001-1-1", cache.Get(cacheKey)); // no // what's the nice URL now? Assert.AreEqual("http://domain2.com/1001-1-1/", publishedUrlProvider.GetUrl(100111)); // good - //Assert.AreEqual("http://domain1.com/1001-1/1001-1-1", routingContext.NiceUrlProvider.GetNiceUrl(100111, true)); // bad } private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, object urlProvider) @@ -87,14 +89,16 @@ namespace Umbraco.Tests.Routing throw new NotImplementedException(); } - void SetDomains1() + private void SetDomains1() { - SetupDomainServiceMock(new[] - { - new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"}, - new UmbracoDomain("http://domain2.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"} - }); + var domainService = Mock.Get(DomainService); + domainService.Setup(service => service.GetAll(It.IsAny())) + .Returns((bool incWildcards) => new[] + { + new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"}, + new UmbracoDomain("http://domain2.com/") {Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"} + }); } private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, DefaultUrlProvider urlProvider) @@ -103,15 +107,14 @@ namespace Umbraco.Tests.Routing return new UrlProvider( new TestUmbracoContextAccessor(umbracoContext), Microsoft.Extensions.Options.Options.Create(webRoutingSettings), - new UrlProviderCollection(new []{urlProvider}), - new MediaUrlProviderCollection(Enumerable.Empty()), + new UrlProviderCollection(() => new[] { urlProvider }), + new MediaUrlProviderCollection(() => Enumerable.Empty()), Mock.Of() ); } protected override string GetXmlContent(int templateId) - { - return @" + => @" @@ -190,6 +193,5 @@ namespace Umbraco.Tests.Routing "; - } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scheduling/ContentVersionCleanupTest.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scheduling/ContentVersionCleanupTest.cs new file mode 100644 index 0000000000..430cd184c1 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scheduling/ContentVersionCleanupTest.cs @@ -0,0 +1,147 @@ +using System; +using System.Threading.Tasks; +using AutoFixture.NUnit3; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Tests.UnitTests.AutoFixture; + +namespace Umbraco.Tests.Scheduling +{ + [TestFixture] + class ContentVersionCleanupTest + { + [Test, AutoMoqData] + public async Task ContentVersionCleanup_WhenNotEnabled_DoesNotCleanupWillRepeat( + [Frozen] Mock> settings, + [Frozen] Mock mainDom, + [Frozen] Mock serverRoleAccessor, + [Frozen] Mock runtimeState, + [Frozen] Mock cleanupService, + ContentVersionCleanup sut) + { + settings.Setup(x => x.CurrentValue).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new ContentVersionCleanupPolicySettings() + { + EnableCleanup = false + } + }); + runtimeState.Setup(x => x.Level).Returns(RuntimeLevel.Run); + mainDom.Setup(x => x.IsMainDom).Returns(true); + serverRoleAccessor.Setup(x => x.CurrentServerRole).Returns(ServerRole.SchedulingPublisher); + + await sut.PerformExecuteAsync(null); + + cleanupService.Verify(x => x.PerformContentVersionCleanup(It.IsAny()), Times.Never); + } + + [Test, AutoMoqData] + public async Task ContentVersionCleanup_RuntimeLevelNotRun_DoesNotCleanupWillRepeat( + [Frozen] Mock> settings, + [Frozen] Mock mainDom, + [Frozen] Mock serverRoleAccessor, + [Frozen] Mock runtimeState, + [Frozen] Mock cleanupService, + ContentVersionCleanup sut) + { + settings.Setup(x => x.CurrentValue).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new ContentVersionCleanupPolicySettings() + { + EnableCleanup = true + } + }); + runtimeState.Setup(x => x.Level).Returns(RuntimeLevel.Unknown); + mainDom.Setup(x => x.IsMainDom).Returns(true); + serverRoleAccessor.Setup(x => x.CurrentServerRole).Returns(ServerRole.SchedulingPublisher); + + await sut.PerformExecuteAsync(null); + + cleanupService.Verify(x => x.PerformContentVersionCleanup(It.IsAny()), Times.Never); + } + + [Test, AutoMoqData] + public async Task ContentVersionCleanup_ServerRoleUnknown_DoesNotCleanupWillRepeat( + [Frozen] Mock> settings, + [Frozen] Mock mainDom, + [Frozen] Mock serverRoleAccessor, + [Frozen] Mock runtimeState, + [Frozen] Mock cleanupService, + ContentVersionCleanup sut) + { + settings.Setup(x => x.CurrentValue).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new ContentVersionCleanupPolicySettings() + { + EnableCleanup = true + } + }); + runtimeState.Setup(x => x.Level).Returns(RuntimeLevel.Run); + mainDom.Setup(x => x.IsMainDom).Returns(true); + serverRoleAccessor.Setup(x => x.CurrentServerRole).Returns(ServerRole.Unknown); + + await sut.PerformExecuteAsync(null); + + cleanupService.Verify(x => x.PerformContentVersionCleanup(It.IsAny()), Times.Never); + } + + [Test, AutoMoqData] + public async Task ContentVersionCleanup_NotMainDom_DoesNotCleanupWillNotRepeat( + [Frozen] Mock> settings, + [Frozen] Mock mainDom, + [Frozen] Mock serverRoleAccessor, + [Frozen] Mock runtimeState, + [Frozen] Mock cleanupService, + ContentVersionCleanup sut) + { + settings.Setup(x => x.CurrentValue).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new ContentVersionCleanupPolicySettings() + { + EnableCleanup = true + } + }); + + runtimeState.Setup(x => x.Level).Returns(RuntimeLevel.Run); + mainDom.Setup(x => x.IsMainDom).Returns(false); + serverRoleAccessor.Setup(x => x.CurrentServerRole).Returns(ServerRole.SchedulingPublisher); + + await sut.PerformExecuteAsync(null); + + cleanupService.Verify(x => x.PerformContentVersionCleanup(It.IsAny()), Times.Never); + } + + [Test, AutoMoqData] + public async Task ContentVersionCleanup_Enabled_DelegatesToCleanupService( + [Frozen] Mock> settings, + [Frozen] Mock mainDom, + [Frozen] Mock serverRoleAccessor, + [Frozen] Mock runtimeState, + [Frozen] Mock cleanupService, + ContentVersionCleanup sut) + { + settings.Setup(x => x.CurrentValue).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new ContentVersionCleanupPolicySettings() + { + EnableCleanup = true + } + }); + + runtimeState.Setup(x => x.Level).Returns(RuntimeLevel.Run); + mainDom.Setup(x => x.IsMainDom).Returns(true); + serverRoleAccessor.Setup(x => x.CurrentServerRole).Returns(ServerRole.SchedulingPublisher); + + await sut.PerformExecuteAsync(null); + + cleanupService.Verify(x => x.PerformContentVersionCleanup(It.IsAny()), Times.Once); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/ContentVersionCleanupServiceTest.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/ContentVersionCleanupServiceTest.cs new file mode 100644 index 0000000000..11ff6db989 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/ContentVersionCleanupServiceTest.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using AutoFixture.NUnit3; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Tests.UnitTests.AutoFixture; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + internal class ContentVersionCleanupServiceTest + { + [Test] + [AutoMoqData] + public void PerformContentVersionCleanup_Always_RespectsDeleteRevisionsCancellation( + [Frozen] Mock eventAggregator, + [Frozen] Mock policy, + [Frozen] Mock documentVersionRepository, + List someHistoricVersions, + DateTime aDateTime, + ContentVersionService sut) + { + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(someHistoricVersions); + + eventAggregator.Setup(x => x.PublishCancelable(It.IsAny())) + .Returns(true); + + policy.Setup(x => x.Apply(aDateTime, someHistoricVersions)) + .Returns(someHistoricVersions); + + // # Act + IReadOnlyCollection report = sut.PerformContentVersionCleanup(aDateTime); + + Assert.Multiple(() => + { + eventAggregator.Verify(x => x.PublishCancelable(It.IsAny()), Times.Exactly(someHistoricVersions.Count)); + Assert.AreEqual(0, report.Count); + }); + } + + [Test] + [AutoMoqData] + public void PerformContentVersionCleanup_Always_FiresDeletedVersionsForEachDeletedVersion( + [Frozen] Mock eventAggregator, + [Frozen] Mock policy, + [Frozen] Mock documentVersionRepository, + List someHistoricVersions, + DateTime aDateTime, + ContentVersionService sut) + { + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(someHistoricVersions); + + eventAggregator + .Setup(x => x.PublishCancelable(It.IsAny())) + .Returns(false); + + policy.Setup(x => x.Apply(aDateTime, someHistoricVersions)) + .Returns(someHistoricVersions); + + // # Act + sut.PerformContentVersionCleanup(aDateTime); + + eventAggregator.Verify(x => x.Publish(It.IsAny()), Times.Exactly(someHistoricVersions.Count)); + } + + [Test, AutoMoqData] + public void PerformContentVersionCleanup_Always_ReturnsReportOfDeletedItems( + [Frozen] Mock eventAggregator, + [Frozen] Mock policy, + [Frozen] Mock documentVersionRepository, + List someHistoricVersions, + DateTime aDateTime, + ContentVersionService sut) + { + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(someHistoricVersions); + + eventAggregator + .Setup(x => x.PublishCancelable(It.IsAny())) + .Returns(false); + + // # Act + var report = sut.PerformContentVersionCleanup(aDateTime); + + Assert.Multiple(() => + { + Assert.Greater(report.Count, 0); + Assert.AreEqual(someHistoricVersions.Count, report.Count); + }); + } + + [Test, AutoMoqData] + public void PerformContentVersionCleanup_Always_AdheresToCleanupPolicy( + [Frozen] Mock eventAggregator, + [Frozen] Mock policy, + [Frozen] Mock documentVersionRepository, + List someHistoricVersions, + DateTime aDateTime, + ContentVersionService sut) + { + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(someHistoricVersions); + + eventAggregator + .Setup(x => x.PublishCancelable(It.IsAny())) + .Returns(false); + + policy.Setup(x => x.Apply(It.IsAny(), It.IsAny>())) + .Returns>((_, items) => items.Take(1)); + + // # Act + var report = sut.PerformContentVersionCleanup(aDateTime); + + Debug.Assert(someHistoricVersions.Count > 1); + + Assert.Multiple(() => + { + policy.Verify(x => x.Apply(aDateTime, someHistoricVersions), Times.Once); + Assert.AreEqual(someHistoricVersions.First(), report.Single()); + }); + } + + /// + /// For v9 this just needs a rewrite, no static events, no service location etc + /// + [Test, AutoMoqData] + public void PerformContentVersionCleanup_HasVersionsToDelete_CallsDeleteOnRepositoryWithFilteredSet( + [Frozen] Mock eventAggregator, + [Frozen] Mock policy, + [Frozen] Mock documentVersionRepository, + List someHistoricVersions, + DateTime aDateTime, + ContentVersionService sut) + { + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(someHistoricVersions); + + eventAggregator + .Setup(x => x.PublishCancelable(It.IsAny())) + .Returns(false); + + var filteredSet = someHistoricVersions.Take(1); + + policy.Setup(x => x.Apply(It.IsAny(), It.IsAny>())) + .Returns>((_, items) => filteredSet); + + // # Act + var report = sut.PerformContentVersionCleanup(aDateTime); + + Debug.Assert(someHistoricVersions.Any()); + + var expectedId = filteredSet.First().VersionId; + + documentVersionRepository.Verify(x => x.DeleteVersions(It.Is>(y => y.Single() == expectedId)), Times.Once); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DefaultContentVersionCleanupPolicyTest.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DefaultContentVersionCleanupPolicyTest.cs new file mode 100644 index 0000000000..801c27110d --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DefaultContentVersionCleanupPolicyTest.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoFixture.NUnit3; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Infrastructure.Services.Implement; +using Umbraco.Cms.Tests.UnitTests.AutoFixture; +using ContentVersionCleanupPolicySettings = Umbraco.Cms.Core.Models.ContentVersionCleanupPolicySettings; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + public class DefaultContentVersionCleanupPolicyTest + { + [Test, AutoMoqData] + public void Apply_AllOlderThanKeepSettings_AllVersionsReturned( + [Frozen] Mock documentVersionRepository, + [Frozen] Mock> contentSettings, + DefaultContentVersionCleanupPolicy sut) + { + var versionId = 0; + + var historicItems = new List + { + new ContentVersionMeta(versionId: ++versionId, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + new ContentVersionMeta(versionId: ++versionId, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + }; + + contentSettings.Setup(x => x.Value).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new Cms.Core.Configuration.Models.ContentVersionCleanupPolicySettings() + { + EnableCleanup = true, + KeepAllVersionsNewerThanDays = 0, + KeepLatestVersionPerDayForDays = 0 + } + }); + + documentVersionRepository.Setup(x => x.GetCleanupPolicies()) + .Returns(Array.Empty()); + + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(historicItems); + + var results = sut.Apply(DateTime.Today, historicItems).ToList(); + + Assert.AreEqual(2, results.Count); + } + + [Test, AutoMoqData] + public void Apply_OverlappingKeepSettings_KeepAllVersionsNewerThanDaysTakesPriority( + [Frozen] Mock documentVersionRepository, + [Frozen] Mock> contentSettings, + DefaultContentVersionCleanupPolicy sut) + { + var versionId = 0; + + var historicItems = new List + { + new ContentVersionMeta(versionId: ++versionId, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + new ContentVersionMeta(versionId: ++versionId, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + }; + + contentSettings.Setup(x => x.Value).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new Cms.Core.Configuration.Models.ContentVersionCleanupPolicySettings() + { + EnableCleanup = true, + KeepAllVersionsNewerThanDays = 2, + KeepLatestVersionPerDayForDays = 2 + } + }); + + documentVersionRepository.Setup(x => x.GetCleanupPolicies()) + .Returns(Array.Empty()); + + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(historicItems); + + var results = sut.Apply(DateTime.Today, historicItems).ToList(); + + Assert.AreEqual(0, results.Count); + } + + [Test, AutoMoqData] + public void Apply_WithinInKeepLatestPerDay_ReturnsSinglePerContentPerDay( + [Frozen] Mock documentVersionRepository, + [Frozen] Mock> contentSettings, + DefaultContentVersionCleanupPolicy sut) + { + var historicItems = new List + { + new ContentVersionMeta(versionId: 1, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 2, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 3, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + + new ContentVersionMeta(versionId: 4, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddDays(-1).AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 5, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddDays(-1).AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 6, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddDays(-1).AddHours(-1), false, false, false, null), + // another content + new ContentVersionMeta(versionId: 7, contentId: 2, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 8, contentId: 2, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 9, contentId: 2, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + }; + + + contentSettings.Setup(x => x.Value).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new Cms.Core.Configuration.Models.ContentVersionCleanupPolicySettings() + { + EnableCleanup = true, + KeepAllVersionsNewerThanDays = 0, + KeepLatestVersionPerDayForDays = 3 + } + }); + + documentVersionRepository.Setup(x => x.GetCleanupPolicies()) + .Returns(Array.Empty()); + + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(historicItems); + + var results = sut.Apply(DateTime.Today, historicItems).ToList(); + + // Keep latest per day for 3 days per content type + // 2 content types, one of which has 2 days of entries, the other only a single day + Assert.Multiple(() => + { + Assert.AreEqual(6, results.Count); + Assert.AreEqual(4, results.Count(x => x.ContentTypeId == 1)); + Assert.AreEqual(2, results.Count(x => x.ContentTypeId == 2)); + Assert.False(results.Any(x => x.VersionId == 9)); // Most recent for content type 2 + Assert.False(results.Any(x => x.VersionId == 3)); // Most recent for content type 1 today + Assert.False(results.Any(x => x.VersionId == 6)); // Most recent for content type 1 yesterday + }); + } + + [Test, AutoMoqData] + public void Apply_HasOverridePolicy_RespectsPreventCleanup( + [Frozen] Mock documentVersionRepository, + [Frozen] Mock> contentSettings, + DefaultContentVersionCleanupPolicy sut) + { + var historicItems = new List + { + new ContentVersionMeta(versionId: 1, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 2, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 3, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + // another content & type + new ContentVersionMeta(versionId: 4, contentId: 2, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 5, contentId: 2, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 6, contentId: 2, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + }; + + contentSettings.Setup(x => x.Value).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new Cms.Core.Configuration.Models.ContentVersionCleanupPolicySettings() + { + EnableCleanup = true, + KeepAllVersionsNewerThanDays = 0, + KeepLatestVersionPerDayForDays = 0 + } + }); + + documentVersionRepository.Setup(x => x.GetCleanupPolicies()) + .Returns(new ContentVersionCleanupPolicySettings[] + { + new ContentVersionCleanupPolicySettings{ ContentTypeId = 2, PreventCleanup = true } + }); + + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(historicItems); + + var results = sut.Apply(DateTime.Today, historicItems).ToList(); + + Assert.True(results.All(x => x.ContentTypeId == 1)); + } + + [Test, AutoMoqData] + public void Apply_HasOverridePolicy_RespectsKeepAll( + [Frozen] Mock documentVersionRepository, + [Frozen] Mock> contentSettings, + DefaultContentVersionCleanupPolicy sut) + { + var historicItems = new List + { + new ContentVersionMeta(versionId: 1, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 2, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 3, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + // another content & type + new ContentVersionMeta(versionId: 4, contentId: 2, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 5, contentId: 2, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 6, contentId: 2, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + }; + + contentSettings.Setup(x => x.Value).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new Cms.Core.Configuration.Models.ContentVersionCleanupPolicySettings() + { + EnableCleanup = true, + KeepAllVersionsNewerThanDays = 0, + KeepLatestVersionPerDayForDays = 0 + } + }); + + documentVersionRepository.Setup(x => x.GetCleanupPolicies()) + .Returns(new ContentVersionCleanupPolicySettings[] + { + new ContentVersionCleanupPolicySettings{ ContentTypeId = 2, PreventCleanup = false, KeepAllVersionsNewerThanDays = 3 } + }); + + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(historicItems); + + var results = sut.Apply(DateTime.Today, historicItems).ToList(); + + Assert.True(results.All(x => x.ContentTypeId == 1)); + } + + [Test, AutoMoqData] + public void Apply_HasOverridePolicy_RespectsKeepLatest( + [Frozen] Mock documentVersionRepository, + [Frozen] Mock> contentSettings, + DefaultContentVersionCleanupPolicy sut) + { + var historicItems = new List + { + new ContentVersionMeta(versionId: 1, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 2, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 3, contentId: 1, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + // another content + new ContentVersionMeta(versionId: 4, contentId: 2, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 5, contentId: 2, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 6, contentId: 2, contentTypeId: 1, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + // another content & type + new ContentVersionMeta(versionId: 7, contentId: 3, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-3), false, false, false, null), + new ContentVersionMeta(versionId: 8, contentId: 3, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-2), false, false, false, null), + new ContentVersionMeta(versionId: 9, contentId: 3, contentTypeId: 2, -1, versionDate: DateTime.Today.AddHours(-1), false, false, false, null), + }; + + contentSettings.Setup(x => x.Value).Returns(new ContentSettings() + { + ContentVersionCleanupPolicy = new Cms.Core.Configuration.Models.ContentVersionCleanupPolicySettings() + { + EnableCleanup = true, + KeepAllVersionsNewerThanDays = 0, + KeepLatestVersionPerDayForDays = 0 + } + }); + + documentVersionRepository.Setup(x => x.GetCleanupPolicies()) + .Returns(new ContentVersionCleanupPolicySettings[] + { + new ContentVersionCleanupPolicySettings{ ContentTypeId = 2, PreventCleanup = false, KeepLatestVersionPerDayForDays = 3 } + }); + + documentVersionRepository.Setup(x => x.GetDocumentVersionsEligibleForCleanup()) + .Returns(historicItems); + + var results = sut.Apply(DateTime.Today, historicItems).ToList(); + + // By default no historic versions are kept + // Override policy for content type 2 keeps latest per day for 3 days, no versions retained for content type with id 1 + // There were 3 entries for content type 2 all on the same day + // version id 9 is most recent for content type 2, and should be filtered, all the rest should be present. + Assert.Multiple(() => + { + Assert.AreEqual(8, results.Count); + Assert.AreEqual(2, results.Count(x => x.ContentTypeId == 2)); + Assert.False(results.Any(x => x.VersionId == 9)); + }); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/DefaultViewContentProviderTests.cs similarity index 71% rename from tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/DefaultViewContentProviderTests.cs index 446659467b..d43d88f001 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/DefaultViewContentProviderTests.cs @@ -7,12 +7,14 @@ using Umbraco.Cms.Core.IO; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Templates { [TestFixture] - public class ViewHelperTests + public class DefaultViewContentProviderTests { + private IDefaultViewContentProvider DefaultViewContentProvider => new DefaultViewContentProvider(); + [Test] public void NoOptions() { - var view = ViewHelper.GetDefaultFileContent(); + var view = DefaultViewContentProvider.GetDefaultFileContent(); Assert.AreEqual( FixView(@"@using Umbraco.Cms.Web.Common.PublishedModels; @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @@ -24,7 +26,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Templates [Test] public void Layout() { - var view = ViewHelper.GetDefaultFileContent(layoutPageAlias: "Dharznoik"); + var view = DefaultViewContentProvider.GetDefaultFileContent(layoutPageAlias: "Dharznoik"); Assert.AreEqual( FixView(@"@using Umbraco.Cms.Web.Common.PublishedModels; @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @@ -36,7 +38,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Templates [Test] public void ClassName() { - var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName"); + var view = DefaultViewContentProvider.GetDefaultFileContent(modelClassName: "ClassName"); Assert.AreEqual( FixView(@"@using Umbraco.Cms.Web.Common.PublishedModels; @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @@ -48,7 +50,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Templates [Test] public void Namespace() { - var view = ViewHelper.GetDefaultFileContent(modelNamespace: "Models"); + var view = DefaultViewContentProvider.GetDefaultFileContent(modelNamespace: "Models"); Assert.AreEqual( FixView(@"@using Umbraco.Cms.Web.Common.PublishedModels; @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @@ -60,7 +62,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Templates [Test] public void ClassNameAndNamespace() { - var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models"); + var view = DefaultViewContentProvider.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models"); Assert.AreEqual( FixView(@"@using Umbraco.Cms.Web.Common.PublishedModels; @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @@ -73,7 +75,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Templates [Test] public void ClassNameAndNamespaceAndAlias() { - var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); + var view = DefaultViewContentProvider.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); Assert.AreEqual( FixView(@"@using Umbraco.Cms.Web.Common.PublishedModels; @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @@ -86,7 +88,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Templates [Test] public void Combined() { - var view = ViewHelper.GetDefaultFileContent(layoutPageAlias: "Dharznoik", modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); + var view = DefaultViewContentProvider.GetDefaultFileContent(layoutPageAlias: "Dharznoik", modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); Assert.AreEqual( FixView(@"@using Umbraco.Cms.Web.Common.PublishedModels; @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage diff --git a/tests/Umbraco.Tests/PublishedContent/ContentSerializationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/ContentSerializationTests.cs similarity index 84% rename from tests/Umbraco.Tests/PublishedContent/ContentSerializationTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/ContentSerializationTests.cs index 9a44cf35f9..46824d6386 100644 --- a/tests/Umbraco.Tests/PublishedContent/ContentSerializationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/ContentSerializationTests.cs @@ -1,12 +1,12 @@ -using Moq; +using Moq; using NUnit.Framework; using System; using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.PublishedCache.NuCache.DataSource; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; -namespace Umbraco.Tests.PublishedContent +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache { [TestFixture] public class ContentSerializationTests @@ -77,9 +77,12 @@ namespace Umbraco.Tests.PublishedContent { public override int Compare(CultureVariation x, CultureVariation y) { - if (x == null && y == null) return 0; - if (x == null && y != null) return -1; - if (x != null && y == null) return 1; + if (x == null && y == null) + return 0; + if (x == null && y != null) + return -1; + if (x != null && y == null) + return 1; return x.Date.CompareTo(y.Date) | x.IsDraft.CompareTo(y.IsDraft) | x.Name.CompareTo(y.Name) | x.UrlSegment.CompareTo(y.UrlSegment); } @@ -89,9 +92,12 @@ namespace Umbraco.Tests.PublishedContent { public override int Compare(PropertyData x, PropertyData y) { - if (x == null && y == null) return 0; - if (x == null && y != null) return -1; - if (x != null && y == null) return 1; + if (x == null && y == null) + return 0; + if (x == null && y != null) + return -1; + if (x != null && y == null) + return 1; var xVal = x.Value?.ToString() ?? string.Empty; var yVal = y.Value?.ToString() ?? string.Empty; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentCacheTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentCacheTests.cs new file mode 100644 index 0000000000..0ef8a856fd --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentCacheTests.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache +{ + [TestFixture] + public class PublishContentCacheTests : PublishedSnapshotServiceTestBase + { + private IPublishedContentCache _cache; + + [SetUp] + public override void Setup() + { + base.Setup(); + + string xml = PublishedContentXml.PublishContentCacheTestsXml(); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + // configure the Home content type to be composed of another for tests. + var compositionType = new ContentType(TestHelper.ShortStringHelper, -1) + { + Alias = "MyCompositionAlias" + }; + contentTypes.First(x => x.Alias == "Home").AddContentType(compositionType); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + _cache = GetPublishedSnapshot().Content; + } + + [Test] + public void Has_Content() + { + Assert.IsTrue(_cache.HasContent()); + } + + + [Test] + public void Get_Root_Docs() + { + var result = _cache.GetAtRoot(); + Assert.AreEqual(2, result.Count()); + Assert.AreEqual(1046, result.ElementAt(0).Id); + Assert.AreEqual(1172, result.ElementAt(1).Id); + } + + + [TestCase("/", 1046)] + [TestCase("/home", 1046)] + [TestCase("/Home", 1046)] //test different cases + [TestCase("/home/sub1", 1173)] + [TestCase("/Home/sub1", 1173)] + [TestCase("/home/Sub1", 1173)] //test different cases + [TestCase("/home/Sub'Apostrophe", 1177)] + public void Get_Node_By_Route(string route, int nodeId) + { + var result = _cache.GetByRoute(route, false); + Assert.IsNotNull(result); + Assert.AreEqual(nodeId, result.Id); + } + + + + [TestCase("/", 1046)] + [TestCase("/sub1", 1173)] + [TestCase("/Sub1", 1173)] + public void Get_Node_By_Route_Hiding_Top_Level_Nodes(string route, int nodeId) + { + var result = _cache.GetByRoute(route, true); + Assert.IsNotNull(result); + Assert.AreEqual(nodeId, result.Id); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentDataTableTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentDataTableTests.cs new file mode 100644 index 0000000000..80920e071f --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentDataTableTests.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache +{ + /// + /// Unit tests for IPublishedContent and extensions + /// + [TestFixture] + public class PublishedContentDataTableTests : PublishedSnapshotServiceTestBase + { + private readonly DataType[] _dataTypes = GetDefaultDataTypes(); + + private static ContentType CreateContentType(string name, IDataType dataType, IReadOnlyDictionary propertyAliasesAndNames) + { + var contentType = new ContentType(TestHelper.ShortStringHelper, -1) + { + Alias = name, + Name = name, + Key = Guid.NewGuid(), + Id = name.GetHashCode() + }; + foreach(var prop in propertyAliasesAndNames) + { + contentType.AddPropertyType(new PropertyType(TestHelper.ShortStringHelper, dataType, prop.Key) + { + Name = prop.Value + }); + } + + return contentType; + } + + private IEnumerable CreateCache( + bool createChildren, + IDataType dataType, + out ContentType[] contentTypes) + { + var result = new List(); + var valueCounter = 1; + var parentId = 3; + + var properties = new Dictionary + { + ["property1"] = "Property 1", + ["property2"] = "Property 2", + }; + + ContentType parentContentType = CreateContentType("Parent", dataType, new Dictionary(properties) + { + ["property3"] = "Property 3" + }); + ContentType childContentType = CreateContentType("Child", dataType, new Dictionary(properties) + { + ["property4"] = "Property 4" + }); + ContentType child2ContentType = CreateContentType("Child2", dataType, new Dictionary(properties) + { + ["property4"] = "Property 4" + }); + + contentTypes = new[] { parentContentType, childContentType, child2ContentType }; + + ContentData parentData = new ContentDataBuilder() + .WithName("Page" + Guid.NewGuid()) + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("property1", "value" + valueCounter) + .WithPropertyData("property2", "value" + (valueCounter + 1)) + .WithPropertyData("property3", "value" + (valueCounter + 2)) + .Build()) + .Build(); + + ContentNodeKit parent = ContentNodeKitBuilder.CreateWithContent( + parentContentType.Id, + parentId, $"-1,{parentId}", + draftData: parentData, + publishedData: parentData); + + result.Add(parent); + + if (createChildren) + { + for (int i = 0; i < 3; i++) + { + valueCounter += 3; + var childId = parentId + i + 1; + + ContentData childData = new ContentDataBuilder() + .WithName("Page" + Guid.NewGuid()) + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("property1", "value" + valueCounter) + .WithPropertyData("property2", "value" + (valueCounter + 1)) + .WithPropertyData("property4", "value" + (valueCounter + 2)) + .Build()) + .Build(); + + ContentNodeKit child = ContentNodeKitBuilder.CreateWithContent( + i > 0 ? childContentType.Id : child2ContentType.Id, + childId, $"-1,{parentId},{childId}", i, + draftData: childData, + publishedData: childData); + + result.Add(child); + } + } + + return result; + } + + [Test] + public void To_DataTable() + { + var cache = CreateCache(true, _dataTypes[0], out ContentType[] contentTypes); + InitializedCache(cache, contentTypes, dataTypes: _dataTypes); + + var snapshot = GetPublishedSnapshot(); + var root = snapshot.Content.GetAtRoot().First(); + + var dt = root.ChildrenAsTable( + VariationContextAccessor, + ContentTypeService, + MediaTypeService, + Mock.Of(), + Mock.Of()); + + Assert.AreEqual(11, dt.Columns.Count); + Assert.AreEqual(3, dt.Rows.Count); + Assert.AreEqual("value4", dt.Rows[0]["Property 1"]); + Assert.AreEqual("value5", dt.Rows[0]["Property 2"]); + Assert.AreEqual("value6", dt.Rows[0]["Property 4"]); + Assert.AreEqual("value7", dt.Rows[1]["Property 1"]); + Assert.AreEqual("value8", dt.Rows[1]["Property 2"]); + Assert.AreEqual("value9", dt.Rows[1]["Property 4"]); + Assert.AreEqual("value10", dt.Rows[2]["Property 1"]); + Assert.AreEqual("value11", dt.Rows[2]["Property 2"]); + Assert.AreEqual("value12", dt.Rows[2]["Property 4"]); + } + + [Test] + public void To_DataTable_With_Filter() + { + var cache = CreateCache(true, _dataTypes[0], out ContentType[] contentTypes); + InitializedCache(cache, contentTypes, dataTypes: _dataTypes); + + var snapshot = GetPublishedSnapshot(); + var root = snapshot.Content.GetAtRoot().First(); + + var dt = root.ChildrenAsTable( + VariationContextAccessor, + ContentTypeService, + MediaTypeService, + Mock.Of(), + Mock.Of(), + "Child"); + + Assert.AreEqual(11, dt.Columns.Count); + Assert.AreEqual(2, dt.Rows.Count); + Assert.AreEqual("value7", dt.Rows[0]["Property 1"]); + Assert.AreEqual("value8", dt.Rows[0]["Property 2"]); + Assert.AreEqual("value9", dt.Rows[0]["Property 4"]); + Assert.AreEqual("value10", dt.Rows[1]["Property 1"]); + Assert.AreEqual("value11", dt.Rows[1]["Property 2"]); + Assert.AreEqual("value12", dt.Rows[1]["Property 4"]); + } + + [Test] + public void To_DataTable_No_Rows() + { + var cache = CreateCache(false, _dataTypes[0], out ContentType[] contentTypes); + InitializedCache(cache, contentTypes, dataTypes: _dataTypes); + + var snapshot = GetPublishedSnapshot(); + var root = snapshot.Content.GetAtRoot().First(); + + var dt = root.ChildrenAsTable( + VariationContextAccessor, + ContentTypeService, + MediaTypeService, + Mock.Of(), + Mock.Of()); + + //will return an empty data table + Assert.AreEqual(0, dt.Columns.Count); + Assert.AreEqual(0, dt.Rows.Count); + } + + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentExtensionTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentExtensionTests.cs new file mode 100644 index 0000000000..c3af93f911 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentExtensionTests.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache +{ + [TestFixture] + public class PublishedContentExtensionTests : PublishedSnapshotServiceTestBase + { + private const string XmlContent = @" + + +]> + + +"; + + [SetUp] + public override void Setup() + { + base.Setup(); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + XmlContent, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + // configure inheritance for content types + var baseType = new ContentType(TestHelper.ShortStringHelper, -1) { Alias = "Base" }; + contentTypes[0].AddContentType(baseType); + + InitializedCache(kits, contentTypes, dataTypes); + } + + [Test] + public void IsDocumentType_NonRecursive_ActualType_ReturnsTrue() + { + var publishedContent = GetContent(1100); + Assert.That(publishedContent.IsDocumentType("Inherited", false)); + } + + [Test] + public void IsDocumentType_NonRecursive_BaseType_ReturnsFalse() + { + var publishedContent = GetContent(1100); + Assert.That(publishedContent.IsDocumentType("Base", false), Is.False); + } + + [Test] + public void IsDocumentType_Recursive_ActualType_ReturnsTrue() + { + var publishedContent = GetContent(1100); + Assert.That(publishedContent.IsDocumentType("Inherited", true)); + } + + [Test] + public void IsDocumentType_Recursive_BaseType_ReturnsTrue() + { + var publishedContent = GetContent(1100); + Assert.That(publishedContent.IsDocumentType("Base", true)); + } + + [Test] + public void IsDocumentType_Recursive_InvalidBaseType_ReturnsFalse() + { + var publishedContent = GetContent(1100); + Assert.That(publishedContent.IsDocumentType("invalidbase", true), Is.False); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentLanguageVariantTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentLanguageVariantTests.cs new file mode 100644 index 0000000000..3098dc6752 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentLanguageVariantTests.cs @@ -0,0 +1,346 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache +{ + [TestFixture] + public class PublishedContentLanguageVariantTests : PublishedSnapshotServiceTestBase + { + [SetUp] + public override void Setup() + { + base.Setup(); + + var dataTypes = GetDefaultDataTypes(); + var cache = CreateCache(dataTypes, out ContentType[] contentTypes); + + InitializedCache(cache, contentTypes, dataTypes: dataTypes); + } + + protected override PropertyValueConverterCollection PropertyValueConverterCollection + { + get + { + PropertyValueConverterCollection collection = base.PropertyValueConverterCollection; + return new PropertyValueConverterCollection(() => collection.Append(new TestNoValueValueConverter())); + } + } + + private class TestNoValueValueConverter : SimpleTinyMceValueConverter + { + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.Alias == "noprop"; + + // for this test, we return false for IsValue for this property + public override bool? IsValue(object value, PropertyValueLevel level) => false; + } + + /// + /// Override to mock localization service + /// + /// + /// + /// + protected override ServiceContext CreateServiceContext(IContentType[] contentTypes, IMediaType[] mediaTypes, IDataType[] dataTypes) + { + var serviceContext = base.CreateServiceContext(contentTypes, mediaTypes, dataTypes); + + var localizationService = Mock.Get(serviceContext.LocalizationService); + + var languages = new List + { + new Language(GlobalSettings, "en-US") { Id = 1, CultureName = "English", IsDefault = true }, + new Language(GlobalSettings, "fr") { Id = 2, CultureName = "French" }, + new Language(GlobalSettings, "es") { Id = 3, CultureName = "Spanish", FallbackLanguageId = 1 }, + new Language(GlobalSettings, "it") { Id = 4, CultureName = "Italian", FallbackLanguageId = 3 }, + new Language(GlobalSettings, "de") { Id = 5, CultureName = "German" }, + new Language(GlobalSettings, "da") { Id = 6, CultureName = "Danish", FallbackLanguageId = 8 }, + new Language(GlobalSettings, "sv") { Id = 7, CultureName = "Swedish", FallbackLanguageId = 6 }, + new Language(GlobalSettings, "no") { Id = 8, CultureName = "Norweigan", FallbackLanguageId = 7 }, + new Language(GlobalSettings, "nl") { Id = 9, CultureName = "Dutch", FallbackLanguageId = 1 } + }; + + localizationService.Setup(x => x.GetAllLanguages()).Returns(languages); + localizationService.Setup(x => x.GetLanguageById(It.IsAny())) + .Returns((int id) => languages.SingleOrDefault(y => y.Id == id)); + localizationService.Setup(x => x.GetLanguageByIsoCode(It.IsAny())) + .Returns((string c) => languages.SingleOrDefault(y => y.IsoCode == c)); + + return serviceContext; + } + + /// + /// Creates a content cache + /// + /// + /// + /// + /// + /// Builds a content hierarchy of 3 nodes, each has a different set of cultural properties. + /// The first 2 share the same content type, the last one is a different content type. + /// NOTE: The content items themselves are 'Invariant' but their properties are 'Variant' by culture. + /// Normally in Umbraco this is prohibited but our APIs and database do actually support that behavior. + /// It is simpler to have these tests run this way, else we would need to use WithCultureInfos + /// for each item and pass in name values for all cultures we are supporting and then specify the + /// default VariationContextAccessor.VariationContext value to be a default culture instead of "". + /// + private IEnumerable CreateCache(IDataType[] dataTypes, out ContentType[] contentTypes) + { + var result = new List(); + + var propertyDataTypes = new Dictionary + { + // we only have one data type for this test which will be resolved with string empty. + [string.Empty] = dataTypes[0] + }; + + var contentType1 = new ContentType(ShortStringHelper, -1); + + ContentData item1Data = new ContentDataBuilder() + .WithName("Content 1") + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("welcomeText", "Welcome") + .WithPropertyData("welcomeText", "Welcome", "en-US") + .WithPropertyData("welcomeText", "Willkommen", "de") + .WithPropertyData("welcomeText", "Welkom", "nl") + .WithPropertyData("welcomeText2", "Welcome") + .WithPropertyData("welcomeText2", "Welcome", "en-US") + .WithPropertyData("noprop", "xxx") + .Build()) + // build with a dynamically created content type + .Build(ShortStringHelper, propertyDataTypes, contentType1, "ContentType1"); + + ContentNodeKit item1 = ContentNodeKitBuilder.CreateWithContent( + contentType1.Id, + 1, "-1,1", + draftData: item1Data, + publishedData: item1Data); + + result.Add(item1); + + ContentData item2Data = new ContentDataBuilder() + .WithName("Content 2") + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("welcomeText", "Welcome") + .WithPropertyData("welcomeText", "Welcome", "en-US") + .WithPropertyData("noprop", "xxx") + .Build()) + // build while dynamically updating the same content type + .Build(ShortStringHelper, propertyDataTypes, contentType1); + + ContentNodeKit item2 = ContentNodeKitBuilder.CreateWithContent( + contentType1.Id, + 2, "-1,1,2", + parentContentId: 1, + draftData: item2Data, + publishedData: item2Data); + + result.Add(item2); + + var contentType2 = new ContentType(ShortStringHelper, -1); + + ContentData item3Data = new ContentDataBuilder() + .WithName("Content 3") + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("prop3", "Oxxo") + .WithPropertyData("prop3", "Oxxo", "en-US") + .Build()) + // build with a dynamically created content type + .Build(ShortStringHelper, propertyDataTypes, contentType2, "ContentType2"); + + ContentNodeKit item3 = ContentNodeKitBuilder.CreateWithContent( + contentType2.Id, + 3, "-1,1,2,3", + parentContentId: 2, + draftData: item3Data, + publishedData: item3Data); + + result.Add(item3); + + contentTypes = new[] { contentType1, contentType2 }; + + return result; + } + + [Test] + public void Can_Get_Content_For_Populated_Requested_Language() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First(); + var value = content.Value(Mock.Of(), "welcomeText", "en-US"); + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Can_Get_Content_For_Populated_Requested_Non_Default_Language() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First(); + var value = content.Value(Mock.Of(), "welcomeText", "de"); + Assert.AreEqual("Willkommen", value); + } + + [Test] + public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_Without_Fallback() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First(); + var value = content.Value(Mock.Of(), "welcomeText", "fr"); + Assert.IsNull(value); + } + + [Test] + public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Unless_Requested() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First(); + var value = content.Value(Mock.Of(), "welcomeText", "es"); + Assert.IsNull(value); + } + + [Test] + public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First(); + var value = content.Value(PublishedValueFallback, "welcomeText", "es", fallback: Fallback.ToLanguage); + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First(); + var value = content.Value(PublishedValueFallback, "welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First(); + var value = content.Value(Mock.Of(), "welcomeText", "no", fallback: Fallback.ToLanguage); + Assert.IsNull(value); + } + + [Test] + public void Do_Not_Get_Content_Recursively_Unless_Requested() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First().Children.First(); + var value = content.Value(Mock.Of(), "welcomeText2"); + Assert.IsNull(value); + } + + [Test] + public void Can_Get_Content_Recursively() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First().Children.First(); + var value = content.Value(PublishedValueFallback, "welcomeText2", fallback: Fallback.ToAncestors); + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Do_Not_Get_Content_Recursively_Unless_Requested2() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First().Children.First().Children.First(); + Assert.IsNull(content.GetProperty("welcomeText2")); + var value = content.Value(Mock.Of(), "welcomeText2"); + Assert.IsNull(value); + } + + [Test] + public void Can_Get_Content_Recursively2() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First().Children.First().Children.First(); + Assert.IsNull(content.GetProperty("welcomeText2")); + var value = content.Value(PublishedValueFallback, "welcomeText2", fallback: Fallback.ToAncestors); + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Can_Get_Content_Recursively3() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First().Children.First().Children.First(); + Assert.IsNull(content.GetProperty("noprop")); + var value = content.Value(PublishedValueFallback, "noprop", fallback: Fallback.ToAncestors); + // property has no value - based on the converter + // but we still get the value (ie, the converter would do something) + Assert.AreEqual("xxx", value.ToString()); + } + + [Test] + public void Can_Get_Content_With_Recursive_Priority() + { + VariationContextAccessor.VariationContext = new VariationContext("nl"); + + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First().Children.First(); + + var value = content.Value(PublishedValueFallback, "welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language)); + + // No Dutch value is directly assigned. Check has fallen back to Dutch value from parent. + Assert.AreEqual("Welkom", value); + } + + [Test] + public void Can_Get_Content_With_Fallback_Language_Priority() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First().Children.First(); + + var value = content.Value(PublishedValueFallback, "welcomeText", "nl", fallback: Fallback.ToLanguage); + + // No Dutch value is directly assigned. Check has fallen back to English value from language variant. + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Throws_For_Non_Supported_Fallback() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First().Children.First(); + + Assert.Throws(() => content.Value(PublishedValueFallback, "welcomeText", "nl", fallback: Fallback.To(999))); + } + + [Test] + public void Can_Fallback_To_Default_Value() + { + var snapshot = GetPublishedSnapshot(); + var content = snapshot.Content.GetAtRoot().First().Children.First(); + + // no Dutch value is assigned, so getting null + var value = content.Value(PublishedValueFallback, "welcomeText", "nl"); + Assert.IsNull(value); + + // even if we 'just' provide a default value + value = content.Value(PublishedValueFallback, "welcomeText", "nl", defaultValue: "woop"); + Assert.IsNull(value); + + // but it works with proper fallback settings + value = content.Value(PublishedValueFallback, "welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop"); + Assert.AreEqual("woop", value); + } + } +} diff --git a/tests/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentTests.cs similarity index 51% rename from tests/Umbraco.Tests/PublishedContent/PublishedContentTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentTests.cs index c34d9e7595..d7a5a2bc48 100644 --- a/tests/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedContentTests.cs @@ -1,193 +1,108 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Logging; -using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Templates; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Composing; -namespace Umbraco.Tests.PublishedContent +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache { - /// - /// Tests the methods on IPublishedContent using the DefaultPublishedContentStore - /// + [TestFixture] - [UmbracoTest(TypeLoader = UmbracoTestOptions.TypeLoader.PerFixture)] - public class PublishedContentTests : PublishedContentTestBase + public class PublishedContentTests : PublishedSnapshotServiceTestBase { - protected override void Compose() - { - base.Compose(); - _publishedSnapshotAccessorMock = new Mock(); - Builder.Services.AddUnique(_publishedSnapshotAccessorMock.Object); - - Builder.Services.AddUnique(f => new PublishedModelFactory(f.GetRequiredService().GetTypes(), f.GetRequiredService())); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - - var loggerFactory = NullLoggerFactory.Instance; - var mediaService = Mock.Of(); - var contentTypeBaseServiceProvider = Mock.Of(); - var umbracoContextAccessor = Mock.Of(); - var backOfficeSecurityAccessor = Mock.Of(); - var publishedUrlProvider = Mock.Of(); - var imageSourceParser = new HtmlImageSourceParser(publishedUrlProvider); - var serializer = new ConfigurationEditorJsonSerializer(); - var mediaFileService = new MediaFileManager(Mock.Of(), Mock.Of(), - loggerFactory.CreateLogger(), Mock.Of()); - var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, loggerFactory.CreateLogger(), HostingEnvironment, mediaService, contentTypeBaseServiceProvider, mediaFileService, ShortStringHelper, publishedUrlProvider, serializer); - var linkParser = new HtmlLocalLinkParser(umbracoContextAccessor, publishedUrlProvider); - - var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new VoidEditor(DataValueEditorFactory), serializer) { Id = 1 }, - new DataType(new TrueFalsePropertyEditor(DataValueEditorFactory, IOHelper), serializer) { Id = 1001 }, - new DataType(new RichTextPropertyEditor(DataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, linkParser, pastedImages, IOHelper, Mock.Of()), serializer) { Id = 1002 }, - new DataType(new IntegerPropertyEditor(DataValueEditorFactory), serializer) { Id = 1003 }, - new DataType(new TextboxPropertyEditor(DataValueEditorFactory, IOHelper), serializer) { Id = 1004 }, - new DataType(new MediaPickerPropertyEditor(DataValueEditorFactory, IOHelper), serializer) { Id = 1005 }); - Builder.Services.AddUnique(f => dataTypeService); - } - - protected override void Initialize() - { - base.Initialize(); - - var factory = Factory.GetRequiredService() as PublishedContentTypeFactory; - - // need to specify a custom callback for unit tests - // AutoPublishedContentTypes generates properties automatically - // when they are requested, but we must declare those that we - // explicitely want to be here... - - IEnumerable CreatePropertyTypes(IPublishedContentType contentType) - { - // AutoPublishedContentType will auto-generate other properties - yield return factory.CreatePropertyType(contentType, "umbracoNaviHide", 1001); - yield return factory.CreatePropertyType(contentType, "selectedNodes", 1); - yield return factory.CreatePropertyType(contentType, "umbracoUrlAlias", 1); - yield return factory.CreatePropertyType(contentType, "content", 1002); - yield return factory.CreatePropertyType(contentType, "testRecursive", 1); - } - - var compositionAliases = new[] { "MyCompositionAlias" }; - var anythingType = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", compositionAliases, CreatePropertyTypes); - var homeType = new AutoPublishedContentType(Guid.NewGuid(), 0, "home", compositionAliases, CreatePropertyTypes); - ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType; - } - - - protected override TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, ILogger logger, IProfilingLogger profilingLogger , IHostingEnvironment hostingEnvironment) - { - var baseLoader = base.CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, profilingLogger , hostingEnvironment); - - return new TypeLoader(typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, profilingLogger , false, - // this is so the model factory looks into the test assembly - baseLoader.AssembliesToScan - .Union(new[] { typeof(PublishedContentTests).Assembly }) - .ToList()); - } - private readonly Guid _node1173Guid = Guid.NewGuid(); - private Mock _publishedSnapshotAccessorMock; + private PublishedModelFactory _publishedModelFactory; + private DataType[] _dataTypes; - protected override string GetXmlContent(int templateId) + [SetUp] + public override void Setup() { - return @" - - - - -]> - - - - - 1 - - - This is some content]]> - - - - - - - - - - - - - - - - 1 - - - - - - - - - -"; + base.Setup(); + + string xml = PublishedContentXml.PublishedContentTestXml(1234, _node1173Guid); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + _dataTypes = dataTypes; + + // configure the Home content type to be composed of another for tests. + var compositionType = new ContentType(TestHelper.ShortStringHelper, -1) + { + Alias = "MyCompositionAlias" + }; + contentTypes.First(x => x.Alias == "Home").AddContentType(compositionType); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); } - internal IPublishedContent GetNode(int id) + // override to specify our own factory with custom types + protected override IPublishedModelFactory PublishedModelFactory + => _publishedModelFactory ??= new PublishedModelFactory( + new[] { typeof(Home), typeof(Anything), typeof(CustomDocument) }, + PublishedValueFallback); + + [PublishedModel("Home")] + internal class Home : PublishedContentModel { - var ctx = GetUmbracoContext("/test"); - var doc = ctx.Content.GetById(id); - Assert.IsNotNull(doc); - return doc; + public Home(IPublishedContent content, IPublishedValueFallback fallback) + : base(content, fallback) + { } + + public bool UmbracoNaviHide => this.Value(Mock.Of(), "umbracoNaviHide"); + } + + [PublishedModel("anything")] + internal class Anything : PublishedContentModel + { + public Anything(IPublishedContent content, IPublishedValueFallback fallback) + : base(content, fallback) + { } + } + + [PublishedModel("CustomDocument")] + internal class CustomDocument : PublishedContentModel + { + public CustomDocument(IPublishedContent content, IPublishedValueFallback fallback) + : base(content, fallback) + { } } [Test] public void GetNodeByIds() { - var ctx = GetUmbracoContext("/test"); - var contentById = ctx.Content.GetById(1173); + var snapshot = GetPublishedSnapshot(); + + var contentById = snapshot.Content.GetById(1173); Assert.IsNotNull(contentById); - var contentByGuid = ctx.Content.GetById(_node1173Guid); + var contentByGuid = snapshot.Content.GetById(_node1173Guid); Assert.IsNotNull(contentByGuid); Assert.AreEqual(contentById.Id, contentByGuid.Id); Assert.AreEqual(contentById.Key, contentByGuid.Key); - contentById = ctx.Content.GetById(666); + contentById = snapshot.Content.GetById(666); Assert.IsNull(contentById); - contentByGuid = ctx.Content.GetById(Guid.NewGuid()); + contentByGuid = snapshot.Content.GetById(Guid.NewGuid()); Assert.IsNull(contentByGuid); } [Test] public void Is_Last_From_Where_Filter_Dynamic_Linq() { - var doc = GetNode(1173); + var doc = GetContent(1173); var items = doc.Children(VariationContextAccessor).Where(x => x.IsVisible(Mock.Of())).ToIndexedArray(); @@ -195,11 +110,11 @@ namespace Umbraco.Tests.PublishedContent { if (item.Content.Id != 1178) { - Assert.IsFalse(item.IsLast()); + Assert.IsFalse(item.IsLast(), $"The item {item.Content.Id} is last"); } else { - Assert.IsTrue(item.IsLast()); + Assert.IsTrue(item.IsLast(), $"The item {item.Content.Id} is not last"); } } } @@ -207,7 +122,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Is_Last_From_Where_Filter() { - var doc = GetNode(1173); + var doc = GetContent(1173); var items = doc .Children(VariationContextAccessor) @@ -243,30 +158,14 @@ namespace Umbraco.Tests.PublishedContent } } - [PublishedModel("Home")] - internal class Home : PublishedContentModel - { - public Home(IPublishedContent content, IPublishedValueFallback fallback) - : base(content, fallback) - {} - } - - [PublishedModel("anything")] - internal class Anything : PublishedContentModel - { - public Anything(IPublishedContent content, IPublishedValueFallback fallback) - : base(content, fallback) - { } - } - [Test] public void Is_Last_From_Where_Filter2() { - var doc = GetNode(1173); + var doc = GetContent(1173); var ct = doc.ContentType; var items = doc.Children(VariationContextAccessor) - .Select(x => x.CreateModel(Current.PublishedModelFactory)) // linq, returns IEnumerable + .Select(x => x.CreateModel(PublishedModelFactory)) // linq, returns IEnumerable // only way around this is to make sure every IEnumerable extension // explicitely returns a PublishedContentSet, not an IEnumerable @@ -295,7 +194,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Is_Last_From_Take() { - var doc = GetNode(1173); + var doc = GetContent(1173); var items = doc.Children(VariationContextAccessor).Take(4).ToIndexedArray(); @@ -315,7 +214,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Is_Last_From_Skip() { - var doc = GetNode(1173); + var doc = GetContent(1173); foreach (var d in doc.Children(VariationContextAccessor).Skip(1).ToIndexedArray()) { @@ -333,10 +232,10 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Is_Last_From_Concat() { - var doc = GetNode(1173); + var doc = GetContent(1173); var items = doc.Children(VariationContextAccessor) - .Concat(new[] { GetNode(1175), GetNode(4444) }) + .Concat(new[] { GetContent(1175), GetContent(4444) }) .ToIndexedArray(); foreach (var item in items) @@ -355,7 +254,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Descendants_Ordered_Properly() { - var doc = GetNode(1046); + var doc = GetContent(1046); var expected = new[] { 1046, 1173, 1174, 117, 1177, 1178, 1179, 1176, 1175, 4444, 1172 }; var exindex = 0; @@ -370,9 +269,11 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Get_Property_Value_Recursive() { - var doc = GetNode(1174); - var rVal = doc.Value(Factory.GetRequiredService(), "testRecursive", fallback: Fallback.ToAncestors); - var nullVal = doc.Value(Factory.GetRequiredService(), "DoNotFindThis", fallback: Fallback.ToAncestors); + // TODO: We need to use a different fallback? + + var doc = GetContent(1174); + var rVal = doc.Value(PublishedValueFallback, "testRecursive", fallback: Fallback.ToAncestors); + var nullVal = doc.Value(PublishedValueFallback, "DoNotFindThis", fallback: Fallback.ToAncestors); Assert.AreEqual("This is the recursive val", rVal); Assert.AreEqual(null, nullVal); } @@ -380,17 +281,17 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Get_Property_Value_Uses_Converter() { - var doc = GetNode(1173); + var doc = GetContent(1173); - var propVal = doc.Value(Mock.Of(), "content"); + var propVal = doc.Value(PublishedValueFallback, "content"); Assert.IsInstanceOf(typeof(IHtmlEncodedString), propVal); Assert.AreEqual("
This is some content
", propVal.ToString()); - var propVal2 = doc.Value(Mock.Of(), "content"); + var propVal2 = doc.Value(PublishedValueFallback, "content"); Assert.IsInstanceOf(typeof(IHtmlEncodedString), propVal2); Assert.AreEqual("
This is some content
", propVal2.ToString()); - var propVal3 = doc.Value(Mock.Of(), "Content"); + var propVal3 = doc.Value(PublishedValueFallback, "Content"); Assert.IsInstanceOf(typeof(IHtmlEncodedString), propVal3); Assert.AreEqual("
This is some content
", propVal3.ToString()); } @@ -398,12 +299,12 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Complex_Linq() { - var doc = GetNode(1173); + var doc = GetContent(1173); var result = doc.Ancestors().OrderBy(x => x.Level) .Single() .Descendants(Mock.Of()) - .FirstOrDefault(x => x.Value(Mock.Of(), "selectedNodes", defaultValue: "").Split(',').Contains("1173")); + .FirstOrDefault(x => x.Value(PublishedValueFallback, "selectedNodes", fallback: Fallback.ToDefaultValue, defaultValue: "").Split(',').Contains("1173")); Assert.IsNotNull(result); } @@ -411,16 +312,16 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_GroupBy_DocumentTypeAlias() { - var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { }); - var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { }); - var contentTypes = new Dictionary - { - { home.Alias, home }, - { custom.Alias, custom } - }; - ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias]; + //var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { }); + //var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { }); + //var contentTypes = new Dictionary + //{ + // { home.Alias, home }, + // { custom.Alias, custom } + //}; + //ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias]; - var doc = GetNode(1046); + var doc = GetContent(1046); var found1 = doc.Children(VariationContextAccessor).GroupBy(x => x.ContentType.Alias).ToArray(); @@ -432,16 +333,16 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_Where_DocumentTypeAlias() { - var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { }); - var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { }); - var contentTypes = new Dictionary - { - { home.Alias, home }, - { custom.Alias, custom } - }; - ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias]; + //var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { }); + //var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { }); + //var contentTypes = new Dictionary + //{ + // { home.Alias, home }, + // { custom.Alias, custom } + //}; + //ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias]; - var doc = GetNode(1046); + var doc = GetContent(1046); var found1 = doc.Children(VariationContextAccessor).Where(x => x.ContentType.Alias == "CustomDocument"); var found2 = doc.Children(VariationContextAccessor).Where(x => x.ContentType.Alias == "Home"); @@ -453,7 +354,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_Order_By_Update_Date() { - var doc = GetNode(1173); + var doc = GetContent(1173); var ordered = doc.Children(VariationContextAccessor).OrderBy(x => x.UpdateDate); @@ -468,12 +369,12 @@ namespace Umbraco.Tests.PublishedContent [Test] public void FirstChild() { - var doc = GetNode(1173); // has child nodes + var doc = GetContent(1173); // has child nodes Assert.IsNotNull(doc.FirstChild(Mock.Of())); Assert.IsNotNull(doc.FirstChild(Mock.Of(), x => true)); Assert.IsNotNull(doc.FirstChild(Mock.Of())); - doc = GetNode(1175); // does not have child nodes + doc = GetContent(1175); // does not have child nodes Assert.IsNull(doc.FirstChild(Mock.Of())); Assert.IsNull(doc.FirstChild(Mock.Of(), x => true)); Assert.IsNull(doc.FirstChild(Mock.Of())); @@ -482,7 +383,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void FirstChildAsT() { - var doc = GetNode(1046); // has child nodes + var doc = GetContent(1046); // has child nodes var model = doc.FirstChild(Mock.Of(), x => true); // predicate @@ -491,7 +392,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsInstanceOf(model); Assert.IsInstanceOf(model); - doc = GetNode(1175); // does not have child nodes + doc = GetContent(1175); // does not have child nodes Assert.IsNull(doc.FirstChild(Mock.Of())); Assert.IsNull(doc.FirstChild(Mock.Of(), x => true)); } @@ -499,7 +400,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void IsComposedOf() { - var doc = GetNode(1173); + var doc = GetContent(1173); var isComposedOf = doc.IsComposedOf("MyCompositionAlias"); @@ -509,7 +410,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void HasProperty() { - var doc = GetNode(1173); + var doc = GetContent(1173); var hasProp = doc.HasProperty(Constants.Conventions.Content.UrlAlias); @@ -519,7 +420,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void HasValue() { - var doc = GetNode(1173); + var doc = GetContent(1173); var hasValue = doc.HasValue(Mock.Of(), Constants.Conventions.Content.UrlAlias); var noValue = doc.HasValue(Mock.Of(), "blahblahblah"); @@ -531,7 +432,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ancestors_Where_Visible() { - var doc = GetNode(1174); + var doc = GetContent(1174); var whereVisible = doc.Ancestors().Where(x => x.IsVisible(Mock.Of())); Assert.AreEqual(1, whereVisible.Count()); @@ -541,8 +442,8 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Visible() { - var hidden = GetNode(1046); - var visible = GetNode(1173); + var hidden = GetContent(1046); + var visible = GetContent(1173); Assert.IsFalse(hidden.IsVisible(Mock.Of())); Assert.IsTrue(visible.IsVisible(Mock.Of())); @@ -551,7 +452,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ancestor_Or_Self() { - var doc = GetNode(1173); + var doc = GetContent(1173); var result = doc.AncestorOrSelf(); @@ -564,7 +465,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void U4_4559() { - var doc = GetNode(1174); + var doc = GetContent(1174); var result = doc.AncestorOrSelf(1); Assert.IsNotNull(result); Assert.AreEqual(1046, result.Id); @@ -573,27 +474,27 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ancestors_Or_Self() { - var doc = GetNode(1174); + var doc = GetContent(1174); var result = doc.AncestorsOrSelf().ToArray(); Assert.IsNotNull(result); Assert.AreEqual(3, result.Length); - Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1174, 1173, 1046 })); + Assert.IsTrue(result.Select(x => x.Id).ContainsAll(new [] { 1174, 1173, 1046 })); } [Test] public void Ancestors() { - var doc = GetNode(1174); + var doc = GetContent(1174); var result = doc.Ancestors().ToArray(); Assert.IsNotNull(result); Assert.AreEqual(2, result.Length); - Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1173, 1046 })); + Assert.IsTrue(result.Select(x => x.Id).ContainsAll(new [] { 1173, 1046 })); } [Test] @@ -607,12 +508,12 @@ namespace Umbraco.Tests.PublishedContent // -- Custom Doc4: 117 (parent 1173) // - Custom Doc3: 1172 (no parent) - var home = GetNode(1173); - var root = GetNode(1046); - var customDoc = GetNode(1178); - var customDoc2 = GetNode(1179); - var customDoc3 = GetNode(1172); - var customDoc4 = GetNode(117); + var home = GetContent(1173); + var root = GetContent(1046); + var customDoc = GetContent(1178); + var customDoc2 = GetContent(1179); + var customDoc3 = GetContent(1172); + var customDoc4 = GetContent(117); Assert.IsTrue(root.IsAncestor(customDoc4)); Assert.IsFalse(root.IsAncestor(customDoc3)); @@ -656,12 +557,12 @@ namespace Umbraco.Tests.PublishedContent // -- Custom Doc4: 117 (parent 1173) // - Custom Doc3: 1172 (no parent) - var home = GetNode(1173); - var root = GetNode(1046); - var customDoc = GetNode(1178); - var customDoc2 = GetNode(1179); - var customDoc3 = GetNode(1172); - var customDoc4 = GetNode(117); + var home = GetContent(1173); + var root = GetContent(1046); + var customDoc = GetContent(1178); + var customDoc2 = GetContent(1179); + var customDoc3 = GetContent(1172); + var customDoc4 = GetContent(117); Assert.IsTrue(root.IsAncestorOrSelf(customDoc4)); Assert.IsFalse(root.IsAncestorOrSelf(customDoc3)); @@ -699,27 +600,27 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Descendants_Or_Self() { - var doc = GetNode(1046); + var doc = GetContent(1046); var result = doc.DescendantsOrSelf(Mock.Of()).ToArray(); Assert.IsNotNull(result); Assert.AreEqual(10, result.Count()); - Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175 })); + Assert.IsTrue(result.Select(x => x.Id).ContainsAll(new [] { 1046, 1173, 1174, 1176, 1175 })); } [Test] public void Descendants() { - var doc = GetNode(1046); + var doc = GetContent(1046); var result = doc.Descendants(Mock.Of()).ToArray(); Assert.IsNotNull(result); Assert.AreEqual(9, result.Count()); - Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 })); + Assert.IsTrue(result.Select(x => x.Id).ContainsAll(new [] { 1173, 1174, 1176, 1175, 4444 })); } [Test] @@ -733,12 +634,12 @@ namespace Umbraco.Tests.PublishedContent // -- Custom Doc4: 117 (parent 1173) // - Custom Doc3: 1172 (no parent) - var home = GetNode(1173); - var root = GetNode(1046); - var customDoc = GetNode(1178); - var customDoc2 = GetNode(1179); - var customDoc3 = GetNode(1172); - var customDoc4 = GetNode(117); + var home = GetContent(1173); + var root = GetContent(1046); + var customDoc = GetContent(1178); + var customDoc2 = GetContent(1179); + var customDoc3 = GetContent(1172); + var customDoc4 = GetContent(117); Assert.IsFalse(root.IsDescendant(root)); Assert.IsFalse(root.IsDescendant(home)); @@ -782,12 +683,12 @@ namespace Umbraco.Tests.PublishedContent // -- Custom Doc4: 117 (parent 1173) // - Custom Doc3: 1172 (no parent) - var home = GetNode(1173); - var root = GetNode(1046); - var customDoc = GetNode(1178); - var customDoc2 = GetNode(1179); - var customDoc3 = GetNode(1172); - var customDoc4 = GetNode(117); + var home = GetContent(1173); + var root = GetContent(1046); + var customDoc = GetContent(1178); + var customDoc2 = GetContent(1179); + var customDoc3 = GetContent(1172); + var customDoc4 = GetContent(117); Assert.IsTrue(root.IsDescendantOrSelf(root)); Assert.IsFalse(root.IsDescendantOrSelf(home)); @@ -830,39 +731,40 @@ namespace Umbraco.Tests.PublishedContent // --- Level1.1.2: 117 (parent 1173) // --- Level1.1.3: 1177 (parent 1173) // --- Level1.1.4: 1178 (parent 1173) + // ---- Level1.1.4.1: 1179 (parent 1178) // --- Level1.1.5: 1176 (parent 1173) // -- Level1.2: 1175 (parent 1046) // -- Level1.3: 4444 (parent 1046) - var root = GetNode(1046); - var level1_1 = GetNode(1173); - var level1_1_1 = GetNode(1174); - var level1_1_2 = GetNode(117); - var level1_1_3 = GetNode(1177); - var level1_1_4 = GetNode(1178); - var level1_1_5 = GetNode(1176); - var level1_2 = GetNode(1175); - var level1_3 = GetNode(4444); + // - Root : 1172 (no parent) - _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot(It.IsAny())).Returns(new []{root}); + var root = GetContent(1046); + var level1_1 = GetContent(1173); + var level1_1_1 = GetContent(1174); + var level1_1_2 = GetContent(117); + var level1_1_3 = GetContent(1177); + var level1_1_4 = GetContent(1178); + var level1_1_5 = GetContent(1176); + var level1_2 = GetContent(1175); + var level1_3 = GetContent(4444); + var root2 = GetContent(1172); - var variationContextAccessor = Factory.GetRequiredService(); - var publishedSnapshot = _publishedSnapshotAccessorMock.Object.PublishedSnapshot; + var publishedSnapshot = GetPublishedSnapshot(); - CollectionAssertAreEqual(new []{root}, root.SiblingsAndSelf(publishedSnapshot, variationContextAccessor)); + CollectionAssertAreEqual(new[] { root, root2 }, root.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_1.SiblingsAndSelf(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_2.SiblingsAndSelf(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_3.SiblingsAndSelf(publishedSnapshot, variationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1, level1_2, level1_3 }, level1_1.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1, level1_2, level1_3 }, level1_2.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1, level1_2, level1_3 }, level1_3.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.SiblingsAndSelf(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.SiblingsAndSelf(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_3.SiblingsAndSelf(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_4.SiblingsAndSelf(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_5.SiblingsAndSelf(publishedSnapshot, variationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_1.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_2.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_3.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_4.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_5.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor)); } - [Test] + [Test] public void Siblings() { // Structure: @@ -872,40 +774,41 @@ namespace Umbraco.Tests.PublishedContent // --- Level1.1.2: 117 (parent 1173) // --- Level1.1.3: 1177 (parent 1173) // --- Level1.1.4: 1178 (parent 1173) + // ---- Level1.1.4.1: 1179 (parent 1178) // --- Level1.1.5: 1176 (parent 1173) // -- Level1.2: 1175 (parent 1046) // -- Level1.3: 4444 (parent 1046) - var root = GetNode(1046); - var level1_1 = GetNode(1173); - var level1_1_1 = GetNode(1174); - var level1_1_2 = GetNode(117); - var level1_1_3 = GetNode(1177); - var level1_1_4 = GetNode(1178); - var level1_1_5 = GetNode(1176); - var level1_2 = GetNode(1175); - var level1_3 = GetNode(4444); + // - Root : 1172 (no parent) - _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot(It.IsAny())).Returns(new []{root}); + var root = GetContent(1046); + var level1_1 = GetContent(1173); + var level1_1_1 = GetContent(1174); + var level1_1_2 = GetContent(117); + var level1_1_3 = GetContent(1177); + var level1_1_4 = GetContent(1178); + var level1_1_5 = GetContent(1176); + var level1_2 = GetContent(1175); + var level1_3 = GetContent(4444); + var root2 = GetContent(1172); - var variationContextAccessor = Factory.GetRequiredService(); - var publishedSnapshot = _publishedSnapshotAccessorMock.Object.PublishedSnapshot; + var publishedSnapshot = GetPublishedSnapshot(); - CollectionAssertAreEqual(new IPublishedContent[0], root.Siblings(publishedSnapshot, variationContextAccessor)); + CollectionAssertAreEqual(new[] { root2 }, root.Siblings(publishedSnapshot, VariationContextAccessor)); - CollectionAssertAreEqual( new []{level1_2, level1_3}, level1_1.Siblings(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1, level1_3}, level1_2.Siblings(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1, level1_2}, level1_3.Siblings(publishedSnapshot, variationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_2, level1_3 }, level1_1.Siblings(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1, level1_3 }, level1_2.Siblings(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1, level1_2 }, level1_3.Siblings(publishedSnapshot, VariationContextAccessor)); - CollectionAssertAreEqual( new []{ level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.Siblings(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1_1, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.Siblings(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_4, level1_1_5}, level1_1_3.Siblings(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_5}, level1_1_4.Siblings(publishedSnapshot, variationContextAccessor)); - CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4}, level1_1_5.Siblings(publishedSnapshot, variationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_1.Siblings(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_1, level1_1_3, level1_1_4, level1_1_5 }, level1_1_2.Siblings(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_4, level1_1_5 }, level1_1_3.Siblings(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_5 }, level1_1_4.Siblings(publishedSnapshot, VariationContextAccessor)); + CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4 }, level1_1_5.Siblings(publishedSnapshot, VariationContextAccessor)); } private void CollectionAssertAreEqual(IEnumerable expected, IEnumerable actual) - where T: IPublishedContent + where T : IPublishedContent { var e = expected.Select(x => x.Id); var a = actual.Select(x => x.Id); @@ -915,37 +818,26 @@ namespace Umbraco.Tests.PublishedContent [Test] public void FragmentProperty() { - var factory = Factory.GetRequiredService() as PublishedContentTypeFactory; - IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - yield return factory.CreatePropertyType(contentType, "detached", 1003); + yield return PublishedContentTypeFactory.CreatePropertyType(contentType, "detached", _dataTypes[0].Id); } - var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes); + var ct = PublishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes); var pt = ct.GetPropertyType("detached"); var prop = new PublishedElementPropertyBase(pt, null, false, PropertyCacheLevel.None, 5548); Assert.IsInstanceOf(prop.GetValue()); Assert.AreEqual(5548, prop.GetValue()); } - public void Fragment1() - { - var type = ContentTypesCache.Get(PublishedItemType.Content, "detachedSomething"); - var values = new Dictionary(); - var f = new PublishedElement(type, Guid.NewGuid(), values, false); - } - [Test] public void Fragment2() { - var factory = Factory.GetRequiredService() as PublishedContentTypeFactory; - IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - yield return factory.CreatePropertyType(contentType, "legend", 1004); - yield return factory.CreatePropertyType(contentType, "image", 1005); - yield return factory.CreatePropertyType(contentType, "size", 1003); + yield return PublishedContentTypeFactory.CreatePropertyType(contentType, "legend", _dataTypes[0].Id); + yield return PublishedContentTypeFactory.CreatePropertyType(contentType, "image", _dataTypes[0].Id); + yield return PublishedContentTypeFactory.CreatePropertyType(contentType, "size", _dataTypes[0].Id); } const string val1 = "boom bam"; @@ -954,7 +846,7 @@ namespace Umbraco.Tests.PublishedContent var guid = Guid.NewGuid(); - var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes); + var ct = PublishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes); var c = new ImageWithLegendModel(ct, guid, new Dictionary { @@ -967,6 +859,87 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(val3, c.Size); } + [Test] + public void First() + { + var publishedSnapshot = GetPublishedSnapshot(); + var content = publishedSnapshot.Content.GetAtRoot().First(); + Assert.AreEqual("Home", content.Name(VariationContextAccessor)); + } + + [Test] + public void Distinct() + { + var items = GetContent(1173) + .Children(VariationContextAccessor) + .Distinct() + .Distinct() + .ToIndexedArray(); + + Assert.AreEqual(5, items.Length); + + IndexedArrayItem item = items[0]; + Assert.AreEqual(1174, item.Content.Id); + Assert.IsTrue(item.IsFirst()); + Assert.IsFalse(item.IsLast()); + + item = items[^1]; + Assert.AreEqual(1176, item.Content.Id); + Assert.IsFalse(item.IsFirst()); + Assert.IsTrue(item.IsLast()); + } + + [Test] + public void OfType1() + { + var publishedSnapshot = GetPublishedSnapshot(); + var items = publishedSnapshot.Content.GetAtRoot() + .OfType() + .Distinct() + .ToIndexedArray(); + Assert.AreEqual(1, items.Length); + Assert.IsInstanceOf(items.First().Content); + } + + [Test] + public void OfType2() + { + var publishedSnapshot = GetPublishedSnapshot(); + var content = publishedSnapshot.Content.GetAtRoot() + .OfType() + .Distinct() + .ToIndexedArray(); + Assert.AreEqual(1, content.Length); + Assert.IsInstanceOf(content.First().Content); + } + + [Test] + public void OfType() + { + var content = GetContent(1173) + .Children(VariationContextAccessor) + .OfType() + .First(x => x.UmbracoNaviHide == true); + Assert.AreEqual(1176, content.Id); + } + + [Test] + public void Position() + { + var items = GetContent(1173).Children(VariationContextAccessor) + .Where(x => x.Value(Mock.Of(), "umbracoNaviHide") == 0) + .ToIndexedArray(); + + Assert.AreEqual(3, items.Length); + + Assert.IsTrue(items.First().IsFirst()); + Assert.IsFalse(items.First().IsLast()); + Assert.IsFalse(items.Skip(1).First().IsFirst()); + Assert.IsFalse(items.Skip(1).First().IsLast()); + Assert.IsFalse(items.Skip(2).First().IsFirst()); + Assert.IsTrue(items.Skip(2).First().IsLast()); + } + class ImageWithLegendModel : PublishedElement { public ImageWithLegendModel(IPublishedContentType contentType, Guid fragmentKey, Dictionary values, bool previewing) @@ -980,5 +953,31 @@ namespace Umbraco.Tests.PublishedContent public int Size => this.Value(Mock.Of(), "size"); } + + //[PublishedModel("ContentType2")] + //public class ContentType2 : PublishedContentModel + //{ + // #region Plumbing + + // public ContentType2(IPublishedContent content, IPublishedValueFallback fallback) + // : base(content, fallback) + // { } + + // #endregion + + // public int Prop1 => this.Value(Mock.Of(), "prop1"); + //} + + //[PublishedModel("ContentType2Sub")] + //public class ContentType2Sub : ContentType2 + //{ + // #region Plumbing + + // public ContentType2Sub(IPublishedContent content, IPublishedValueFallback fallback) + // : base(content, fallback) + // { } + + // #endregion + //} } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedMediaTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedMediaTests.cs new file mode 100644 index 0000000000..e24383855c --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedMediaTests.cs @@ -0,0 +1,242 @@ +using System.Collections.Generic; +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Infrastructure.Serialization; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache +{ + /// + /// Tests the typed extension methods on IPublishedContent using the DefaultPublishedMediaStore + /// + [TestFixture] + public class PublishedMediaTests : PublishedSnapshotServiceTestBase + { + [SetUp] + public override void Setup() + { + base.Setup(); + + var dataTypes = GetDefaultDataTypes().ToList(); + var serializer = new ConfigurationEditorJsonSerializer(); + var rteDataType = new DataType(new VoidEditor("RTE", Mock.Of()), serializer) { Id = 4 }; + dataTypes.Add(rteDataType); + _dataTypes = dataTypes.ToArray(); + + _propertyDataTypes = new() + { + // defaults will just use the first one + [string.Empty] = _dataTypes[0], + + // content uses the RTE + ["content"] = _dataTypes[1] + }; + } + + private Dictionary _propertyDataTypes; + private DataType[] _dataTypes; + + private ContentNodeKit CreateRoot(out MediaType mediaType) + { + mediaType = new MediaType(ShortStringHelper, -1); + + ContentData item1Data = new ContentDataBuilder() + .WithName("Content 1") + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("content", "
This is some content
") + .Build()) + // build with a dynamically created media type + .Build(ShortStringHelper, _propertyDataTypes, mediaType, "image2"); + + ContentNodeKit item1 = ContentNodeKitBuilder.CreateWithContent( + mediaType.Id, + 1, "-1,1", + draftData: item1Data, + publishedData: item1Data); + + return item1; + } + + private IEnumerable CreateChildren( + int startId, + ContentNodeKit parent, + IMediaType mediaType, + int count) + { + for (int i = 0; i < count; i++) + { + var id = startId + i + 1; + + ContentData item1Data = new ContentDataBuilder() + .WithName("Child " + id) + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("content", "
This is some content
") + .Build()) + .Build(); + + var parentPath = parent.Node.Path; + + ContentNodeKit item1 = ContentNodeKitBuilder.CreateWithContent( + mediaType.Id, + id, $"{parentPath},{id}", + draftData: item1Data, + publishedData: item1Data); + + yield return item1; + } + } + + private void InitializeWithHierarchy( + out int rootId, + out IReadOnlyList firstLevelChildren, + out IReadOnlyList secondLevelChildren) + { + var cache = new List(); + var root = CreateRoot(out MediaType mediaType); + firstLevelChildren = CreateChildren(10, root, mediaType, 3).ToList(); + secondLevelChildren = CreateChildren(20, firstLevelChildren[0], mediaType, 3).ToList(); + cache.Add(root); + cache.AddRange(firstLevelChildren); + cache.AddRange(secondLevelChildren); + InitializedCache(null, null, _dataTypes, cache, new[] { mediaType }); + rootId = root.Node.Id; + } + + [Test] + public void Get_Property_Value_Uses_Converter() + { + var cache = CreateRoot(out MediaType mediaType); + InitializedCache(null, null, _dataTypes.ToArray(), new[] { cache }, new[] { mediaType }); + + var publishedMedia = GetMedia(1); + + var propVal = publishedMedia.Value(PublishedValueFallback, "content"); + Assert.IsInstanceOf(propVal); + Assert.AreEqual("
This is some content
", propVal.ToString()); + + var propVal2 = publishedMedia.Value(PublishedValueFallback, "content"); + Assert.IsInstanceOf(propVal2); + Assert.AreEqual("
This is some content
", propVal2.ToString()); + + var propVal3 = publishedMedia.Value(PublishedValueFallback, "Content"); + Assert.IsInstanceOf(propVal3); + Assert.AreEqual("
This is some content
", propVal3.ToString()); + } + + [Test] + public void Children() + { + InitializeWithHierarchy( + out var rootId, + out IReadOnlyList firstLevelChildren, + out IReadOnlyList secondLevelChildren); + + var publishedMedia = GetMedia(rootId); + + var rootChildren = publishedMedia.Children(VariationContextAccessor); + Assert.IsTrue(rootChildren.Select(x => x.Id).ContainsAll(firstLevelChildren.Select(x => x.Node.Id))); + + var publishedChild1 = GetMedia(firstLevelChildren[0].Node.Id); + var subChildren = publishedChild1.Children(VariationContextAccessor); + Assert.IsTrue(subChildren.Select(x => x.Id).ContainsAll(secondLevelChildren.Select(x => x.Node.Id))); + } + + [Test] + public void Descendants() + { + InitializeWithHierarchy( + out var rootId, + out IReadOnlyList firstLevelChildren, + out IReadOnlyList secondLevelChildren); + + var publishedMedia = GetMedia(rootId); + var rootDescendants = publishedMedia.Descendants(VariationContextAccessor); + + var descendentIds = firstLevelChildren.Select(x => x.Node.Id).Concat(secondLevelChildren.Select(x => x.Node.Id)); + + Assert.IsTrue(rootDescendants.Select(x => x.Id).ContainsAll(descendentIds)); + + var publishedChild1 = GetMedia(firstLevelChildren[0].Node.Id); + var subDescendants = publishedChild1.Descendants(VariationContextAccessor); + Assert.IsTrue(subDescendants.Select(x => x.Id).ContainsAll(secondLevelChildren.Select(x => x.Node.Id))); + } + + [Test] + public void DescendantsOrSelf() + { + InitializeWithHierarchy( + out var rootId, + out IReadOnlyList firstLevelChildren, + out IReadOnlyList secondLevelChildren); + + var publishedMedia = GetMedia(rootId); + var rootDescendantsOrSelf = publishedMedia.DescendantsOrSelf(VariationContextAccessor); + var descendentAndSelfIds = firstLevelChildren.Select(x => x.Node.Id) + .Concat(secondLevelChildren.Select(x => x.Node.Id)) + .Append(rootId); + + Assert.IsTrue(rootDescendantsOrSelf.Select(x => x.Id).ContainsAll(descendentAndSelfIds)); + + var publishedChild1 = GetMedia(firstLevelChildren[0].Node.Id); + var subDescendantsOrSelf = publishedChild1.DescendantsOrSelf(VariationContextAccessor); + Assert.IsTrue(subDescendantsOrSelf.Select(x => x.Id).ContainsAll( + secondLevelChildren.Select(x => x.Node.Id).Append(firstLevelChildren[0].Node.Id))); + } + + [Test] + public void Parent() + { + InitializeWithHierarchy( + out var rootId, + out IReadOnlyList firstLevelChildren, + out IReadOnlyList secondLevelChildren); + + var publishedMedia = GetMedia(rootId); + Assert.AreEqual(null, publishedMedia.Parent); + + var publishedChild1 = GetMedia(firstLevelChildren[0].Node.Id); + Assert.AreEqual(publishedMedia.Id, publishedChild1.Parent.Id); + + var publishedSubChild1 = GetMedia(secondLevelChildren[0].Node.Id); + Assert.AreEqual(firstLevelChildren[0].Node.Id, publishedSubChild1.Parent.Id); + } + + [Test] + public void Ancestors() + { + InitializeWithHierarchy( + out var rootId, + out IReadOnlyList firstLevelChildren, + out IReadOnlyList secondLevelChildren); + + var publishedSubChild1 = GetMedia(secondLevelChildren[0].Node.Id); + Assert.IsTrue(publishedSubChild1.Ancestors().Select(x => x.Id) + .ContainsAll(new[] { firstLevelChildren[0].Node.Id, rootId })); + } + + [Test] + public void AncestorsOrSelf() + { + InitializeWithHierarchy( + out var rootId, + out IReadOnlyList firstLevelChildren, + out IReadOnlyList secondLevelChildren); + + var publishedSubChild1 = GetMedia(secondLevelChildren[0].Node.Id); + Assert.IsTrue(publishedSubChild1.AncestorsOrSelf().Select(x => x.Id) + .ContainsAll(new[] { secondLevelChildren[0].Node.Id, firstLevelChildren[0].Node.Id, rootId })); + } + + + } +} diff --git a/tests/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs similarity index 59% rename from tests/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs index 17e78647f2..532c7ccf4b 100644 --- a/tests/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs @@ -1,77 +1,30 @@ using System; using System.Collections.Generic; -using System.Data; using System.Linq; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; using NUnit.Framework; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Changes; -using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Cms.Tests.Common; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing.Objects; -using Umbraco.Web.Composing; -namespace Umbraco.Tests.PublishedContent +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache { [TestFixture] - public class NuCacheChildrenTests + public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceTestBase { - private IPublishedModelFactory PublishedModelFactory { get; } = new NoopPublishedModelFactory(); - private IVariationContextAccessor VariationContextAccessor { get; } = TestHelper.VariationContextAccessor; - - private IPublishedSnapshotService _snapshotService; - private IVariationContextAccessor _variationAccesor; - private IPublishedSnapshotAccessor _snapshotAccessor; private ContentType _contentTypeInvariant; private ContentType _contentTypeVariant; - private TestDataSource _source; - private IContentCacheDataSerializerFactory _contentNestedDataSerializerFactory; + private ContentType[] _contentTypes; - [TearDown] - public void Teardown() + [SetUp] + public override void Setup() { - _snapshotService?.Dispose(); - } - - private void Init(Func> kits) - { - var factory = Mock.Of(); - Current.Factory = factory; - - var hostingEnvironment = Mock.Of(); - - Mock.Get(factory).Setup(x => x.GetService(typeof(IPublishedModelFactory))).Returns(PublishedModelFactory); - - var runtime = Mock.Of(); - Mock.Get(runtime).Setup(x => x.Level).Returns(RuntimeLevel.Run); - - var serializer = new ConfigurationEditorJsonSerializer(); - - // create data types, property types and content types - var dataType = new DataType(new VoidEditor("Editor", Mock.Of()), serializer) { Id = 3 }; - - var dataTypes = new[] - { - dataType - }; + base.Setup(); var propertyType = new PropertyType(TestHelper.ShortStringHelper, "Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Nothing }; _contentTypeInvariant = new ContentType(TestHelper.ShortStringHelper, -1) { Id = 2, Alias = "itype", Variations = ContentVariation.Nothing }; @@ -81,94 +34,11 @@ namespace Umbraco.Tests.PublishedContent _contentTypeVariant = new ContentType(TestHelper.ShortStringHelper, -1) { Id = 3, Alias = "vtype", Variations = ContentVariation.Culture }; _contentTypeVariant.AddPropertyType(propertyType); - var contentTypes = new[] + _contentTypes = new[] { _contentTypeInvariant, _contentTypeVariant }; - - var contentTypeService = new Mock(); - contentTypeService.Setup(x => x.GetAll()).Returns(contentTypes); - contentTypeService.Setup(x => x.GetAll(It.IsAny())).Returns(contentTypes); - - var mediaTypeService = new Mock(); - mediaTypeService.Setup(x => x.GetAll()).Returns(Enumerable.Empty()); - mediaTypeService.Setup(x => x.GetAll(It.IsAny())).Returns(Enumerable.Empty()); - - var contentTypeServiceBaseFactory = new Mock(); - contentTypeServiceBaseFactory.Setup(x => x.For(It.IsAny())).Returns(contentTypeService.Object); - - var dataTypeService = Mock.Of(); - Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes); - - // create a service context - var serviceContext = ServiceContext.CreatePartial( - dataTypeService: dataTypeService, - memberTypeService: Mock.Of(), - memberService: Mock.Of(), - contentTypeService: contentTypeService.Object, - mediaTypeService: mediaTypeService.Object, - localizationService: Mock.Of(), - domainService: Mock.Of() - ); - - // create a scope provider - var scopeProvider = new Mock(); - scopeProvider - .Setup(x => x.CreateScope( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(Mock.Of); - - // create a published content type factory - var contentTypeFactory = new PublishedContentTypeFactory( - PublishedModelFactory, - new PropertyValueConverterCollection(Array.Empty()), - dataTypeService); - - // create accessors - _variationAccesor = new TestVariationContextAccessor(); - _snapshotAccessor = new TestPublishedSnapshotAccessor(); - - // create a data source for NuCache - _source = new TestDataSource(kits()); - _contentNestedDataSerializerFactory = new JsonContentNestedDataSerializerFactory(); - - var typeFinder = TestHelper.GetTypeFinder(); - - var globalSettings = new GlobalSettings(); - var nuCacheSettings = new NuCacheSettings(); - - // at last, create the complete NuCache snapshot service! - var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; - _snapshotService = new PublishedSnapshotService( - options, - null, - serviceContext, - contentTypeFactory, - _snapshotAccessor, - _variationAccesor, - Mock.Of(), - NullLoggerFactory.Instance, - scopeProvider.Object, - _source, - new TestDefaultCultureAccessor(), - Options.Create(globalSettings), - Mock.Of(), - PublishedModelFactory, - hostingEnvironment, - Options.Create(nuCacheSettings), - _contentNestedDataSerializerFactory); - - - // invariant is the current default - _variationAccesor.VariationContext = new VariationContext(); - - Mock.Get(factory).Setup(x => x.GetService(typeof(IVariationContextAccessor))).Returns(_variationAccesor); } private IEnumerable GetNestedVariantKits() @@ -224,25 +94,13 @@ namespace Umbraco.Tests.PublishedContent var level = path.Count(x => x == ','); var now = DateTime.Now; - var contentData = new ContentData - { - Name = "N" + id, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - }; + var contentData = ContentDataBuilder.CreateBasic("N" + id, now); - return new ContentNodeKit - { - ContentTypeId = _contentTypeInvariant.Id, - Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), - DraftData = null, - PublishedData = contentData - }; + return ContentNodeKitBuilder.CreateWithContent( + _contentTypeInvariant.Id, + id, path, sortOrder, level, parentId, 0, Guid.NewGuid(), DateTime.Now, + null, + contentData); } private IEnumerable GetVariantKits() @@ -289,23 +147,16 @@ namespace Umbraco.Tests.PublishedContent var level = path.Count(x => x == ','); var now = DateTime.Now; - return new ContentNodeKit - { - ContentTypeId = _contentTypeVariant.Id, - Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = "N" + id, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = GetCultureInfos(id, now) - } - }; + var contentData = ContentDataBuilder.CreateVariant( + "N" + id, + GetCultureInfos(id, now), + now); + + return ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeVariant.Id, + id: id, path: path, sortOrder: sortOrder, level: level, parentContentId: parentId, + draftData: null, + publishedData: contentData); } private IEnumerable GetVariantWithDraftKits() @@ -334,28 +185,20 @@ namespace Umbraco.Tests.PublishedContent var level = path.Count(x => x == ','); var now = DateTime.Now; - ContentData CreateContentData(bool published) => new ContentData - { - Name = "N" + id, - Published = published, - TemplateId = 0, - VersionId = 1, - VersionDate = now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = GetCultureInfos(id, now) - }; + ContentData CreateContentData(bool published) => ContentDataBuilder.CreateVariant( + "N" + id, + GetCultureInfos(id, now), + now, + published); var withDraft = id % 2 == 0; var withPublished = !withDraft; - return new ContentNodeKit - { - ContentTypeId = _contentTypeVariant.Id, - Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), - DraftData = withDraft ? CreateContentData(false) : null, - PublishedData = withPublished ? CreateContentData(true) : null - }; + return ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeVariant.Id, + id: id, path: path, sortOrder: sortOrder, level: level, parentContentId: parentId, + draftData: withDraft ? CreateContentData(false) : null, + publishedData: withPublished ? CreateContentData(true) : null); } yield return CreateKit(1, -1, 1); @@ -379,10 +222,9 @@ namespace Umbraco.Tests.PublishedContent [Test] public void EmptyTest() { - Init(() => Enumerable.Empty()); + InitializedCache(Array.Empty(), _contentTypes); - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + var snapshot = GetPublishedSnapshot(); var documents = snapshot.Content.GetAtRoot().ToArray(); Assert.AreEqual(0, documents.Length); @@ -391,37 +233,35 @@ namespace Umbraco.Tests.PublishedContent [Test] public void ChildrenTest() { - Init(GetInvariantKits); + InitializedCache(GetInvariantKits(), _contentTypes); - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + var snapshot = GetPublishedSnapshot(); var documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1", "N2", "N3"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4", "N5", "N6"); - documents = snapshot.Content.GetById(2).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(2).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N9", "N8", "N7"); - documents = snapshot.Content.GetById(3).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(3).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N10"); - documents = snapshot.Content.GetById(4).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(4).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N11", "N12"); - documents = snapshot.Content.GetById(10).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(10).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents); } [Test] public void ParentTest() { - Init(GetInvariantKits); + InitializedCache(GetInvariantKits(), _contentTypes); - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + var snapshot = GetPublishedSnapshot(); Assert.IsNull(snapshot.Content.GetById(1).Parent); Assert.IsNull(snapshot.Content.GetById(2).Parent); @@ -444,40 +284,27 @@ namespace Umbraco.Tests.PublishedContent [Test] public void MoveToRootTest() { - Init(GetInvariantKits); + InitializedCache(GetInvariantKits(), _contentTypes); // get snapshot - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + var snapshot = GetPublishedSnapshot(); // do some changes - var kit = _source.Kits[10]; - _source.Kits[10] = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), 1, "-1,10", 4, -1, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = kit.PublishedData.Name, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; + var kit = NuCacheContentService.ContentKits[10]; + NuCacheContentService.ContentKits[10] = ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeInvariant.Id, + id: kit.Node.Id, path: "-1,10", sortOrder: 4, level: 1, parentContentId: -1, + draftData: null, + publishedData: ContentDataBuilder.CreateBasic(kit.PublishedData.Name)); // notify - _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(10, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(10, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); // changes that *I* make are immediately visible on the current snapshot var documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1", "N2", "N3", "N10"); - documents = snapshot.Content.GetById(3).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(3).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents); Assert.IsNull(snapshot.Content.GetById(10).Parent); @@ -486,40 +313,27 @@ namespace Umbraco.Tests.PublishedContent [Test] public void MoveFromRootTest() { - Init(GetInvariantKits); + InitializedCache(GetInvariantKits(), _contentTypes); // get snapshot - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + var snapshot = GetPublishedSnapshot(); // do some changes - var kit = _source.Kits[1]; - _source.Kits[1] = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), 1, "-1,3,10,1", 1, 10, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = kit.PublishedData.Name, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; + var kit = NuCacheContentService.ContentKits[1]; + NuCacheContentService.ContentKits[1] = ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeInvariant.Id, + id: kit.Node.Id, path: "-1,3,10,1", sortOrder: 1, level: 1, parentContentId: 10, + draftData: null, + publishedData: ContentDataBuilder.CreateBasic(kit.PublishedData.Name)); // notify - _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); // changes that *I* make are immediately visible on the current snapshot var documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N2", "N3"); - documents = snapshot.Content.GetById(10).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(10).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N1"); Assert.AreEqual(10, snapshot.Content.GetById(1).Parent?.Id); @@ -528,166 +342,81 @@ namespace Umbraco.Tests.PublishedContent [Test] public void ReOrderTest() { - Init(GetInvariantKits); + InitializedCache(GetInvariantKits(), _contentTypes); // get snapshot - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + var snapshot = GetPublishedSnapshot(); // do some changes - var kit = _source.Kits[7]; - _source.Kits[7] = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 1, kit.Node.ParentContentId, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = kit.PublishedData.Name, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; + var kit = NuCacheContentService.ContentKits[7]; + NuCacheContentService.ContentKits[7] = ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeInvariant.Id, + id: kit.Node.Id, path: kit.Node.Path, sortOrder: 1, level: kit.Node.Level, parentContentId: kit.Node.ParentContentId, + draftData: null, + publishedData: ContentDataBuilder.CreateBasic(kit.PublishedData.Name)); - kit = _source.Kits[8]; - _source.Kits[8] = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 3, kit.Node.ParentContentId, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = kit.PublishedData.Name, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; + kit = NuCacheContentService.ContentKits[8]; + NuCacheContentService.ContentKits[8] = ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeInvariant.Id, + id: kit.Node.Id, path: kit.Node.Path, sortOrder: 3, level: kit.Node.Level, parentContentId: kit.Node.ParentContentId, + draftData: null, + publishedData: ContentDataBuilder.CreateBasic(kit.PublishedData.Name)); - kit = _source.Kits[9]; - _source.Kits[9] = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 2, kit.Node.ParentContentId, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = kit.PublishedData.Name, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; + kit = NuCacheContentService.ContentKits[9]; + NuCacheContentService.ContentKits[9] = ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeInvariant.Id, + id: kit.Node.Id, path: kit.Node.Path, sortOrder: 2, level: kit.Node.Level, parentContentId: kit.Node.ParentContentId, + draftData: null, + publishedData: ContentDataBuilder.CreateBasic(kit.PublishedData.Name)); // notify - _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(kit.Node.ParentContentId, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(kit.Node.ParentContentId, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); // changes that *I* make are immediately visible on the current snapshot - var documents = snapshot.Content.GetById(kit.Node.ParentContentId).Children(_variationAccesor).ToArray(); + var documents = snapshot.Content.GetById(kit.Node.ParentContentId).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N7", "N9", "N8"); } [Test] public void MoveTest() { - Init(GetInvariantKits); + InitializedCache(GetInvariantKits(), _contentTypes); // get snapshot - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + var snapshot = GetPublishedSnapshot(); // do some changes - var kit = _source.Kits[4]; - _source.Kits[4] = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 2, kit.Node.ParentContentId, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = kit.PublishedData.Name, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; + var kit = NuCacheContentService.ContentKits[4]; + NuCacheContentService.ContentKits[4] = ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeInvariant.Id, + id: kit.Node.Id, path: kit.Node.Path, sortOrder: 2, level: kit.Node.Level, parentContentId: kit.Node.ParentContentId, + draftData: null, + publishedData: ContentDataBuilder.CreateBasic(kit.PublishedData.Name)); - kit = _source.Kits[5]; - _source.Kits[5] = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 3, kit.Node.ParentContentId, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = kit.PublishedData.Name, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; + kit = NuCacheContentService.ContentKits[5]; + NuCacheContentService.ContentKits[5] = ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeInvariant.Id, + id: kit.Node.Id, path: kit.Node.Path, sortOrder: 3, level: kit.Node.Level, parentContentId: kit.Node.ParentContentId, + draftData: null, + publishedData: ContentDataBuilder.CreateBasic(kit.PublishedData.Name)); - kit = _source.Kits[6]; - _source.Kits[6] = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 4, kit.Node.ParentContentId, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = kit.PublishedData.Name, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; + kit = NuCacheContentService.ContentKits[6]; + NuCacheContentService.ContentKits[6] = ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeInvariant.Id, + id: kit.Node.Id, path: kit.Node.Path, sortOrder: 4, level: kit.Node.Level, parentContentId: kit.Node.ParentContentId, + draftData: null, + publishedData: ContentDataBuilder.CreateBasic(kit.PublishedData.Name)); + ; - kit = _source.Kits[7]; - _source.Kits[7] = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, "-1,1,7", 1, 1, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = kit.PublishedData.Name, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; + kit = NuCacheContentService.ContentKits[7]; + NuCacheContentService.ContentKits[7] = ContentNodeKitBuilder.CreateWithContent( + contentTypeId: _contentTypeInvariant.Id, + id: kit.Node.Id, path: "-1,1,7", sortOrder: 1, level: kit.Node.Level, parentContentId: 1, + draftData: null, + publishedData: ContentDataBuilder.CreateBasic(kit.PublishedData.Name)); // notify - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { // removal must come first new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshBranch), @@ -695,10 +424,10 @@ namespace Umbraco.Tests.PublishedContent }, out _, out _); // changes that *I* make are immediately visible on the current snapshot - var documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + var documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N7", "N4", "N5", "N6"); - documents = snapshot.Content.GetById(2).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(2).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N9", "N8"); Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id); @@ -712,7 +441,7 @@ namespace Umbraco.Tests.PublishedContent var paths = new Dictionary { { -1, "-1" } }; - Init(() => new List + InitializedCache(new List { CreateInvariantKit(1, -1, 1, paths), // first level CreateInvariantKit(2, 1, 1, paths), // second level @@ -727,19 +456,18 @@ namespace Umbraco.Tests.PublishedContent CreateInvariantKit(8, 5, 4, paths), CreateInvariantKit(9, 5, 5, paths), CreateInvariantKit(10, 5, 6, paths) - }); + }, _contentTypes); // get snapshot - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + var snapshot = GetPublishedSnapshot(); - var snapshotService = (PublishedSnapshotService)_snapshotService; + var snapshotService = (PublishedSnapshotService)SnapshotService; var contentStore = snapshotService.GetContentStore(); //This will set a flag to force creating a new Gen next time the store is locked (i.e. In Notify) contentStore.CreateSnapshot(); // notify - which ensures there are 2 generations in the cache meaning each LinkedNode has a Next value. - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); @@ -749,7 +477,7 @@ namespace Umbraco.Tests.PublishedContent // to a child, we null out the .Value of the LinkedNode within the while loop because we didn't capture // this value before recursing. Assert.DoesNotThrow(() => - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _)); @@ -758,45 +486,45 @@ namespace Umbraco.Tests.PublishedContent [Test] public void NestedVariationChildrenTest() { - Init(GetNestedVariantKits); + InitializedCache(GetNestedVariantKits(), _contentTypes); - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + // get snapshot + var snapshot = GetPublishedSnapshot(); //TEST with en-us variation context - _variationAccesor.VariationContext = new VariationContext("en-US"); + VariationContextAccessor.VariationContext = new VariationContext("en-US"); var documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1-en-US"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4", "N7-en-US"); //Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants - documents = snapshot.Content.GetById(4).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(4).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N10-en-US", "N11"); //Get the variant and list children, there's a variation context so it should return invariant AND en-us variants - documents = snapshot.Content.GetById(7).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(7).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N12-en-US", "N13"); //TEST with fr-fr variation context - _variationAccesor.VariationContext = new VariationContext("fr-FR"); + VariationContextAccessor.VariationContext = new VariationContext("fr-FR"); documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1-fr-FR"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4", "N7-fr-FR"); //Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants - documents = snapshot.Content.GetById(4).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(4).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N10-fr-FR", "N11"); //Get the variant and list children, there's a variation context so it should return invariant AND en-us variants - documents = snapshot.Content.GetById(7).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(7).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N12-fr-FR", "N13"); //TEST specific cultures @@ -804,26 +532,26 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetAtRoot("fr-FR").ToArray(); AssertDocuments(documents, "N1-fr-FR"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor, "fr-FR").ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor, "fr-FR").ToArray(); AssertDocuments(documents, "N4", "N7-fr-FR"); //NOTE: Returns invariant, this is expected - documents = snapshot.Content.GetById(1).Children(_variationAccesor, "").ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor, "").ToArray(); AssertDocuments(documents, "N4"); //Only returns invariant since that is what was requested - documents = snapshot.Content.GetById(4).Children(_variationAccesor, "fr-FR").ToArray(); + documents = snapshot.Content.GetById(4).Children(VariationContextAccessor, "fr-FR").ToArray(); AssertDocuments(documents, "N10-fr-FR", "N11"); //NOTE: Returns invariant, this is expected - documents = snapshot.Content.GetById(4).Children(_variationAccesor, "").ToArray(); + documents = snapshot.Content.GetById(4).Children(VariationContextAccessor, "").ToArray(); AssertDocuments(documents, "N11"); //Only returns invariant since that is what was requested - documents = snapshot.Content.GetById(7).Children(_variationAccesor, "fr-FR").ToArray(); + documents = snapshot.Content.GetById(7).Children(VariationContextAccessor, "fr-FR").ToArray(); AssertDocuments(documents, "N12-fr-FR", "N13"); //NOTE: Returns invariant, this is expected - documents = snapshot.Content.GetById(7).Children(_variationAccesor, "").ToArray(); + documents = snapshot.Content.GetById(7).Children(VariationContextAccessor, "").ToArray(); AssertDocuments(documents, "N13"); //Only returns invariant since that is what was requested //TEST without variation context // This will actually convert the culture to "" which will be invariant since that's all it will know how to do // This will return a NULL name for culture specific entities because there is no variation context - _variationAccesor.VariationContext = null; + VariationContextAccessor.VariationContext = null; documents = snapshot.Content.GetAtRoot().ToArray(); //will return nothing because there's only variant at root @@ -832,72 +560,72 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetAtRoot("fr-FR").ToArray(); Assert.AreEqual(1, documents.Length); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4"); //Get the invariant and list children - documents = snapshot.Content.GetById(4).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(4).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N11"); //Get the variant and list children - documents = snapshot.Content.GetById(7).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(7).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N13"); } [Test] public void VariantChildrenTest() { - Init(GetVariantKits); + InitializedCache(GetVariantKits(), _contentTypes); - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + // get snapshot + var snapshot = GetPublishedSnapshot(); - _variationAccesor.VariationContext = new VariationContext("en-US"); + VariationContextAccessor.VariationContext = new VariationContext("en-US"); var documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1-en-US", "N2-en-US", "N3-en-US"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4-en-US", "N5-en-US", "N6-en-US"); - documents = snapshot.Content.GetById(2).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(2).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N9-en-US", "N8-en-US", "N7-en-US"); - documents = snapshot.Content.GetById(3).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(3).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N10-en-US"); - documents = snapshot.Content.GetById(4).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(4).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N11-en-US", "N12-en-US"); - documents = snapshot.Content.GetById(10).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(10).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents); - _variationAccesor.VariationContext = new VariationContext("fr-FR"); + VariationContextAccessor.VariationContext = new VariationContext("fr-FR"); documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1-fr-FR", "N3-fr-FR"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4-fr-FR", "N6-fr-FR"); - documents = snapshot.Content.GetById(2).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(2).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N9-fr-FR", "N7-fr-FR"); - documents = snapshot.Content.GetById(3).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(3).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N10-fr-FR"); - documents = snapshot.Content.GetById(4).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(4).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N12-fr-FR"); - documents = snapshot.Content.GetById(10).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(10).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents); - documents = snapshot.Content.GetById(1).Children(_variationAccesor, "*").ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor, "*").ToArray(); AssertDocuments(documents, "N4-fr-FR", null, "N6-fr-FR"); AssertDocuments("en-US", documents, "N4-en-US", "N5-en-US", "N6-en-US"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor, "en-US").ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor, "en-US").ToArray(); AssertDocuments(documents, "N4-fr-FR", null, "N6-fr-FR"); AssertDocuments("en-US", documents, "N4-en-US", "N5-en-US", "N6-en-US"); @@ -909,32 +637,32 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetAtRoot("*").ToArray(); AssertDocuments(documents, "N1-fr-FR", null, "N3-fr-FR"); - documents = snapshot.Content.GetById(1).DescendantsOrSelf(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).DescendantsOrSelf(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N1-fr-FR", "N4-fr-FR", "N12-fr-FR", "N6-fr-FR"); - documents = snapshot.Content.GetById(1).DescendantsOrSelf(_variationAccesor, "*").ToArray(); + documents = snapshot.Content.GetById(1).DescendantsOrSelf(VariationContextAccessor, "*").ToArray(); AssertDocuments(documents, "N1-fr-FR", "N4-fr-FR", null /*11*/, "N12-fr-FR", null /*5*/, "N6-fr-FR"); } [Test] public void RemoveTest() { - Init(GetInvariantKits); + InitializedCache(GetInvariantKits(), _contentTypes); - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + // get snapshot + var snapshot = GetPublishedSnapshot(); var documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1", "N2", "N3"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4", "N5", "N6"); - documents = snapshot.Content.GetById(2).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(2).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N9", "N8", "N7"); // notify - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.Remove), // remove last new ContentCacheRefresher.JsonPayload(5, Guid.Empty, TreeChangeTypes.Remove), // remove middle @@ -944,14 +672,14 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1", "N2"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4", "N6"); - documents = snapshot.Content.GetById(2).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(2).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N8", "N7"); // notify - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.Remove), // remove first new ContentCacheRefresher.JsonPayload(8, Guid.Empty, TreeChangeTypes.Remove), // remove @@ -961,19 +689,19 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N2"); - documents = snapshot.Content.GetById(2).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(2).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents); } [Test] public void UpdateTest() { - Init(GetInvariantKits); + InitializedCache(GetInvariantKits(), _contentTypes); - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + // get snapshot + var snapshot = GetPublishedSnapshot(); - var snapshotService = (PublishedSnapshotService)_snapshotService; + var snapshotService = (PublishedSnapshotService)SnapshotService; var contentStore = snapshotService.GetContentStore(); var parentNodes = contentStore.Test.GetValues(1); @@ -984,14 +712,14 @@ namespace Umbraco.Tests.PublishedContent var documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1", "N2", "N3"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4", "N5", "N6"); - documents = snapshot.Content.GetById(2).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(2).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N9", "N8", "N7"); // notify - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch), new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshNode), @@ -1009,10 +737,10 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1", "N2", "N3"); - documents = snapshot.Content.GetById(1).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(1).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N4", "N5", "N6"); - documents = snapshot.Content.GetById(2).Children(_variationAccesor).ToArray(); + documents = snapshot.Content.GetById(2).Children(VariationContextAccessor).ToArray(); AssertDocuments(documents, "N9", "N8", "N7"); @@ -1021,12 +749,12 @@ namespace Umbraco.Tests.PublishedContent [Test] public void AtRootTest() { - Init(GetVariantWithDraftKits); + InitializedCache(GetVariantWithDraftKits(), _contentTypes); - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + // get snapshot + var snapshot = GetPublishedSnapshot(); - _variationAccesor.VariationContext = new VariationContext("en-US"); + VariationContextAccessor.VariationContext = new VariationContext("en-US"); // N2 is draft only @@ -1050,16 +778,16 @@ namespace Umbraco.Tests.PublishedContent yield return CreateInvariantKit(2, 1, 1, paths); } - Init(GetKits); + InitializedCache(GetKits(), _contentTypes); - var snapshotService = (PublishedSnapshotService)_snapshotService; + var snapshotService = (PublishedSnapshotService)SnapshotService; var contentStore = snapshotService.GetContentStore(); var parentNodes = contentStore.Test.GetValues(1); var parentNode = parentNodes[0]; AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 2); - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.Remove) }, out _, out _); @@ -1089,9 +817,9 @@ namespace Umbraco.Tests.PublishedContent yield return CreateInvariantKit(4, 1, 3, paths); } - Init(GetKits); + InitializedCache(GetKits(), _contentTypes); - var snapshotService = (PublishedSnapshotService)_snapshotService; + var snapshotService = (PublishedSnapshotService)SnapshotService; var contentStore = snapshotService.GetContentStore(); Assert.AreEqual(1, contentStore.Test.LiveGen); @@ -1118,7 +846,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsFalse(contentStore.Test.NextGen); - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.Remove) //remove middle child }, out _, out _); @@ -1169,9 +897,9 @@ namespace Umbraco.Tests.PublishedContent yield return CreateInvariantKit(40, 1, 3, paths); } - Init(GetKits); + InitializedCache(GetKits(), _contentTypes); - var snapshotService = (PublishedSnapshotService)_snapshotService; + var snapshotService = (PublishedSnapshotService)SnapshotService; var contentStore = snapshotService.GetContentStore(); Assert.AreEqual(1, contentStore.Test.LiveGen); @@ -1186,7 +914,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsFalse(contentStore.Test.NextGen); - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshNode) }, out _, out _); @@ -1241,12 +969,12 @@ namespace Umbraco.Tests.PublishedContent } //init with all published - Init(GetKits); + InitializedCache(GetKits(), _contentTypes); - var snapshotService = (PublishedSnapshotService)_snapshotService; + var snapshotService = (PublishedSnapshotService)SnapshotService; var contentStore = snapshotService.GetContentStore(); - var rootKit = _source.Kits[1].Clone(PublishedModelFactory); + var rootKit = NuCacheContentService.ContentKits[1].Clone(PublishedModelFactory); void ChangePublishFlagOfRoot(bool published, int assertGen, TreeChangeTypes changeType) { @@ -1256,12 +984,13 @@ namespace Umbraco.Tests.PublishedContent Assert.IsFalse(contentStore.Test.NextGen); //Change the root publish flag - var kit = rootKit.Clone(PublishedModelFactory); - kit.DraftData = published ? null : kit.PublishedData; - kit.PublishedData = published ? kit.PublishedData : null; - _source.Kits[1] = kit; + var kit = rootKit.Clone( + PublishedModelFactory, + published ? null : rootKit.PublishedData, + published ? rootKit.PublishedData : null); + NuCacheContentService.ContentKits[1] = kit; - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, changeType) }, out _, out _); @@ -1311,9 +1040,9 @@ namespace Umbraco.Tests.PublishedContent yield return CreateInvariantKit(4, 1, 3, paths); } - Init(GetKits); + InitializedCache(GetKits(), _contentTypes); - var snapshotService = (PublishedSnapshotService)_snapshotService; + var snapshotService = (PublishedSnapshotService)SnapshotService; var contentStore = snapshotService.GetContentStore(); Assert.AreEqual(1, contentStore.Test.LiveGen); @@ -1340,7 +1069,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsFalse(contentStore.Test.NextGen); - _snapshotService.Notify(new[] + SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.RefreshBranch) //remove middle child }, out _, out _); @@ -1371,13 +1100,13 @@ namespace Umbraco.Tests.PublishedContent public void MultipleCacheIteration() { //see https://github.com/umbraco/Umbraco-CMS/issues/7798 - Init(GetInvariantKits); - var snapshot = this._snapshotService.CreatePublishedSnapshot(previewToken: null); - _snapshotAccessor.PublishedSnapshot = snapshot; + InitializedCache(GetInvariantKits(), _contentTypes); + var snapshot = GetPublishedSnapshot(); var items = snapshot.Content.GetByXPath("/root/itype"); Assert.AreEqual(items.Count(), items.Count()); } + private void AssertLinkedNode(ContentNode node, int parent, int prevSibling, int nextSibling, int firstChild, int lastChild) { Assert.AreEqual(parent, node.ParentContentId); @@ -1398,7 +1127,7 @@ namespace Umbraco.Tests.PublishedContent { Assert.AreEqual(names.Length, documents.Length); for (var i = 0; i < names.Length; i++) - Assert.AreEqual(names[i], documents[i].Name(_variationAccesor, culture)); + Assert.AreEqual(names[i], documents[i].Name(VariationContextAccessor, culture)); } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceContentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceContentTests.cs new file mode 100644 index 0000000000..00c3e965c6 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceContentTests.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services.Changes; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache +{ + + + [TestFixture] + public class PublishedSnapshotServiceContentTests : PublishedSnapshotServiceTestBase + { + private ContentType _contentType; + private PropertyType _propertyType; + + [SetUp] + public override void Setup() + { + base.Setup(); + + _propertyType = new PropertyType(TestHelper.ShortStringHelper, "Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Culture }; + _contentType = new ContentType(TestHelper.ShortStringHelper, -1) { Id = 2, Alias = "alias-ct", Variations = ContentVariation.Culture }; + _contentType.AddPropertyType(_propertyType); + + var contentTypes = new[] + { + _contentType + }; + + InitializedCache(new[] { CreateKit() }, contentTypes); + } + + private ContentNodeKit CreateKit() + { + var draftData = new ContentDataBuilder() + .WithName("It Works2!") + .WithPublished(false) + .WithProperties(new Dictionary + { + ["prop"] = new[] + { + new PropertyData { Culture = "", Segment = "", Value = "val2" }, + new PropertyData { Culture = "fr-FR", Segment = "", Value = "val-fr2" }, + new PropertyData { Culture = "en-UK", Segment = "", Value = "val-uk2" }, + new PropertyData { Culture = "dk-DA", Segment = "", Value = "val-da2" }, + new PropertyData { Culture = "de-DE", Segment = "", Value = "val-de2" } + } + }) + .WithCultureInfos(new Dictionary + { + // draft data = everything, and IsDraft indicates what's edited + ["fr-FR"] = new CultureVariation { Name = "name-fr2", IsDraft = true, Date = new DateTime(2018, 01, 03, 01, 00, 00) }, + ["en-UK"] = new CultureVariation { Name = "name-uk2", IsDraft = true, Date = new DateTime(2018, 01, 04, 01, 00, 00) }, + ["dk-DA"] = new CultureVariation { Name = "name-da2", IsDraft = true, Date = new DateTime(2018, 01, 05, 01, 00, 00) }, + ["de-DE"] = new CultureVariation { Name = "name-de1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) } + }) + .Build(); + + var publishedData = new ContentDataBuilder() + .WithName("It Works1!") + .WithPublished(true) + .WithProperties(new Dictionary + { + ["prop"] = new[] + { + new PropertyData { Culture = "", Segment = "", Value = "val1" }, + new PropertyData { Culture = "fr-FR", Segment = "", Value = "val-fr1" }, + new PropertyData { Culture = "en-UK", Segment = "", Value = "val-uk1" } + } + }) + .WithCultureInfos(new Dictionary + { + // published data = only what's actually published, and IsDraft has to be false + ["fr-FR"] = new CultureVariation { Name = "name-fr1", IsDraft = false, Date = new DateTime(2018, 01, 01, 01, 00, 00) }, + ["en-UK"] = new CultureVariation { Name = "name-uk1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) }, + ["de-DE"] = new CultureVariation { Name = "name-de1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) } + }) + .Build(); + + var kit = ContentNodeKitBuilder.CreateWithContent( + 2, + 1, "-1,1", 0, + draftData: draftData, + publishedData: publishedData); + + return kit; + } + + [Test] + public void Verifies_Variant_Data() + { + // this test implements a full standalone NuCache (based upon a test IDataSource, does not + // use any local db files, does not rely on any database) - and tests variations + + // get a snapshot, get a published content + IPublishedSnapshot snapshot = GetPublishedSnapshot(); + IPublishedContent publishedContent = snapshot.Content.GetById(1); + + Assert.IsNotNull(publishedContent); + Assert.AreEqual("val1", publishedContent.Value(Mock.Of(), "prop")); + Assert.AreEqual("val-fr1", publishedContent.Value(Mock.Of(), "prop", "fr-FR")); + Assert.AreEqual("val-uk1", publishedContent.Value(Mock.Of(), "prop", "en-UK")); + + Assert.IsNull(publishedContent.Name(VariationContextAccessor)); // no invariant name for varying content + Assert.AreEqual("name-fr1", publishedContent.Name(VariationContextAccessor, "fr-FR")); + Assert.AreEqual("name-uk1", publishedContent.Name(VariationContextAccessor, "en-UK")); + + var draftContent = snapshot.Content.GetById(true, 1); + Assert.AreEqual("val2", draftContent.Value(Mock.Of(), "prop")); + Assert.AreEqual("val-fr2", draftContent.Value(Mock.Of(), "prop", "fr-FR")); + Assert.AreEqual("val-uk2", draftContent.Value(Mock.Of(), "prop", "en-UK")); + + Assert.IsNull(draftContent.Name(VariationContextAccessor)); // no invariant name for varying content + Assert.AreEqual("name-fr2", draftContent.Name(VariationContextAccessor, "fr-FR")); + Assert.AreEqual("name-uk2", draftContent.Name(VariationContextAccessor, "en-UK")); + + // now french is default + VariationContextAccessor.VariationContext = new VariationContext("fr-FR"); + Assert.AreEqual("val-fr1", publishedContent.Value(Mock.Of(), "prop")); + Assert.AreEqual("name-fr1", publishedContent.Name(VariationContextAccessor)); + Assert.AreEqual(new DateTime(2018, 01, 01, 01, 00, 00), publishedContent.CultureDate(VariationContextAccessor)); + + // now uk is default + VariationContextAccessor.VariationContext = new VariationContext("en-UK"); + Assert.AreEqual("val-uk1", publishedContent.Value(Mock.Of(), "prop")); + Assert.AreEqual("name-uk1", publishedContent.Name(VariationContextAccessor)); + Assert.AreEqual(new DateTime(2018, 01, 02, 01, 00, 00), publishedContent.CultureDate(VariationContextAccessor)); + + // invariant needs to be retrieved explicitly, when it's not default + Assert.AreEqual("val1", publishedContent.Value(Mock.Of(), "prop", culture: "")); + + // but, + // if the content type / property type does not vary, then it's all invariant again + // modify the content type and property type, notify the snapshot service + _contentType.Variations = ContentVariation.Nothing; + _propertyType.Variations = ContentVariation.Nothing; + SnapshotService.Notify(new[] { new ContentTypeCacheRefresher.JsonPayload("IContentType", publishedContent.ContentType.Id, ContentTypeChangeTypes.RefreshMain) }); + + // get a new snapshot (nothing changed in the old one), get the published content again + var anotherSnapshot = SnapshotService.CreatePublishedSnapshot(previewToken: null); + var againContent = anotherSnapshot.Content.GetById(1); + + Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.Variations); + Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.GetPropertyType("prop").Variations); + + // now, "no culture" means "invariant" + Assert.AreEqual("It Works1!", againContent.Name(VariationContextAccessor)); + Assert.AreEqual("val1", againContent.Value(Mock.Of(), "prop")); + } + + [Test] + public void Verifies_Published_And_Draft_Content() + { + // get the published published content + var snapshot = GetPublishedSnapshot(); + var c1 = snapshot.Content.GetById(1); + + // published content = nothing is draft here + Assert.IsFalse(c1.IsDraft("fr-FR")); + Assert.IsFalse(c1.IsDraft("en-UK")); + Assert.IsFalse(c1.IsDraft("dk-DA")); + Assert.IsFalse(c1.IsDraft("de-DE")); + + // and only those with published name, are published + Assert.IsTrue(c1.IsPublished("fr-FR")); + Assert.IsTrue(c1.IsPublished("en-UK")); + Assert.IsFalse(c1.IsDraft("dk-DA")); + Assert.IsTrue(c1.IsPublished("de-DE")); + + // get the draft published content + var c2 = snapshot.Content.GetById(true, 1); + + // draft content = we have drafts + Assert.IsTrue(c2.IsDraft("fr-FR")); + Assert.IsTrue(c2.IsDraft("en-UK")); + Assert.IsTrue(c2.IsDraft("dk-DA")); + Assert.IsFalse(c2.IsDraft("de-DE")); // except for the one that does not + + // and only those with published name, are published + Assert.IsTrue(c2.IsPublished("fr-FR")); + Assert.IsTrue(c2.IsPublished("en-UK")); + Assert.IsFalse(c2.IsPublished("dk-DA")); + Assert.IsTrue(c2.IsPublished("de-DE")); + } + + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/RootNodeTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/RootNodeTests.cs new file mode 100644 index 0000000000..1ec48759ad --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/RootNodeTests.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache +{ + + [TestFixture] + public class RootNodeTests : PublishedSnapshotServiceTestBase + { + [SetUp] + public override void Setup() + { + base.Setup(); + + string xml = PublishedContentXml.TestWithDatabaseXml(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes); + } + + [Test] + public void PublishedContentHasNoRootNode() + { + var snapshot = GetPublishedSnapshot(); + + // there is no content node with ID -1 + var content = snapshot.Content.GetById(-1); + Assert.IsNull(content); + + // content at root has null parent + content = snapshot.Content.GetById(1046); + Assert.IsNotNull(content); + Assert.AreEqual(1, content.Level); + Assert.IsNull(content.Parent); + + // non-existing content is null + content = snapshot.Content.GetById(666); + Assert.IsNull(content); + } + + } +} diff --git a/tests/Umbraco.Tests/Routing/UrlRoutesTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/UrlRoutesTests.cs similarity index 68% rename from tests/Umbraco.Tests/Routing/UrlRoutesTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/UrlRoutesTests.cs index d315cc53a5..a2cfe7d0e6 100644 --- a/tests/Umbraco.Tests/Routing/UrlRoutesTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/UrlRoutesTests.cs @@ -1,25 +1,23 @@ -using System; +using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.TestHelpers; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Tests.Common.Published; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; -namespace Umbraco.Tests.Routing +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache { // purpose: test the values returned by PublishedContentCache.GetRouteById // and .GetByRoute (no caching at all, just routing nice URLs) including all // the quirks due to hideTopLevelFromPath and backward compatibility. - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class UrlRoutesTests : TestWithDatabaseBase + public class UrlRoutesTests : PublishedSnapshotServiceTestBase { - #region Test Setup - - protected override string GetXmlContent(int templateId) - { - return @" + private static string GetXmlContent(int templateId) + => @" @@ -48,17 +46,6 @@ namespace Umbraco.Tests.Routing "; - } - - protected override void Initialize() - { - base.Initialize(); - - if (FirstTestInFixture) - ServiceContext.ContentTypeService.Save(new ContentType(ShortStringHelper, -1) { Alias = "Doc", Name = "name" }); - } - - #endregion /* * Just so it's documented somewhere, as of jan. 2017, routes obey the following pseudo-code: @@ -193,11 +180,19 @@ DetermineRouteById(id): [TestCase(2006, false, "/x/b/e")] public void GetRouteByIdNoHide(int id, bool hide, string expected) { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = hide }; + GlobalSettings.HideTopLevelNodeFromPath = hide; - var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings); - var cache = umbracoContext.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); + string xml = GetXmlContent(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var cache = GetPublishedSnapshot().Content; var route = cache.GetRouteById(false, id); Assert.AreEqual(expected, route); @@ -216,12 +211,19 @@ DetermineRouteById(id): [TestCase(2006, true, "/b/e")] // risky! public void GetRouteByIdHide(int id, bool hide, string expected) { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = hide }; + GlobalSettings.HideTopLevelNodeFromPath = hide; - var snapshotService = CreatePublishedSnapshotService(globalSettings); - var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings, snapshotService: snapshotService); - var cache = umbracoContext.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); + string xml = GetXmlContent(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var cache = GetPublishedSnapshot().Content; var route = cache.GetRouteById(false, id); Assert.AreEqual(expected, route); @@ -230,27 +232,23 @@ DetermineRouteById(id): [Test] public void GetRouteByIdCache() { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; + GlobalSettings.HideTopLevelNodeFromPath = false; + + string xml = GetXmlContent(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var cache = GetPublishedSnapshot().Content; - var snapshotService = CreatePublishedSnapshotService(globalSettings); - var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings, snapshotService: snapshotService); - var cache = umbracoContext.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var route = cache.GetRouteById(false, 1000); Assert.AreEqual("/a", route); - - // GetRouteById registers a non-trusted route, which is cached for - // id -> route queries (fast GetUrl) but *not* for route -> id - // queries (safe inbound routing) - - var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); - Assert.AreEqual(1, cachedRoutes.Count); - Assert.IsTrue(cachedRoutes.ContainsKey(1000)); - Assert.AreEqual("/a", cachedRoutes[1000]); - - var cachedIds = cache.RoutesCache.GetCachedIds(); - Assert.AreEqual(0, cachedIds.Count); } [TestCase("/", false, 1000)] @@ -261,12 +259,19 @@ DetermineRouteById(id): [TestCase("/x", false, 2000)] public void GetByRouteNoHide(string route, bool hide, int expected) { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = hide }; + GlobalSettings.HideTopLevelNodeFromPath = hide; - var snapshotService = CreatePublishedSnapshotService(globalSettings); - var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings, snapshotService: snapshotService); - var cache = umbracoContext.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); + string xml = GetXmlContent(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var cache = GetPublishedSnapshot().Content; const bool preview = false; // make sure we don't cache - but HOW? should be some sort of switch?! var content = cache.GetByRoute(preview, route); @@ -292,12 +297,19 @@ DetermineRouteById(id): [TestCase("/b/c", true, 1002)] // (hence the 2005 collision) public void GetByRouteHide(string route, bool hide, int expected) { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = hide }; + GlobalSettings.HideTopLevelNodeFromPath = hide; - var snapshotService = CreatePublishedSnapshotService(globalSettings); - var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings, snapshotService: snapshotService); - var cache = umbracoContext.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); + string xml = GetXmlContent(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var cache = GetPublishedSnapshot().Content; const bool preview = false; // make sure we don't cache - but HOW? should be some sort of switch?! var content = cache.GetByRoute(preview, route); @@ -315,30 +327,23 @@ DetermineRouteById(id): [Test] public void GetByRouteCache() { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; + GlobalSettings.HideTopLevelNodeFromPath = false; - var snapshotService = CreatePublishedSnapshotService(globalSettings); - var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings, snapshotService:snapshotService); - var cache = umbracoContext.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); + string xml = GetXmlContent(1234); + + IEnumerable kits = PublishedContentXmlAdapter.GetContentNodeKits( + xml, + TestHelper.ShortStringHelper, + out ContentType[] contentTypes, + out DataType[] dataTypes).ToList(); + + InitializedCache(kits, contentTypes, dataTypes: dataTypes); + + var cache = GetPublishedSnapshot().Content; var content = cache.GetByRoute(false, "/a/b/c"); Assert.IsNotNull(content); Assert.AreEqual(1002, content.Id); - - // GetByRoute registers a trusted route, which is cached both for - // id -> route queries (fast GetUrl) and for route -> id queries - // (fast inbound routing) - - var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); - Assert.AreEqual(1, cachedRoutes.Count); - Assert.IsTrue(cachedRoutes.ContainsKey(1002)); - Assert.AreEqual("/a/b/c", cachedRoutes[1002]); - - var cachedIds = cache.RoutesCache.GetCachedIds(); - Assert.AreEqual(1, cachedIds.Count); - Assert.IsTrue(cachedIds.ContainsKey("/a/b/c")); - Assert.AreEqual(1002, cachedIds["/a/b/c"]); } } } diff --git a/tests/Umbraco.Tests/Serialization/AutoInterningStringConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/AutoInterningStringConverterTests.cs similarity index 87% rename from tests/Umbraco.Tests/Serialization/AutoInterningStringConverterTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/AutoInterningStringConverterTests.cs index f83ea940c9..472536351b 100644 --- a/tests/Umbraco.Tests/Serialization/AutoInterningStringConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/AutoInterningStringConverterTests.cs @@ -1,14 +1,11 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Serialization; -using Umbraco.Core.Serialization; +using Umbraco.Cms.Infrastructure.Serialization; -namespace Umbraco.Tests.Serialization +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Serialization { [TestFixture] public class AutoInterningStringConverterTests @@ -61,7 +58,7 @@ namespace Umbraco.Tests.Serialization public string Name { get; set; } [JsonConverter(typeof(AutoInterningStringKeyCaseInsensitiveDictionaryConverter))] - public Dictionary Values = new Dictionary(); + public Dictionary Values { get; set; } = new Dictionary(); } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs index ea4c043aa2..497af66d63 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs @@ -23,10 +23,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { var culture = CultureInfo.GetCultureInfo("en-US"); var txtService = new LocalizedTextService( - new Dictionary>> + new Dictionary>>> { { - culture, new Dictionary> + culture, new Lazy>>(() => new Dictionary> { { "testArea1", new Dictionary @@ -42,7 +42,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { "blah2", "blahValue2" } } }, - } + }) } }, s_loggerFactory.CreateLogger()); @@ -119,10 +119,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { var culture = CultureInfo.GetCultureInfo("en-US"); var txtService = new LocalizedTextService( - new Dictionary>> + new Dictionary>>> { { - culture, new Dictionary> + culture, new Lazy>>(() => new Dictionary> { { "testArea", new Dictionary @@ -130,7 +130,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { "testKey", "testValue" } } } - } + }) } }, s_loggerFactory.CreateLogger()); @@ -144,10 +144,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { var culture = CultureInfo.GetCultureInfo("en-US"); var txtService = new LocalizedTextService( - new Dictionary>> + new Dictionary>>> { { - culture, new Dictionary> + culture, new Lazy>>(() => new Dictionary> { { "testArea", new Dictionary @@ -155,7 +155,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { "testKey", "testValue" } } } - } + }) + } }, s_loggerFactory.CreateLogger()); @@ -169,10 +170,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { var culture = CultureInfo.GetCultureInfo("en-US"); var txtService = new LocalizedTextService( - new Dictionary>> + new Dictionary>>> { { - culture, new Dictionary> + culture, new Lazy>>(() => new Dictionary> { { "testArea", new Dictionary @@ -180,7 +181,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { "testKey", "testValue" } } } - } + }) } }, s_loggerFactory.CreateLogger()); @@ -195,10 +196,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { var culture = CultureInfo.GetCultureInfo("en-US"); var txtService = new LocalizedTextService( - new Dictionary>> + new Dictionary>>> { { - culture, new Dictionary> + culture, new Lazy>>(() => new Dictionary> { { "testArea", new Dictionary @@ -206,7 +207,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { "testKey", "testValue" } } } - } + }) } }, s_loggerFactory.CreateLogger()); @@ -220,10 +221,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { var culture = CultureInfo.GetCultureInfo("en-US"); var txtService = new LocalizedTextService( - new Dictionary>> + new Dictionary>>> { { - culture, new Dictionary> + culture, new Lazy>>(() => new Dictionary> { { "testArea", new Dictionary @@ -231,7 +232,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { "testKey", "Hello %0%, you are such a %1% %2%" } } } - } + }) + } }, s_loggerFactory.CreateLogger()); @@ -350,10 +352,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { var culture = CultureInfo.GetCultureInfo("en-US"); var txtService = new LocalizedTextService( - new Dictionary>> + new Dictionary>>> { { - culture, new Dictionary> + culture, new Lazy>>(() => new Dictionary> { { "testArea", new Dictionary @@ -361,7 +363,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services { "testKey", "testValue" } } } - } + }) } }, s_loggerFactory.CreateLogger()); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs index 4dea81facb..73f38da362 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs @@ -26,6 +26,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded Id = 1, Alias = "type1", ClrName = "Type1", + Name = "type1Name", ParentId = 0, BaseType = null, ItemType = TypeModel.ItemTypes.Content, @@ -34,6 +35,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded { Alias = "prop1", ClrName = "Prop1", + Name = "prop1Name", ModelClrType = typeof(string), }); @@ -67,6 +69,7 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.PublishedModels { + /// type1Name [PublishedModel(""type1"")] public partial class Type1 : PublishedContentModel { @@ -97,6 +100,9 @@ namespace Umbraco.Cms.Web.Common.PublishedModels // properties + /// + /// prop1Name + /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] [global::System.Diagnostics.CodeAnalysis.MaybeNull] [ImplementPropertyType(""prop1"")] @@ -271,6 +277,388 @@ namespace Umbraco.Cms.Web.Common.PublishedModels Assert.IsTrue(gen.Contains(" global::Umbraco.Cms.Core.Exceptions.BootFailedException Prop3")); } + [Test] + public void GenerateInheritedType() + { + var parentType = new TypeModel + { + Id = 1, + Alias = "parentType", + ClrName = "ParentType", + Name = "parentTypeName", + ParentId = 0, + IsParent = true, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + }; + parentType.Properties.Add(new PropertyModel + { + Alias = "prop1", + ClrName = "Prop1", + Name = "prop1Name", + ModelClrType = typeof(string), + }); + + var childType = new TypeModel + { + Id = 2, + Alias = "childType", + ClrName = "ChildType", + Name = "childTypeName", + ParentId = 1, + BaseType = parentType, + ItemType = TypeModel.ItemTypes.Content, + }; + + TypeModel[] docTypes = new[] { parentType, childType }; + + var modelsBuilderConfig = new ModelsBuilderSettings(); + var builder = new TextBuilder(modelsBuilderConfig, docTypes); + + var sb = new StringBuilder(); + builder.Generate(sb, builder.GetModelsToGenerate().First()); + var genParent = sb.ToString(); + + SemVersion version = ApiVersion.Current.Version; + + var expectedParent = @"//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v" + version + @" +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// parentTypeName + [PublishedModel(""parentType"")] + public partial class ParentType : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + public new const string ModelTypeAlias = ""parentType""; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public ParentType(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// prop1Name + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType(""prop1"")] + public virtual string Prop1 => this.Value(_publishedValueFallback, ""prop1""); + } +} +"; + Console.WriteLine(genParent); + Assert.AreEqual(expectedParent.ClearLf(), genParent); + + var sb2 = new StringBuilder(); + builder.Generate(sb2, builder.GetModelsToGenerate().Skip(1).First()); + var genChild = sb2.ToString(); + + var expectedChild = @"//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v" + version + @" +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// childTypeName + [PublishedModel(""childType"")] + public partial class ChildType : ParentType + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + public new const string ModelTypeAlias = ""childType""; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public ChildType(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + } +} +"; + + Console.WriteLine(genChild); + Assert.AreEqual(expectedChild.ClearLf(), genChild); + + } + + [Test] + public void GenerateComposedType() + { + // Umbraco returns nice, pascal-cased names. + var composition1 = new TypeModel + { + Id = 2, + Alias = "composition1", + ClrName = "Composition1", + Name = "composition1Name", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + IsMixin = true, + }; + composition1.Properties.Add(new PropertyModel + { + Alias = "compositionProp", + ClrName = "CompositionProp", + Name = "compositionPropName", + ModelClrType = typeof(string), + ClrTypeName = typeof(string).FullName + }); + + var type1 = new TypeModel + { + Id = 1, + Alias = "type1", + ClrName = "Type1", + Name = "type1Name", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + }; + type1.Properties.Add(new PropertyModel + { + Alias = "prop1", + ClrName = "Prop1", + Name = "prop1Name", + ModelClrType = typeof(string), + }); + type1.MixinTypes.Add(composition1); + + TypeModel[] types = new[] { type1, composition1 }; + + var modelsBuilderConfig = new ModelsBuilderSettings(); + var builder = new TextBuilder(modelsBuilderConfig, types); + + SemVersion version = ApiVersion.Current.Version; + + var sb = new StringBuilder(); + builder.Generate(sb, builder.GetModelsToGenerate().First()); + var genComposed = sb.ToString(); + + var expectedComposed = @"//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v" + version + @" +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// type1Name + [PublishedModel(""type1"")] + public partial class Type1 : PublishedContentModel, IComposition1 + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + public new const string ModelTypeAlias = ""type1""; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public Type1(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// prop1Name + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType(""prop1"")] + public virtual string Prop1 => this.Value(_publishedValueFallback, ""prop1""); + + /// + /// compositionPropName + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType(""compositionProp"")] + public virtual string CompositionProp => global::Umbraco.Cms.Web.Common.PublishedModels.Composition1.GetCompositionProp(this, _publishedValueFallback); + } +} +"; + Console.WriteLine(genComposed); + Assert.AreEqual(expectedComposed.ClearLf(), genComposed); + + var sb2 = new StringBuilder(); + builder.Generate(sb2, builder.GetModelsToGenerate().Skip(1).First()); + var genComposition = sb2.ToString(); + + var expectedComposition = @"//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v" + version + @" +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + // Mixin Content Type with alias ""composition1"" + /// composition1Name + public partial interface IComposition1 : IPublishedContent + { + /// compositionPropName + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + string CompositionProp { get; } + } + + /// composition1Name + [PublishedModel(""composition1"")] + public partial class Composition1 : PublishedContentModel, IComposition1 + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + public new const string ModelTypeAlias = ""composition1""; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public Composition1(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// compositionPropName + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType(""compositionProp"")] + public virtual string CompositionProp => GetCompositionProp(this, _publishedValueFallback); + + /// Static getter for compositionPropName + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static string GetCompositionProp(IComposition1 that, IPublishedValueFallback publishedValueFallback) => that.Value(publishedValueFallback, ""compositionProp""); + } +} +"; + + Console.WriteLine(genComposition); + Assert.AreEqual(expectedComposition.ClearLf(), genComposition); + } + [TestCase("int", typeof(int))] [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))] [TestCase("global::Umbraco.Cms.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded.BuilderTestsClass1", typeof(BuilderTestsClass1))] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index 57ab22640a..ffdea855ce 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -21,6 +21,7 @@ + @@ -33,6 +34,7 @@ + diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/CollectionBuilders/TreeCollectionBuilderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/CollectionBuilders/TreeCollectionBuilderTests.cs new file mode 100644 index 0000000000..5392d50ebd --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/CollectionBuilders/TreeCollectionBuilderTests.cs @@ -0,0 +1,36 @@ +using System.Linq; +using NUnit.Framework; +using Umbraco.Cms.Core.Trees; +using Umbraco.Cms.Web.BackOffice.Trees; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.CollectionBuilders +{ + public class TreeCollectionBuilderTests + { + [Test] + public void Adding_Tree_To_Collection_Builder() + { + var collectionBuilder = new TreeCollectionBuilder(); + var treeDefinition = new Tree(0, "test", "test", "test", "test", TreeUse.Main, typeof(LanguageTreeController), false); + + collectionBuilder.AddTree(treeDefinition); + var collection = collectionBuilder.CreateCollection(null); + + Assert.AreEqual(1, collection.Count); + Assert.AreEqual(treeDefinition, collection.FirstOrDefault()); + } + + [Test] + public void Remove_Tree_From_Collection_Builder() + { + var collectionBuilder = new TreeCollectionBuilder(); + var treeDefinition = new Tree(0, "test", "test", "test", "test", TreeUse.Main, typeof(LanguageTreeController), false); + + collectionBuilder.AddTree(treeDefinition); + collectionBuilder.RemoveTreeController(); + var collection = collectionBuilder.CreateCollection(null); + + Assert.AreEqual(0, collection.Count); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 42b9eb2ddc..2d703b8d0f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -261,7 +261,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers Mock.Of(), Mock.Of(), Mock.Of(), - Mock.Of() + Mock.Of(), + Mock.Of() ); return controller; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs index 2da13a4bed..da5175f272 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs @@ -121,6 +121,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers Mock.Get(umbracoMembersUserManager) .Setup(x => x.ValidatePasswordAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); + Mock.Get(umbracoMembersUserManager) + .Setup(x => x.GetRolesAsync(It.IsAny())) + .ReturnsAsync(() => Array.Empty()); Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); Mock.Get(memberService).SetupSequence( @@ -162,6 +165,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers Mock.Get(umbracoMembersUserManager) .Setup(x => x.ValidatePasswordAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); + Mock.Get(umbracoMembersUserManager) + .Setup(x => x.GetRolesAsync(It.IsAny())) + .ReturnsAsync(() => Array.Empty()); Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); Mock.Get(memberService).SetupSequence( @@ -209,6 +215,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers Mock.Get(umbracoMembersUserManager) .Setup(x => x.UpdateAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); + Mock.Get(umbracoMembersUserManager) + .Setup(x => x.GetRolesAsync(It.IsAny())) + .ReturnsAsync(() => Array.Empty()); Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(globalSettings); @@ -392,6 +401,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers Mock.Get(umbracoMembersUserManager) .Setup(x => x.AddToRolesAsync(It.IsAny(), It.IsAny>())) .ReturnsAsync(() => IdentityResult.Success); + Mock.Get(umbracoMembersUserManager) + .Setup(x => x.GetRolesAsync(It.IsAny())) + .ReturnsAsync(() => Array.Empty()); + Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny())).Returns(() => member); @@ -416,6 +429,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers .Verify(u => u.GetRolesAsync(membersIdentityUser)); Mock.Get(umbracoMembersUserManager) .Verify(u => u.AddToRolesAsync(membersIdentityUser, new[] { roleName })); + Mock.Get(umbracoMembersUserManager) + .Verify(x => x.GetRolesAsync(It.IsAny())); Mock.Get(memberService) .Verify(m => m.Save(It.IsAny())); AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value); @@ -502,7 +517,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers new Mock().Object, mockShortStringHelper, globalSettings, - new Mock().Object) + new Mock().Object, + new Mock>().Object) }); var scopeProvider = Mock.Of(x => x.CreateScope( It.IsAny(), diff --git a/tests/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/tests/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs deleted file mode 100644 index bec5bbbb92..0000000000 --- a/tests/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.Linq; -using System.Xml; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; - -namespace Umbraco.Tests.Cache.PublishedCache -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class PublishContentCacheTests : BaseWebTest - { - private FakeHttpContextFactory _httpContextFactory; - private IUmbracoContext _umbracoContext; - private IPublishedContentCache _cache; - private XmlDocument _xml; - - private string GetXml() - { - return @" - - -]> - - - - - - - - - - - - - - -"; - } - - protected override void Initialize() - { - base.Initialize(); - - _httpContextFactory = new FakeHttpContextFactory("~/Home"); - - var globalSettings = new GlobalSettings(); - var umbracoContextAccessor = Factory.GetRequiredService(); - - _xml = new XmlDocument(); - _xml.LoadXml(GetXml()); - var xmlStore = new XmlStore(() => _xml, null, null, null, HostingEnvironment); - var appCache = new DictionaryAppCache(); - var domainCache = new DomainCache(Mock.Of(), DefaultCultureAccessor); - var publishedShapshot = new PublishedSnapshot( - new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, ContentTypesCache, null, VariationContextAccessor, null), - new PublishedMediaCache(xmlStore, Mock.Of(), Mock.Of(), appCache, ContentTypesCache, Factory.GetRequiredService(), umbracoContextAccessor, VariationContextAccessor), - new PublishedMemberCache(ContentTypesCache, VariationContextAccessor), - domainCache); - var publishedSnapshotService = new Mock(); - publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedShapshot); - - var httpContext = _httpContextFactory.HttpContext; - var httpContextAccessor = TestHelper.GetHttpContextAccessor(httpContext); - _umbracoContext = new UmbracoContext( - httpContextAccessor, - publishedSnapshotService.Object, - Mock.Of(), - globalSettings, - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - - _cache = _umbracoContext.Content; - } - - [Test] - public void Has_Content() - { - Assert.IsTrue(_cache.HasContent()); - } - - - [Test] - public void Get_Root_Docs() - { - var result = _cache.GetAtRoot(); - Assert.AreEqual(2, result.Count()); - Assert.AreEqual(1046, result.ElementAt(0).Id); - Assert.AreEqual(1172, result.ElementAt(1).Id); - } - - - [TestCase("/", 1046)] - [TestCase("/home", 1046)] - [TestCase("/Home", 1046)] //test different cases - [TestCase("/home/sub1", 1173)] - [TestCase("/Home/sub1", 1173)] - [TestCase("/home/Sub1", 1173)] //test different cases - [TestCase("/home/Sub'Apostrophe", 1177)] - public void Get_Node_By_Route(string route, int nodeId) - { - var result = _cache.GetByRoute(route, false); - Assert.IsNotNull(result); - Assert.AreEqual(nodeId, result.Id); - } - - - - [TestCase("/", 1046)] - [TestCase("/sub1", 1173)] - [TestCase("/Sub1", 1173)] - public void Get_Node_By_Route_Hiding_Top_Level_Nodes(string route, int nodeId) - { - var result = _cache.GetByRoute(route, true); - Assert.IsNotNull(result); - Assert.AreEqual(nodeId, result.Id); - } - } -} diff --git a/tests/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/tests/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs deleted file mode 100644 index 416035e0e4..0000000000 --- a/tests/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ /dev/null @@ -1,413 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; -using Examine; -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.PublishedContent; -using Umbraco.Tests.TestHelpers; -using Constants = Umbraco.Cms.Core.Constants; -using Current = Umbraco.Web.Composing.Current; - -namespace Umbraco.Tests.Cache.PublishedCache -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class PublishMediaCacheTests : BaseWebTest - { - private Dictionary _mediaTypes; - private int _testWriterAndCreatorId; - - private IUmbracoContextAccessor _umbracoContextAccessor; - protected override void Compose() - { - base.Compose(); - - Builder.WithCollectionBuilder() - .Clear() - .Append(); - - _umbracoContextAccessor = Current.UmbracoContextAccessor; - } - - protected override void Initialize() - { - base.Initialize(); - var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "myType", new PublishedPropertyType[] { }); - var image = new AutoPublishedContentType(Guid.NewGuid(), 23, "Image", new PublishedPropertyType[] { }); - var testMediaType = new AutoPublishedContentType(Guid.NewGuid(), 24, "TestMediaType", new PublishedPropertyType[] { }); - _mediaTypes = new Dictionary - { - { type.Alias, type }, - { image.Alias, image }, - { testMediaType.Alias, testMediaType } - }; - ContentTypesCache.GetPublishedContentTypeByAlias = alias => _mediaTypes[alias]; - - _testWriterAndCreatorId = ServiceContext.UserService.CreateUserWithIdentity("Shannon", "test").Id; - } - - private IMediaType MakeNewMediaType(IUser user, string text, int parentId = -1) - { - var mt = new MediaType(ShortStringHelper, parentId) { Name = text, Alias = text, Thumbnail = "icon-folder", Icon = "icon-folder" }; - ServiceContext.MediaTypeService.Save(mt); - return mt; - } - - private IMedia MakeNewMedia(string name, IMediaType mediaType, IUser user, int parentId) - { - var m = ServiceContext.MediaService.CreateMediaWithIdentity(name, parentId, mediaType.Alias); - return m; - } - - //NOTE: This is "Without_Examine" too - [Test] - public void Get_Root_Docs() - { - var user = ServiceContext.UserService.GetUserById(0); - var mType = MakeNewMediaType(user, "TestMediaType"); - var mRoot1 = MakeNewMedia("MediaRoot1", mType, user, -1); - var mRoot2 = MakeNewMedia("MediaRoot2", mType, user, -1); - var mChild1 = MakeNewMedia("Child1", mType, user, mRoot1.Id); - var mChild2 = MakeNewMedia("Child2", mType, user, mRoot2.Id); - - var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(new XmlStore((XmlDocument) null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService(), VariationContextAccessor); - var roots = cache.GetAtRoot(); - Assert.AreEqual(2, roots.Count()); - Assert.IsTrue(roots.Select(x => x.Id).ContainsAll(new[] {mRoot1.Id, mRoot2.Id})); - - } - - [Test] - public void Get_Item_Without_Examine() - { - var user = ServiceContext.UserService.GetUserById(0); - var mType = MakeNewMediaType(user, "TestMediaType"); - _mediaTypes[mType.Alias] = new PublishedContentType(mType, null); - var mRoot = MakeNewMedia("MediaRoot", mType, user, -1); - var mChild1 = MakeNewMedia("Child1", mType, user, mRoot.Id); - - //var publishedMedia = PublishedMediaTests.GetNode(mRoot.Id, GetUmbracoContext("/test", 1234)); - var umbracoContext = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService(), VariationContextAccessor); - var publishedMedia = cache.GetById(mRoot.Id); - Assert.IsNotNull(publishedMedia); - - Assert.AreEqual(mRoot.Id, publishedMedia.Id); - Assert.AreEqual(mRoot.CreateDate.ToString("dd/MM/yyyy HH:mm:ss"), publishedMedia.CreateDate.ToString("dd/MM/yyyy HH:mm:ss")); - Assert.AreEqual(mRoot.CreatorId, publishedMedia.CreatorId); - //Assert.AreEqual(mRoot.User.Name, publishedMedia.CreatorName); - Assert.AreEqual(mRoot.ContentType.Alias, publishedMedia.ContentType.Alias); - Assert.AreEqual(mRoot.ContentType.Id, publishedMedia.ContentType.Id); - Assert.AreEqual(mRoot.Level, publishedMedia.Level); - Assert.AreEqual(mRoot.Name, publishedMedia.Name); - Assert.AreEqual(mRoot.Path, publishedMedia.Path); - Assert.AreEqual(mRoot.SortOrder, publishedMedia.SortOrder); - Assert.IsNull(publishedMedia.Parent); - } - - [TestCase("id")] - [TestCase("__NodeId")] - public void DictionaryDocument_Id_Keys(string key) - { - var dicDoc = GetDictionaryDocument(idKey: key); - DoAssert(dicDoc); - } - - [TestCase("template")] - [TestCase("templateId")] - public void DictionaryDocument_Template_Keys(string key) - { - var dicDoc = GetDictionaryDocument(templateKey: key); - DoAssert(dicDoc); - } - - [TestCase("nodeName")] - public void DictionaryDocument_NodeName_Keys(string key) - { - var dicDoc = GetDictionaryDocument(nodeNameKey: key); - DoAssert(dicDoc); - } - - [TestCase("nodeTypeAlias")] - [TestCase("__NodeTypeAlias")] - public void DictionaryDocument_NodeTypeAlias_Keys(string key) - { - var dicDoc = GetDictionaryDocument(nodeTypeAliasKey: key); - DoAssert(dicDoc); - } - - [TestCase("path")] - [TestCase("__Path")] - public void DictionaryDocument_Path_Keys(string key) - { - var dicDoc = GetDictionaryDocument(pathKey: key); - DoAssert(dicDoc); - } - - [Test] - public void DictionaryDocument_Key() - { - var key = Guid.NewGuid(); - var dicDoc = GetDictionaryDocument(keyVal: key); - DoAssert(dicDoc, keyVal: key); - } - - [Test] - public void DictionaryDocument_Get_Children() - { - var child1 = GetDictionaryDocument(idVal: 222333); - var child2 = GetDictionaryDocument(idVal: 444555); - - var dicDoc = GetDictionaryDocument(children: new List() - { - child1, child2 - }); - - Assert.AreEqual(2, dicDoc.Children.Count()); - Assert.AreEqual(222333, dicDoc.Children.ElementAt(0).Id); - Assert.AreEqual(444555, dicDoc.Children.ElementAt(1).Id); - } - - [Test] - public void Convert_From_Search_Result() - { - var ctx = GetUmbracoContext("/test"); - var key = Guid.NewGuid(); - - var fields = new Dictionary - { - {"__IndexType", "media"}, - {"__NodeId", "1234"}, - {"__NodeTypeAlias", Constants.Conventions.MediaTypes.Image}, - {"__Path", "-1,1234"}, - {"__nodeName", "Test"}, - {"id", "1234"}, - {"key", key.ToString()}, - {"urlName", "/media/test.jpg"}, - {"nodeType", "0"}, - {"sortOrder", "0"}, - {"level", "2"}, - {"nodeName", "Test"}, - {"nodeTypeAlias", Constants.Conventions.MediaTypes.Image}, - {"parentID", "-1"}, - {"path", "-1,1234"}, - {"updateDate", DateTime.Parse("2012-07-16T10:34:09").Ticks.ToString()}, - {"createDate", DateTime.Parse("2012-07-17T10:34:09").Ticks.ToString()}, - {"creatorID", _testWriterAndCreatorId.ToString()}, - {"creatorName", "Shannon"} - }; - - var result = new SearchResult("1234", 1, () => fields.ToDictionary(x => x.Key, x => new List { x.Value })); - - var store = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService(), VariationContextAccessor); - var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result)); - - DoAssert(doc, 1234, key, null, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2); - Assert.AreEqual(null, doc.Parent); - } - - [Test] - public void Convert_From_XPath_Navigator() - { - var ctx = GetUmbracoContext("/test"); - var key = Guid.NewGuid(); - - var xmlDoc = GetMediaXml(); - ((XmlElement)xmlDoc.DocumentElement.FirstChild).SetAttribute("key", key.ToString()); - var navigator = xmlDoc.SelectSingleNode("/root/Image").CreateNavigator(); - var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService(),VariationContextAccessor); - var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true)); - - DoAssert(doc, 2000, key, null, 2, "image1", "Image", 23, "Shannon", "Shannon", "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); - Assert.AreEqual(null, doc.Parent); - Assert.AreEqual(2, doc.Children.Count()); - Assert.AreEqual(2001, doc.Children.ElementAt(0).Id); - Assert.AreEqual(2002, doc.Children.ElementAt(1).Id); - } - - private XmlDocument GetMediaXml() - { - var xml = @" - - - - -]> - - - - - - - - - - -"; - xml = xml.Replace("[WriterId]", _testWriterAndCreatorId.ToString()); - xml = xml.Replace("[CreatorId]", _testWriterAndCreatorId.ToString()); - - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(xml); - return xmlDoc; - } - - private Dictionary GetDictionary( - int id, - Guid key, - int parentId, - string idKey, - string templateKey, - string nodeNameKey, - string nodeTypeAliasKey, - string pathKey) - { - return new Dictionary() - { - {idKey, id.ToString()}, - {"key", key.ToString()}, - {templateKey, "0"}, - {"sortOrder", "44"}, - {nodeNameKey, "Testing"}, - {"urlName", "testing"}, - {nodeTypeAliasKey, "myType"}, - {"nodeType", "22"}, - {"writerID", _testWriterAndCreatorId.ToString()}, - {"creatorID", _testWriterAndCreatorId.ToString()}, - {pathKey, "1,2,3,4,5"}, - {"createDate", "2012-01-02"}, - {"updateDate", "2012-01-02"}, - {"level", "3"}, - {"parentID", parentId.ToString()} - }; - } - - private DictionaryPublishedContent GetDictionaryDocument( - string idKey = "id", - string templateKey = "template", - string nodeNameKey = "nodeName", - string nodeTypeAliasKey = "nodeTypeAlias", - string pathKey = "path", - int idVal = 1234, - Guid keyVal = default(Guid), - int parentIdVal = 321, - IEnumerable children = null) - { - if (children == null) - children = new List(); - var dicDoc = new DictionaryPublishedContent( - //the dictionary - GetDictionary(idVal, keyVal, parentIdVal, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey), - //callback to get the parent - d => new DictionaryPublishedContent( - // the dictionary - GetDictionary(parentIdVal, default(Guid), -1, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey), - // callback to get the parent: there is no parent - a => null, - // callback to get the children: we're not going to test this so ignore - (dd, n) => new List(), - // callback to get a property - (dd, a) => dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(a)), - null, // cache provider - VariationContextAccessor, - ContentTypesCache, - // no xpath - null, - // not from examine - false), - //callback to get the children - (dd, n) => children, - // callback to get a property - (dd, a) => dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(a)), - null, // cache provider - VariationContextAccessor, - ContentTypesCache, - // no xpath - null, - // not from examine - false); - return dicDoc; - } - - private void DoAssert( - DictionaryPublishedContent dicDoc, - int idVal = 1234, - Guid keyVal = default(Guid), - int? templateIdVal = null, - int sortOrderVal = 44, - string urlNameVal = "testing", - string nodeTypeAliasVal = "myType", - int nodeTypeIdVal = 22, - string writerNameVal = "Shannon", - string creatorNameVal = "Shannon", - string pathVal = "1,2,3,4,5", - DateTime? createDateVal = null, - DateTime? updateDateVal = null, - int levelVal = 3, - int parentIdVal = 321) - { - if (!createDateVal.HasValue) - createDateVal = DateTime.Parse("2012-01-02"); - if (!updateDateVal.HasValue) - updateDateVal = DateTime.Parse("2012-01-02"); - - DoAssert((IPublishedContent)dicDoc, idVal, keyVal, templateIdVal, sortOrderVal, urlNameVal, nodeTypeAliasVal, nodeTypeIdVal, writerNameVal, - creatorNameVal, pathVal, createDateVal, updateDateVal, levelVal); - - //now validate the parentId that has been parsed, this doesn't exist on the IPublishedContent - Assert.AreEqual(parentIdVal, dicDoc.ParentId); - } - - private void DoAssert( - IPublishedContent doc, - int idVal = 1234, - Guid keyVal = default(Guid), - int? templateIdVal = null, - int sortOrderVal = 44, - string urlNameVal = "testing", - string nodeTypeAliasVal = "myType", - int nodeTypeIdVal = 22, - string writerNameVal = "Shannon", - string creatorNameVal = "Shannon", - string pathVal = "1,2,3,4,5", - DateTime? createDateVal = null, - DateTime? updateDateVal = null, - int levelVal = 3) - { - if (!createDateVal.HasValue) - createDateVal = DateTime.Parse("2012-01-02"); - if (!updateDateVal.HasValue) - updateDateVal = DateTime.Parse("2012-01-02"); - - Assert.AreEqual(idVal, doc.Id); - Assert.AreEqual(keyVal, doc.Key); - Assert.AreEqual(templateIdVal, doc.TemplateId); - Assert.AreEqual(sortOrderVal, doc.SortOrder); - Assert.AreEqual(urlNameVal, doc.UrlSegment); - Assert.AreEqual(nodeTypeAliasVal, doc.ContentType.Alias); - Assert.AreEqual(nodeTypeIdVal, doc.ContentType.Id); - Assert.AreEqual(writerNameVal, doc.GetWriterName(ServiceContext.UserService)); - Assert.AreEqual(creatorNameVal, doc.GetCreatorName(ServiceContext.UserService)); - Assert.AreEqual(_testWriterAndCreatorId, doc.WriterId); - Assert.AreEqual(_testWriterAndCreatorId, doc.CreatorId); - Assert.AreEqual(pathVal, doc.Path); - Assert.AreEqual(createDateVal.Value, doc.CreateDate); - Assert.AreEqual(updateDateVal.Value, doc.UpdateDate); - Assert.AreEqual(levelVal, doc.Level); - } - } -} diff --git a/tests/Umbraco.Tests/Issues/U9560.cs b/tests/Umbraco.Tests/Issues/U9560.cs deleted file mode 100644 index 8687e6e07c..0000000000 --- a/tests/Umbraco.Tests/Issues/U9560.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Issues -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)] - public class U9560 : TestWithDatabaseBase - { - [Test] - public void Test() - { - // create a content type and some properties - var contentType = new ContentType(ShortStringHelper, -1); - contentType.Alias = "test"; - contentType.Name = "test"; - var propertyType = new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext, "prop") { Name = "Prop", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }; - contentType.PropertyTypeCollection.Add(propertyType); - ServiceContext.ContentTypeService.Save(contentType); - - var aliasName = string.Empty; - - // read fields, same as what we do with PetaPoco Fetch - using (var db = Factory.GetRequiredService().CreateDatabase()) - { - db.OpenSharedConnection(); - try - { - var conn = db.Connection; - var cmd = conn.CreateCommand(); - cmd.CommandText = "SELECT mandatory, dataTypeId, propertyTypeGroupId, contentTypeId, sortOrder, alias, name, validationRegExp, description from cmsPropertyType where id=" + propertyType.Id; - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - for (var i = 0; i < reader.FieldCount; i++) - Console.WriteLine(reader.GetName(i)); - aliasName = reader.GetName(5); - } - } - } - finally - { - db.CloseSharedConnection(); - } - } - - // note that although the query is for 'alias' the field is named 'Alias' - Assert.AreEqual("Alias", aliasName); - - // try differently - using (var db = Factory.GetRequiredService().CreateDatabase()) - { - db.OpenSharedConnection(); - try - { - var conn = db.Connection; - var cmd = conn.CreateCommand(); - cmd.CommandText = "SELECT mandatory, dataTypeId, propertyTypeGroupId, contentTypeId, sortOrder, alias as alias, name, validationRegExp, description from cmsPropertyType where id=" + propertyType.Id; - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - for (var i = 0; i < reader.FieldCount; i++) - Console.WriteLine(reader.GetName(i)); - aliasName = reader.GetName(5); - } - } - } - finally - { - db.CloseSharedConnection(); - } - } - - // and now it is OK - Assert.AreEqual("alias", aliasName); - - //// get the legacy content type - //var legacyContentType = new umbraco.cms.businesslogic.ContentType(contentType.Id); - //Assert.AreEqual("test", legacyContentType.Alias); - - //// get the legacy properties - //var legacyProperties = legacyContentType.PropertyTypes; - - //// without the fix, due to some (swallowed) inner exception, we have no properties - ////Assert.IsNull(legacyProperties); - - //// thanks to the fix, it works - //Assert.IsNotNull(legacyProperties); - //Assert.AreEqual(1, legacyProperties.Count); - //Assert.AreEqual("prop", legacyProperties[0].Alias); - } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/ContentXmlDto.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/ContentXmlDto.cs deleted file mode 100644 index d7d82bdd6d..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/ContentXmlDto.cs +++ /dev/null @@ -1,24 +0,0 @@ -using NPoco; -using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; -using Umbraco.Cms.Infrastructure.Persistence.Dtos; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - [TableName("cmsContentXml")] - [PrimaryKey("nodeId", AutoIncrement = false)] - [ExplicitColumns] - internal class ContentXmlDto - { - [Column("nodeId")] - [PrimaryKeyColumn(AutoIncrement = false)] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] - public int NodeId { get; set; } - - [Column("xml")] - [SpecialDbType(SpecialDbTypes.NTEXT)] - public string Xml { get; set; } - - [Column("rv")] - public long Rv { get; set; } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs deleted file mode 100644 index b6f8865748..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ /dev/null @@ -1,221 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Xml.XPath; -using Examine; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Extensions; -using Umbraco.Web.Composing; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - /// - /// An IPublishedContent that is represented all by a dictionary. - /// - /// - /// This is a helper class and definitely not intended for public use, it expects that all of the values required - /// to create an IPublishedContent exist in the dictionary by specific aliases. - /// - internal class DictionaryPublishedContent : PublishedContentBase - { - // note: I'm not sure this class fully complies with IPublishedContent rules especially - // I'm not sure that _properties contains all properties including those without a value, - // neither that GetProperty will return a property without a value vs. null... @zpqrtbnk - - // List of properties that will appear in the XML and do not match - // anything in the ContentType, so they must be ignored. - private static readonly string[] IgnoredKeys = { "version", "isDoc" }; - - public DictionaryPublishedContent( - IReadOnlyDictionary valueDictionary, - Func getParent, - Func> getChildren, - Func getProperty, - IAppCache appCache, - IVariationContextAccessor variationContextAccessor, - PublishedContentTypeCache contentTypeCache, - XPathNavigator nav, - bool fromExamine):base(variationContextAccessor) - { - if (valueDictionary == null) throw new ArgumentNullException(nameof(valueDictionary)); - if (getParent == null) throw new ArgumentNullException(nameof(getParent)); - if (getProperty == null) throw new ArgumentNullException(nameof(getProperty)); - - _getParent = new Lazy(() => getParent(ParentId)); - _getChildren = new Lazy>(() => getChildren(Id, nav)); - _getProperty = getProperty; - _appCache = appCache; - - LoadedFromExamine = fromExamine; - - ValidateAndSetProperty(valueDictionary, val => _id = Int32.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! - ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key", "__key", "__Key"); - //ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); - ValidateAndSetProperty(valueDictionary, val => _sortOrder = Int32.Parse(val), "sortOrder"); - ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName"); - ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); - ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", ExamineFieldNames.ItemTypeFieldName); - ValidateAndSetProperty(valueDictionary, val => _documentTypeId = Int32.Parse(val), "nodeType"); - //ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID"); - ValidateAndSetProperty(valueDictionary, val => _creatorId = Int32.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132 - ValidateAndSetProperty(valueDictionary, val => _path = val, "path", "__Path"); - ValidateAndSetProperty(valueDictionary, val => _createDate = ParseDateTimeValue(val), "createDate"); - ValidateAndSetProperty(valueDictionary, val => _updateDate = ParseDateTimeValue(val), "updateDate"); - ValidateAndSetProperty(valueDictionary, val => _level = Int32.Parse(val), "level"); - ValidateAndSetProperty(valueDictionary, val => - { - int pId; - ParentId = -1; - if (Int32.TryParse(val, out pId)) - { - ParentId = pId; - } - }, "parentID"); - - _contentType = contentTypeCache.Get(PublishedItemType.Media, _documentTypeAlias); - _properties = new Collection(); - - //handle content type properties - //make sure we create them even if there's no value - foreach (var propertyType in _contentType.PropertyTypes) - { - var alias = propertyType.Alias; - _keysAdded.Add(alias); - string value; - const bool isPreviewing = false; // false :: never preview a media - var property = valueDictionary.TryGetValue(alias, out value) == false || value == null - ? new XmlPublishedProperty(propertyType, this, isPreviewing) - : new XmlPublishedProperty(propertyType, this, isPreviewing, value); - _properties.Add(property); - } - - //loop through remaining values that haven't been applied - foreach (var i in valueDictionary.Where(x => - _keysAdded.Contains(x.Key) == false // not already processed - && IgnoredKeys.Contains(x.Key) == false)) // not ignorable - { - if (i.Key.InvariantStartsWith("__")) - { - // no type for that one, dunno how to convert, drop it - //IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); - //_properties.Add(property); - } - else - { - // this is a property that does not correspond to anything, ignore and log - Current.Logger.LogWarning("Dropping property '{PropertyKey}' because it does not belong to the content type.", i.Key); - } - } - } - - private DateTime ParseDateTimeValue(string val) - { - if (LoadedFromExamine == false) - return DateTime.Parse(val); - - //we need to parse the date time using Lucene converters - var ticks = Int64.Parse(val); - return new DateTime(ticks); - } - - /// - /// Flag to get/set if this was loaded from examine cache - /// - internal bool LoadedFromExamine { get; } - - //private readonly Func _getParent; - private readonly Lazy _getParent; - //private readonly Func> _getChildren; - private readonly Lazy> _getChildren; - private readonly Func _getProperty; - private readonly IAppCache _appCache; - - /// - /// Returns 'Media' as the item type - /// - public override PublishedItemType ItemType => PublishedItemType.Media; - - public override IPublishedContent Parent => _getParent.Value; - - public int ParentId { get; private set; } - - public override int Id => _id; - - public override Guid Key => _key; - - public override int? TemplateId => null; - - public override int SortOrder => _sortOrder; - - public override string Name => _name; - - private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); - public override IReadOnlyDictionary Cultures => NoCultures.Value; - - public override string UrlSegment => _urlName; - - public override int WriterId => _creatorId; - - public override int CreatorId => _creatorId; - - public override string Path => _path; - - public override DateTime CreateDate => _createDate; - - public override DateTime UpdateDate => _updateDate; - - public override int Level => _level; - - public override bool IsDraft(string culture = null) => false; - - public override bool IsPublished(string culture = null) => true; - - public override IEnumerable Properties => _properties; - - public override IEnumerable Children => _getChildren.Value; - - public override IEnumerable ChildrenForAllCultures => Children; - - public override IPublishedProperty GetProperty(string alias) - { - return _getProperty(this, alias); - } - - public override IPublishedContentType ContentType => _contentType; - - private readonly List _keysAdded = new List(); - private int _id; - private Guid _key; - //private int _templateId; - private int _sortOrder; - private string _name; - private string _urlName; - private string _documentTypeAlias; - private int _documentTypeId; - //private int _writerId; - private int _creatorId; - private string _path; - private DateTime _createDate; - private DateTime _updateDate; - //private Guid _version; - private int _level; - private readonly ICollection _properties; - private readonly IPublishedContentType _contentType; - - private void ValidateAndSetProperty(IReadOnlyDictionary valueDictionary, Action setProperty, params string[] potentialKeys) - { - var key = potentialKeys.FirstOrDefault(x => valueDictionary.ContainsKey(x) && valueDictionary[x] != null); - if (key == null) - { - throw new FormatException("The valueDictionary is not formatted correctly and is missing any of the '" + String.Join(",", potentialKeys) + "' elements"); - } - - setProperty(valueDictionary[key]); - _keysAdded.Add(key); - } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs deleted file mode 100644 index 0ff61e7e45..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - internal class DomainCache : IDomainCache - { - private readonly IDomainService _domainService; - - public DomainCache(IDomainService domainService, IDefaultCultureAccessor defaultCultureAccessor) - { - _domainService = domainService; - DefaultCulture = defaultCultureAccessor.DefaultCulture; - } - - /// - public IEnumerable GetAll(bool includeWildcards) => _domainService.GetAll(includeWildcards) - .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) - .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, x.LanguageIsoCode, x.IsWildcard)); - - /// - public IEnumerable GetAssigned(int documentId, bool includeWildcards = false) => _domainService.GetAssignedDomains(documentId, includeWildcards) - .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) - .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, x.LanguageIsoCode, x.IsWildcard)); - - /// - public bool HasAssigned(int documentId, bool includeWildcards = false) - => documentId > 0 && GetAssigned(documentId, includeWildcards).Any(); - - /// - public string DefaultCulture { get; } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/BackgroundTaskRunner.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/BackgroundTaskRunner.cs deleted file mode 100644 index 98aa1cf8d8..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/BackgroundTaskRunner.cs +++ /dev/null @@ -1,863 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Runtime; - -namespace Umbraco.Web.Scheduling -{ - /// - /// Manages a queue of tasks and runs them in the background. - /// - /// This class exists for logging purposes - the one you want to use is BackgroundTaskRunner{T}. - public abstract class BackgroundTaskRunner - { - /// - /// Represents a MainDom hook. - /// - public class MainDomHook - { - /// - /// Initializes a new instance of the class. - /// - /// The object. - /// A method to execute when hooking into the main domain. - /// A method to execute when the main domain releases. - public MainDomHook(IMainDom mainDom, Action install, Action release) - { - MainDom = mainDom; - Install = install; - Release = release; - } - - /// - /// Gets the object. - /// - public IMainDom MainDom { get; } - - /// - /// Gets the method to execute when hooking into the main domain. - /// - public Action Install { get; } - - /// - /// Gets the method to execute when the main domain releases. - /// - public Action Release { get; } - - internal bool Register() - { - if (MainDom != null) - { - return MainDom.Register(Install, Release); - } - - // tests - Install?.Invoke(); - return true; - } - } - } - - /// - /// Manages a queue of tasks of type and runs them in the background. - /// - /// The type of the managed tasks. - /// The task runner is web-aware and will ensure that it shuts down correctly when the AppDomain - /// shuts down (ie is unloaded). - public class BackgroundTaskRunner : BackgroundTaskRunner, IBackgroundTaskRunner - where T : class, IBackgroundTask - { - // do not remove this comment! - // - // if you plan to do anything on this class, first go and read - // http://blog.stephencleary.com/2012/12/dont-block-in-asynchronous-code.html - // http://stackoverflow.com/questions/19481964/calling-taskcompletionsource-setresult-in-a-non-blocking-manner - // http://stackoverflow.com/questions/21225361/is-there-anything-like-asynchronous-blockingcollectiont - // and more, and more, and more - // and remember: async is hard - - private readonly string _logPrefix; - private readonly BackgroundTaskRunnerOptions _options; - private readonly ILogger> _logger; - private readonly IApplicationShutdownRegistry _applicationShutdownRegistry; - private readonly object _locker = new object(); - - private readonly BufferBlock _tasks = new BufferBlock(new DataflowBlockOptions()); - - // in various places we are testing these vars outside a lock, so make them volatile - private volatile bool _isRunning; // is running - private volatile bool _completed; // does not accept tasks anymore, may still be running - - private Task _runningTask; // the threading task that is currently executing background tasks - private CancellationTokenSource _shutdownTokenSource; // used to cancel everything and shutdown - private CancellationTokenSource _cancelTokenSource; // used to cancel the current task - private CancellationToken _shutdownToken; - - private bool _terminating; // ensures we raise that event only once - private bool _terminated; // remember we've terminated - private readonly TaskCompletionSource _terminatedSource = new TaskCompletionSource(); // enable awaiting termination - - /// - /// Initializes a new instance of the class. - /// - /// A logger. - /// The application shutdown registry - /// An optional main domain hook. - public BackgroundTaskRunner(ILogger> logger, IApplicationShutdownRegistry applicationShutdownRegistry, MainDomHook hook = null) - : this(typeof(T).FullName, new BackgroundTaskRunnerOptions(), logger, applicationShutdownRegistry, hook) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the runner. - /// A logger. - /// The application shutdown registry - /// An optional main domain hook. - public BackgroundTaskRunner(string name, ILogger> logger, IApplicationShutdownRegistry applicationShutdownRegistry, MainDomHook hook = null) - : this(name, new BackgroundTaskRunnerOptions(), logger, applicationShutdownRegistry, hook) - { } - - /// - /// Initializes a new instance of the class with a set of options. - /// - /// The set of options. - /// A logger. - /// The application shutdown registry - /// An optional main domain hook. - public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger> logger, IApplicationShutdownRegistry applicationShutdownRegistry, MainDomHook hook = null) - : this(typeof(T).FullName, options, logger, applicationShutdownRegistry, hook) - { } - - /// - /// Initializes a new instance of the class with a set of options. - /// - /// The name of the runner. - /// The set of options. - /// A logger. - /// The application shutdown registry - /// An optional main domain hook. - public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger> logger, IApplicationShutdownRegistry applicationShutdownRegistry, MainDomHook hook = null) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _applicationShutdownRegistry = applicationShutdownRegistry; - _logPrefix = "[" + name + "] "; - - if (options.Hosted) - _applicationShutdownRegistry.RegisterObject(this); - - if (hook != null) - _completed = _terminated = hook.Register() == false; - - if (options.AutoStart && _terminated == false) - StartUp(); - } - - /// - /// Gets the number of tasks in the queue. - /// - public int TaskCount => _tasks.Count; - - /// - /// Gets a value indicating whether a threading task is currently running. - /// - public bool IsRunning => _isRunning; - - /// - /// Gets a value indicating whether the runner has completed and cannot accept tasks anymore. - /// - public bool IsCompleted => _completed; - - /// - /// Gets the running threading task as an immutable awaitable. - /// - /// There is no running task. - /// - /// Unless the AutoStart option is true, there will be no current threading task until - /// a background task is added to the queue, and there will be no current threading task - /// when the queue is empty. In which case this method returns null. - /// The returned value can be awaited and that is all (eg no continuation). - /// - internal ThreadingTaskImmutable CurrentThreadingTask - { - get - { - lock (_locker) - { - return _runningTask == null ? null : new ThreadingTaskImmutable(_runningTask); - } - } - } - - /// - /// Gets an awaitable used to await the runner running operation. - /// - /// An awaitable instance. - /// Used to wait until the runner is no longer running (IsRunning == false), - /// though the runner could be started again afterwards by adding tasks to it. If - /// the runner is not running, returns a completed awaitable. - public ThreadingTaskImmutable StoppedAwaitable - { - get - { - lock (_locker) - { - var task = _runningTask ?? Task.CompletedTask; - return new ThreadingTaskImmutable(task); - } - } - } - - /// - /// Gets an awaitable object that can be used to await for the runner to terminate. - /// - /// An awaitable object. - /// - /// Used to wait until the runner has terminated. - /// - /// The only time the runner will be terminated is by the Hosting Environment when the application is being shutdown. - /// - /// - internal ThreadingTaskImmutable TerminatedAwaitable - { - get - { - lock (_locker) - { - return new ThreadingTaskImmutable(_terminatedSource.Task); - } - } - } - - /// - /// Adds a task to the queue. - /// - /// The task to add. - /// The task runner has completed. - public void Add(T task) - { - lock (_locker) - { - if (_completed) - throw new InvalidOperationException("The task runner has completed."); - - // add task - _logger.LogDebug("{LogPrefix} Task Added {TaskType}", _logPrefix , task.GetType().FullName); - _tasks.Post(task); - - // start - StartUpLocked(); - } - } - - /// - /// Tries to add a task to the queue. - /// - /// The task to add. - /// true if the task could be added to the queue; otherwise false. - /// Returns false if the runner is completed. - public bool TryAdd(T task) - { - lock (_locker) - { - if (_completed) - { - _logger.LogDebug("{LogPrefix} Task cannot be added {TaskType}, the task runner has already shutdown", _logPrefix, task.GetType().FullName); - return false; - } - - // add task - _logger.LogDebug("{LogPrefix} Task added {TaskType}", _logPrefix, task.GetType().FullName); - _tasks.Post(task); - - // start - StartUpLocked(); - - return true; - } - } - - /// - /// Cancels to current task, if any. - /// - /// Has no effect if the task runs synchronously, or does not want to cancel. - public void CancelCurrentBackgroundTask() - { - lock (_locker) - { - if (_completed) - throw new InvalidOperationException("The task runner has completed."); - _cancelTokenSource?.Cancel(); - } - } - - /// - /// Starts the tasks runner, if not already running. - /// - /// Is invoked each time a task is added, to ensure it is going to be processed. - /// The task runner has completed. - internal void StartUp() - { - if (_isRunning) return; - - lock (_locker) - { - if (_completed) - throw new InvalidOperationException("The task runner has completed."); - - StartUpLocked(); - } - } - - /// - /// Starts the tasks runner, if not already running. - /// - /// Must be invoked within lock(_locker) and with _isCompleted being false. - private void StartUpLocked() - { - // double check - if (_isRunning) return; - _isRunning = true; - - // create a new token source since this is a new process - _shutdownTokenSource = new CancellationTokenSource(); - _shutdownToken = _shutdownTokenSource.Token; - using (ExecutionContext.SuppressFlow()) // Do not flow AsyncLocal to the child thread - { - _runningTask = Task.Run(async () => await Pump().ConfigureAwait(false), _shutdownToken); - } - - _logger.LogDebug("{LogPrefix} Starting", _logPrefix); - } - - /// - /// Shuts the tasks runner down. - /// - /// True for force the runner to stop. - /// True to wait until the runner has stopped. - /// If is false, no more tasks can be queued but all queued tasks - /// will run. If it is true, then only the current one (if any) will end and no other task will run. - public void Shutdown(bool force, bool wait) - { - lock (_locker) - { - _completed = true; // do not accept new tasks - if (_isRunning == false) return; // done already - } - - var hasTasks = TaskCount > 0; - - if (!force && hasTasks) - { - _logger.LogInformation("{LogPrefix} Waiting for tasks to complete", _logPrefix); - } - - // complete the queue - // will stop waiting on the queue or on a latch - _tasks.Complete(); - - if (force) - { - // we must bring everything down, now - lock (_locker) - { - // was Complete() enough? - // if _tasks.Complete() ended up triggering code to stop the runner and reset - // the _isRunning flag, then there's no need to initiate a cancel on the cancelation token. - if (_isRunning == false) - return; - } - - // try to cancel running async tasks (cannot do much about sync tasks) - // break latched tasks - // stop processing the queue - _shutdownTokenSource?.Cancel(false); // false is the default - _shutdownTokenSource?.Dispose(); - _shutdownTokenSource = null; - } - - // tasks in the queue will be executed... - if (!wait) return; - - _runningTask?.Wait(CancellationToken.None); // wait for whatever is running to end... - } - - private async Task Pump() - { - while (true) - { - // get the next task - // if it returns null the runner is going down, stop - var bgTask = await GetNextBackgroundTask(_shutdownToken); - if (bgTask == null) return; - - // set a cancellation source so that the current task can be cancelled - // link from _shutdownToken so that we can use _cancelTokenSource for both - lock (_locker) - { - _cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_shutdownToken); - } - - try - { - // wait for latch should return the task - // if it returns null it's either that the task has been cancelled - // or the whole runner is going down - in both cases, continue, - // and GetNextBackgroundTask will take care of shutdowns - bgTask = await WaitForLatch(bgTask, _cancelTokenSource.Token); - - if (bgTask != null) - { - // executes & be safe - RunAsync should NOT throw but only raise an event, - // but... just make sure we never ever take everything down - try - { - await RunAsync(bgTask, _cancelTokenSource.Token).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "{LogPrefix} Task runner exception", _logPrefix); - } - } - } - finally - { - // done - lock (_locker) - { - // always dispose CancellationTokenSource when you are done using them - // https://lowleveldesign.org/2015/11/30/catch-in-cancellationtokensource/ - _cancelTokenSource.Dispose(); - _cancelTokenSource = null; - } - } - } - } - - // gets the next background task from the buffer - private async Task GetNextBackgroundTask(CancellationToken token) - { - while (true) - { - var task = await GetNextBackgroundTask2(token); - if (task != null) return task; - - lock (_locker) - { - // deal with race condition - if (_shutdownToken.IsCancellationRequested == false && TaskCount > 0) continue; - - // if we really have nothing to do, stop - _logger.LogDebug("{LogPrefix} Stopping", _logPrefix); - - if (_options.PreserveRunningTask == false) - _runningTask = null; - _isRunning = false; - _shutdownToken = CancellationToken.None; - } - - OnEvent(Stopped, "Stopped"); - return null; - } - } - - private async Task GetNextBackgroundTask2(CancellationToken shutdownToken) - { - // exit if canceling - if (shutdownToken.IsCancellationRequested) - return null; - - // if KeepAlive is false then don't block, exit if there is - // no task in the buffer - yes, there is a race condition, which - // we'll take care of - if (_options.KeepAlive == false && TaskCount == 0) - return null; - - try - { - // A Task that informs of whether and when more output is available. If, when the - // task completes, its Result is true, more output is available in the source (though another - // consumer of the source may retrieve the data). If it returns false, more output is not - // and will never be available, due to the source completing prior to output being available. - - var output = await _tasks.OutputAvailableAsync(shutdownToken); // block until output or cancelled - if (output == false) return null; - } - catch (TaskCanceledException) - { - return null; - } - - try - { - // A task that represents the asynchronous receive operation. When an item value is successfully - // received from the source, the returned task is completed and its Result returns the received - // value. If an item value cannot be retrieved because the source is empty and completed, an - // InvalidOperationException exception is thrown in the returned task. - - // the source cannot be empty *and* completed here - we know we have output - return await _tasks.ReceiveAsync(shutdownToken); - } - catch (TaskCanceledException) - { - return null; - } - } - - // if bgTask is not a latched background task, or if it is not latched, returns immediately - // else waits for the latch, taking care of completion and shutdown and whatnot - private async Task WaitForLatch(T bgTask, CancellationToken token) - { - var latched = bgTask as ILatchedBackgroundTask; - if (latched == null || latched.IsLatched == false) return bgTask; - - // support canceling awaiting - // read https://github.com/dotnet/corefx/issues/2704 - // read http://stackoverflow.com/questions/27238232/how-can-i-cancel-task-whenall - var tokenTaskSource = new TaskCompletionSource(); - token.Register(s => ((TaskCompletionSource)s).SetResult(true), tokenTaskSource); - - // returns the task that completed - // - latched.Latch completes when the latch releases - // - _tasks.Completion completes when the runner completes - // - tokenTaskSource.Task completes when this task, or the whole runner is cancelled - var task = await Task.WhenAny(latched.Latch, _tasks.Completion, tokenTaskSource.Task); - - // ok to run now - if (task == latched.Latch) - return bgTask; - - // we are shutting down if the _tasks.Complete(); was called or the shutdown token was cancelled - var isShuttingDown = _shutdownToken.IsCancellationRequested || task == _tasks.Completion; - - // if shutting down, return the task only if it runs on shutdown - if (isShuttingDown && latched.RunsOnShutdown) - return bgTask; - - // else, either it does not run on shutdown or it's been cancelled, dispose - latched.Dispose(); - return null; - } - - // runs the background task, taking care of shutdown (as far as possible - cannot abort - // a non-async Run for example, so we'll do our best) - private async Task RunAsync(T bgTask, CancellationToken token) - { - try - { - OnTaskStarting(new TaskEventArgs(bgTask)); - - try - { - try - { - if (bgTask.IsAsync) - { - // configure await = false since we don't care about the context, we're on a background thread. - await bgTask.RunAsync(token).ConfigureAwait(false); - } - else - { - bgTask.Run(); - } - } - finally // ensure we disposed - unless latched again ie wants to re-run - { - if (!(bgTask is ILatchedBackgroundTask lbgTask) || lbgTask.IsLatched == false) - { - bgTask.Dispose(); - } - } - } - catch (Exception e) - { - OnTaskError(new TaskEventArgs(bgTask, e)); - throw; - } - - OnTaskCompleted(new TaskEventArgs(bgTask)); - } - catch (Exception ex) - { - - _logger.LogError(ex, "{LogPrefix} Task has failed", _logPrefix); - } - } - - #region Events - - // triggers when a background task starts - public event TypedEventHandler, TaskEventArgs> TaskStarting; - - // triggers when a background task has completed - public event TypedEventHandler, TaskEventArgs> TaskCompleted; - - // triggers when a background task throws - public event TypedEventHandler, TaskEventArgs> TaskError; - - // triggers when a background task is cancelled - public event TypedEventHandler, TaskEventArgs> TaskCancelled; - - // triggers when the runner stops (but could start again if a task is added to it) - internal event TypedEventHandler, EventArgs> Stopped; - - // triggers when the hosting environment requests that the runner terminates - internal event TypedEventHandler, EventArgs> Terminating; - - // triggers when the hosting environment has terminated (no task can be added, no task is running) - internal event TypedEventHandler, EventArgs> Terminated; - - private void OnEvent(TypedEventHandler, EventArgs> handler, string name) - { - OnEvent(handler, name, EventArgs.Empty); - } - - private void OnEvent(TypedEventHandler, TArgs> handler, string name, TArgs e) - { - _logger.LogDebug("{LogPrefix} OnEvent {EventName}", _logPrefix, name); - - if (handler == null) return; - - try - { - handler(this, e); - } - catch (Exception ex) - { - _logger.LogError(ex, "{LogPrefix} {Name} exception occurred", _logPrefix, name); - } - } - - protected virtual void OnTaskError(TaskEventArgs e) - { - OnEvent(TaskError, "TaskError", e); - } - - protected virtual void OnTaskStarting(TaskEventArgs e) - { - OnEvent(TaskStarting, "TaskStarting", e); - } - - protected virtual void OnTaskCompleted(TaskEventArgs e) - { - OnEvent(TaskCompleted, "TaskCompleted", e); - } - - protected virtual void OnTaskCancelled(TaskEventArgs e) - { - OnEvent(TaskCancelled, "TaskCancelled", e); - - // dispose it - e.Task.Dispose(); - } - - #endregion - - #region IDisposable - - private readonly object _disposalLocker = new object(); - public bool IsDisposed { get; private set; } - - ~BackgroundTaskRunner() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (IsDisposed || disposing == false) - return; - - lock (_disposalLocker) - { - if (IsDisposed) - return; - DisposeResources(); - IsDisposed = true; - } - } - - protected virtual void DisposeResources() - { - // just make sure we eventually go down - Shutdown(true, false); - } - - #endregion - - #region IRegisteredObject.Stop - - /// - /// Used by IRegisteredObject.Stop and shutdown on threadpool threads to not block shutdown times. - /// - /// - /// - /// An awaitable Task that is used to handle the shutdown. - /// - internal Task StopInternal(bool immediate) - { - // the first time the hosting environment requests that the runner terminates, - // raise the Terminating event - that could be used to prevent any process that - // would expect the runner to be available from starting. - var onTerminating = false; - lock (_locker) - { - if (_terminating == false) - { - _terminating = true; - _logger.LogInformation("{LogPrefix} Terminating {Immediate}", _logPrefix, immediate ? immediate.ToString() : string.Empty); - onTerminating = true; - } - } - - if (onTerminating) - OnEvent(Terminating, "Terminating"); - - // Run the Stop commands on another thread since IRegisteredObject.Stop calls are called sequentially - // with a single aspnet thread during shutdown and we don't want to delay other calls to IRegisteredObject.Stop. - if (!immediate) - { - using (ExecutionContext.SuppressFlow()) - { - return Task.Run(StopInitial, CancellationToken.None); - } - } - else - { - lock (_locker) - { - if (_terminated) return Task.CompletedTask; - using (ExecutionContext.SuppressFlow()) - { - return Task.Run(StopImmediate, CancellationToken.None); - } - } - } - } - - /// - /// Requests a registered object to un-register. - /// - /// true to indicate the registered object should un-register from the hosting - /// environment before returning; otherwise, false. - /// - /// "When the application manager needs to stop a registered object, it will call the Stop method." - /// The application manager will call the Stop method to ask a registered object to un-register. During - /// processing of the Stop method, the registered object must call the applicationShutdownRegistry.UnregisterObject method. - /// - public void Stop(bool immediate) => StopInternal(immediate); - - /// - /// Called when immediate == false for IRegisteredObject.Stop(bool immediate) - /// - /// - /// Called on a threadpool thread - /// - private void StopInitial() - { - // immediate == false when the app is trying to wind down, immediate == true will be called either: - // after a call with immediate == false or if the app is not trying to wind down and needs to immediately stop. - // So Stop may be called twice or sometimes only once. - - try - { - Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait - } - finally - { - // raise the completed event only after the running threading task has completed - lock (_locker) - { - if (_runningTask != null) - { - _runningTask.ContinueWith( - _ => StopImmediate(), - // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html - TaskScheduler.Default); - } - else - { - StopImmediate(); - } - - } - } - - // If the shutdown token was not canceled in the Shutdown call above, it means there was still tasks - // being processed, in which case we'll give it a couple seconds - if (!_shutdownToken.IsCancellationRequested) - { - // If we are called with immediate == false, wind down above and then shutdown within 2 seconds, - // we want to shut down the app as quick as possible, if we wait until immediate == true, this can - // take a very long time since immediate will only be true when a new request is received on the new - // appdomain (or another iis timeout occurs ... which can take some time). - Thread.Sleep(2000); //we are already on a threadpool thread - StopImmediate(); - } - } - - /// - /// Called when immediate == true for IRegisteredObject.Stop(bool immediate) - /// - /// - /// Called on a threadpool thread - /// - private void StopImmediate() - { - _logger.LogInformation("{LogPrefix} Canceling tasks", _logPrefix); - try - { - Shutdown(true, true); // cancel all tasks, wait for the current one to end - } - finally - { - Terminate(true); - } - } - - // called by Stop either immediately or eventually - private void Terminate(bool immediate) - { - // signal the environment we have terminated - // log - // raise the Terminated event - // complete the awaitable completion source, if any - - if (immediate) - { - //only unregister when it's the final call, else we won't be notified of the final call - _applicationShutdownRegistry.UnregisterObject(this); - } - - if (_terminated) return; // already taken care of - - TaskCompletionSource terminatedSource; - lock (_locker) - { - _terminated = true; - terminatedSource = _terminatedSource; - } - - _logger.LogInformation("{LogPrefix} Tasks {TaskStatus}, terminated", - _logPrefix, - immediate ? "cancelled" : "completed"); - - OnEvent(Terminated, "Terminated"); - - terminatedSource.TrySetResult(0); - } - - #endregion - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/BackgroundTaskRunnerOptions.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/BackgroundTaskRunnerOptions.cs deleted file mode 100644 index bc0369fee8..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/BackgroundTaskRunnerOptions.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Umbraco.Web.Scheduling -{ - /// - /// Provides options to the class. - /// - public class BackgroundTaskRunnerOptions - { - // TODO: Could add options for using a stack vs queue if required - - /// - /// Initializes a new instance of the class. - /// - public BackgroundTaskRunnerOptions() - { - LongRunning = false; - KeepAlive = false; - AutoStart = false; - PreserveRunningTask = false; - Hosted = true; - } - - /// - /// Gets or sets a value indicating whether the running task should be a long-running, - /// coarse grained operation. - /// - public bool LongRunning { get; set; } - - /// - /// Gets or sets a value indicating whether the running task should block and wait - /// on the queue, or end, when the queue is empty. - /// - public bool KeepAlive { get; set; } - - /// - /// Gets or sets a value indicating whether the running task should start immediately - /// or only once a task has been added to the queue. - /// - public bool AutoStart { get; set; } - - /// - /// Gets or sets a value indicating whether the running task should be preserved - /// once completed, or reset to null. For unit tests. - /// - public bool PreserveRunningTask { get; set; } - - /// - /// Gets or sets a value indicating whether the runner should register with (and be - /// stopped by) the hosting. Otherwise, something else should take care of stopping - /// the runner. True by default. - /// - public bool Hosted { get; set; } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/IBackgroundTask.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/IBackgroundTask.cs deleted file mode 100644 index b1285d0080..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/IBackgroundTask.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Umbraco.Web.Scheduling -{ - /// - /// Represents a background task. - /// - public interface IBackgroundTask : IDisposable - { - /// - /// Runs the background task. - /// - void Run(); - - /// - /// Runs the task asynchronously. - /// - /// A cancellation token. - /// A instance representing the execution of the background task. - /// The background task cannot run asynchronously. - Task RunAsync(CancellationToken token); - - /// - /// Indicates whether the background task can run asynchronously. - /// - bool IsAsync { get; } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/IBackgroundTaskRunner.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/IBackgroundTaskRunner.cs deleted file mode 100644 index 52dc75f3fb..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/IBackgroundTaskRunner.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Umbraco.Cms.Core; - -namespace Umbraco.Web.Scheduling -{ - /// - /// Defines a service managing a queue of tasks of type and running them in the background. - /// - /// The type of the managed tasks. - /// The interface is not complete and exists only to have the contravariance on T. - public interface IBackgroundTaskRunner : IDisposable, IRegisteredObject - where T : class, IBackgroundTask - { - bool IsCompleted { get; } - void Add(T task); - bool TryAdd(T task); - - // TODO: complete the interface? - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/ILatchedBackgroundTask.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/ILatchedBackgroundTask.cs deleted file mode 100644 index e981623b34..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/ILatchedBackgroundTask.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Umbraco.Web.Scheduling -{ - /// - /// Represents a latched background task. - /// - /// Latched background tasks can suspend their execution until - /// a condition is met. However if the tasks runner has to terminate, - /// latched background tasks can be executed immediately, depending on - /// the value returned by RunsOnShutdown. - public interface ILatchedBackgroundTask : IBackgroundTask - { - /// - /// Gets a task on latch. - /// - /// The task is not latched. - Task Latch { get; } - - /// - /// Gets a value indicating whether the task is latched. - /// - /// Should return false as soon as the condition is met. - bool IsLatched { get; } - - /// - /// Gets a value indicating whether the task can be executed immediately if the task runner has to terminate. - /// - bool RunsOnShutdown { get; } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/LatchedBackgroundTaskBase.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/LatchedBackgroundTaskBase.cs deleted file mode 100644 index 738bed9b5b..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/LatchedBackgroundTaskBase.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Umbraco.Cms.Core; - -namespace Umbraco.Web.Scheduling -{ - public abstract class LatchedBackgroundTaskBase : DisposableObjectSlim, ILatchedBackgroundTask - { - private TaskCompletionSource _latch; - - protected LatchedBackgroundTaskBase() - { - _latch = new TaskCompletionSource(); - } - - /// - /// Implements IBackgroundTask.Run(). - /// - public virtual void Run() - { - throw new NotSupportedException("This task cannot run synchronously."); - } - - /// - /// Implements IBackgroundTask.RunAsync(). - /// - public virtual Task RunAsync(CancellationToken token) - { - throw new NotSupportedException("This task cannot run asynchronously."); - } - - /// - /// Indicates whether the background task can run asynchronously. - /// - public abstract bool IsAsync { get; } - - public Task Latch => _latch.Task; - - public bool IsLatched => _latch.Task.IsCompleted == false; - - protected void Release() - { - _latch.SetResult(true); - } - - protected void Reset() - { - _latch = new TaskCompletionSource(); - } - - public virtual bool RunsOnShutdown => false; - - // the task is going to be disposed after execution, - // unless it is latched again, thus indicating it wants to - // remain active - - protected override void DisposeResources() - { } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/TaskEventArgs.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/TaskEventArgs.cs deleted file mode 100644 index 972bc12901..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/TaskEventArgs.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -namespace Umbraco.Web.Scheduling -{ - /// - /// Provides arguments for task runner events. - /// - /// The type of the task. - public class TaskEventArgs : EventArgs - where T : IBackgroundTask - { - /// - /// Initializes a new instance of the class with a task. - /// - /// The task. - public TaskEventArgs(T task) - { - Task = task; - } - - /// - /// Initializes a new instance of the class with a task and an exception. - /// - /// The task. - /// An exception. - public TaskEventArgs(T task, Exception exception) - { - Task = task; - Exception = exception; - } - - /// - /// Gets or sets the task. - /// - public T Task { get; private set; } - - /// - /// Gets or sets the exception. - /// - public Exception Exception { get; private set; } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/ThreadingTaskImmutable.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/ThreadingTaskImmutable.cs deleted file mode 100644 index b1ea0d7f71..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/LegacyBackgroundTask/ThreadingTaskImmutable.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -namespace Umbraco.Web.Scheduling -{ - /// - /// Wraps a within an object that gives access to its GetAwaiter method and Status - /// property while ensuring that it cannot be modified in any way. - /// - public class ThreadingTaskImmutable - { - private readonly Task _task; - - /// - /// Initializes a new instance of the class with a Task. - /// - /// The task. - public ThreadingTaskImmutable(Task task) - { - if (task == null) - throw new ArgumentNullException("task"); - _task = task; - } - - /// - /// Gets an awaiter used to await the task. - /// - /// An awaiter instance. - public TaskAwaiter GetAwaiter() - { - return _task.GetAwaiter(); - } - - /// - /// Gets the TaskStatus of the task. - /// - /// The current TaskStatus of the task. - public TaskStatus Status - { - get { return _task.Status; } - } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PreviewContent.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/PreviewContent.cs deleted file mode 100644 index f190b7ee41..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PreviewContent.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Xml; -using Microsoft.Extensions.Logging; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Composing; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - class PreviewContent - { - private readonly int _userId; - private readonly Guid _previewSet; - private string _previewSetPath; - private XmlDocument _previewXml; - private readonly XmlStore _xmlStore; - - /// - /// Gets the XML document. - /// - /// May return null if the preview content set is invalid. - public XmlDocument XmlContent - { - get - { - // null if invalid preview content - if (_previewSetPath == null) return null; - - // load if not loaded yet - if (_previewXml != null) - return _previewXml; - - _previewXml = new XmlDocument(); - - try - { - _previewXml.Load(_previewSetPath); - } - catch (Exception ex) - { - Current.Logger.LogError(ex, "Could not load preview set {PreviewSet} for user {UserId}.", _previewSet, _userId); - - ClearPreviewSet(); - - _previewXml = null; - _previewSetPath = null; // do not try again - } - - return _previewXml; - } - } - - /// - /// Gets the preview token. - /// - /// To be stored in a cookie or wherever appropriate. - public string Token => _userId + ":" + _previewSet; - - /// - /// Initializes a new instance of the class for a user. - /// - /// The underlying Xml store. - /// The user identifier. - public PreviewContent(XmlStore xmlStore, int userId) - { - if (xmlStore == null) - throw new ArgumentNullException(nameof(xmlStore)); - _xmlStore = xmlStore; - - _userId = userId; - _previewSet = Guid.NewGuid(); - _previewSetPath = GetPreviewSetPath(_userId, _previewSet); - } - - /// - /// Initializes a new instance of the with a preview token. - /// - /// The underlying Xml store. - /// The preview token. - public PreviewContent(XmlStore xmlStore, string token) - { - if (xmlStore == null) - throw new ArgumentNullException(nameof(xmlStore)); - _xmlStore = xmlStore; - - if (token.IsNullOrWhiteSpace()) - throw new ArgumentException("Null or empty token.", nameof(token)); - var parts = token.Split(':'); - if (parts.Length != 2) - throw new ArgumentException("Invalid token.", nameof(token)); - - if (int.TryParse(parts[0], out _userId) == false) - throw new ArgumentException("Invalid token.", nameof(token)); - if (Guid.TryParse(parts[1], out _previewSet) == false) - throw new ArgumentException("Invalid token.", nameof(token)); - - _previewSetPath = GetPreviewSetPath(_userId, _previewSet); - } - - // creates and saves a new preview set - // used in 2 places and each time includeSubs is true - // have to use the Document class at the moment because IContent does not do ToXml... - public void CreatePreviewSet(int contentId, bool includeSubs) - { - // note: always include subs - _previewXml = _xmlStore.GetPreviewXml(contentId, includeSubs); - - // make sure the preview folder exists - var dir = new DirectoryInfo(TestHelper.IOHelper.MapPath(Constants.SystemDirectories.Preview)); - if (dir.Exists == false) - dir.Create(); - - // clean old preview sets - ClearPreviewDirectory(_userId, dir); - - // save - _previewXml.Save(_previewSetPath); - } - - // get the full path to the preview set - private static string GetPreviewSetPath(int userId, Guid previewSet) - { - return TestHelper.IOHelper.MapPath(Path.Combine(Constants.SystemDirectories.Preview, userId + "_" + previewSet + ".config")); - } - - // deletes files for the user, and files accessed more than one hour ago - private static void ClearPreviewDirectory(int userId, DirectoryInfo dir) - { - var now = DateTime.Now; - var prefix = userId + "_"; - foreach (var file in dir.GetFiles("*.config") - .Where(x => x.Name.StartsWith(prefix) || (now - x.LastAccessTime).TotalMinutes > 1)) - { - DeletePreviewSetFile(userId, file); - } - } - - // delete one preview set file in a safe way - private static void DeletePreviewSetFile(int userId, FileSystemInfo file) - { - try - { - file.Delete(); - } - catch (Exception ex) - { - Current.Logger.LogError(ex, "Couldn't delete preview set {FileName} for user {UserId}", file.Name, userId); - } - } - - /// - /// Deletes the preview set in a safe way. - /// - public void ClearPreviewSet() - { - if (_previewSetPath == null) return; - var previewSetFile = new FileInfo(_previewSetPath); - DeletePreviewSetFile(_userId, previewSetFile); - } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PreviewXmlDto.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/PreviewXmlDto.cs deleted file mode 100644 index ba49167fda..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PreviewXmlDto.cs +++ /dev/null @@ -1,24 +0,0 @@ -using NPoco; -using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; -using Umbraco.Cms.Infrastructure.Persistence.Dtos; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - [TableName("cmsPreviewXml")] - [PrimaryKey("nodeId", AutoIncrement = false)] - [ExplicitColumns] - internal class PreviewXmlDto - { - [Column("nodeId")] - [PrimaryKeyColumn(AutoIncrement = false)] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] - public int NodeId { get; set; } - - [Column("xml")] - [SpecialDbType(SpecialDbTypes.NTEXT)] - public string Xml { get; set; } - - [Column("rv")] - public long Rv { get; set; } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs deleted file mode 100644 index 6bfaa075b2..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ /dev/null @@ -1,548 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Xml; -using System.Xml.XPath; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Xml; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache - { - private readonly IAppCache _appCache; - private readonly GlobalSettings _globalSettings; - private readonly RoutesCache _routesCache; - private readonly IVariationContextAccessor _variationContextAccessor; - private readonly IDomainCache _domainCache; - private readonly PublishedContentTypeCache _contentTypeCache; - - // initialize a PublishedContentCache instance with - // an XmlStore containing the master xml - // an IAppCache that should be at request-level - // a RoutesCache - need to cleanup that one - // a preview token string (or null if not previewing) - public PublishedContentCache( - XmlStore xmlStore, // an XmlStore containing the master xml - IDomainCache domainCache, // an IDomainCache implementation - IAppCache appCache, // an IAppCache that should be at request-level - GlobalSettings globalSettings, - PublishedContentTypeCache contentTypeCache, // a PublishedContentType cache - RoutesCache routesCache, // a RoutesCache - IVariationContextAccessor variationContextAccessor, - string previewToken) // a preview token string (or null if not previewing) - : base(previewToken.IsNullOrWhiteSpace() == false) - { - _appCache = appCache; - _globalSettings = globalSettings; - _routesCache = routesCache; // may be null for unit-testing - _variationContextAccessor = variationContextAccessor; - _contentTypeCache = contentTypeCache; - _domainCache = domainCache; - - _xmlStore = xmlStore; - _xml = _xmlStore.Xml; // capture - because the cache has to remain consistent - - if (previewToken.IsNullOrWhiteSpace() == false) - _previewContent = new PreviewContent(_xmlStore, previewToken); - } - - #region Unit Tests - - // for INTERNAL, UNIT TESTS use ONLY - internal RoutesCache RoutesCache => _routesCache; - - // for INTERNAL, UNIT TESTS use ONLY - internal XmlStore XmlStore => _xmlStore; - - #endregion - - #region Routes - - public virtual IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string culture = null) - { - if (route == null) throw new ArgumentNullException(nameof(route)); - - // try to get from cache if not previewing - var contentId = preview || _routesCache == null ? 0 : _routesCache.GetNodeId(route); - - // if found id in cache then get corresponding content - // and clear cache if not found - for whatever reason - IPublishedContent content = null; - if (contentId > 0) - { - content = GetById(preview, contentId); - if (content == null) - _routesCache?.ClearNode(contentId); - } - - // still have nothing? actually determine the id - hideTopLevelNode = hideTopLevelNode ?? _globalSettings.HideTopLevelNodeFromPath; // default = settings - content = content ?? DetermineIdByRoute(preview, route, hideTopLevelNode.Value); - - // cache if we have a content and not previewing - if (content != null && preview == false && _routesCache != null) - AddToCacheIfDeepestRoute(content, route); - - return content; - } - - private void AddToCacheIfDeepestRoute(IPublishedContent content, string route) - { - var domainRootNodeId = route.StartsWith("/") ? -1 : int.Parse(route.Substring(0, route.IndexOf('/'))); - - // so we have a route that maps to a content... say "1234/path/to/content" - however, there could be a - // domain set on "to" and route "4567/content" would also map to the same content - and due to how - // URLs computing work (by walking the tree up to the first domain we find) it is that second route - // that would be returned - the "deepest" route - and that is the route we want to cache, *not* the - // longer one - so make sure we don't cache the wrong route - - var deepest = DomainUtilities.ExistsDomainInPath(_domainCache.GetAll(false), content.Path, domainRootNodeId) == false; - - if (deepest) - _routesCache.Store(content.Id, route, true); // trusted route - } - - public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null) - { - return GetByRoute(PreviewDefault, route, hideTopLevelNode); - } - - public virtual string GetRouteById(bool preview, int contentId, string culture = null) - { - // try to get from cache if not previewing - var route = preview || _routesCache == null ? null : _routesCache.GetRoute(contentId); - - // if found in cache then return - if (route != null) - return route; - - // else actually determine the route - route = DetermineRouteById(preview, contentId); - - // node not found - if (route == null) - return null; - - // cache the route BUT do NOT trust it as it can be a colliding route - // meaning if we GetRouteById again, we'll get it from cache, but it - // won't be used for inbound routing - if (preview == false) - _routesCache.Store(contentId, route, false); - - return route; - } - - public string GetRouteById(int contentId, string culture = null) - { - return GetRouteById(PreviewDefault, contentId, culture); - } - - IPublishedContent DetermineIdByRoute(bool preview, string route, bool hideTopLevelNode) - { - //the route always needs to be lower case because we only store the urlName attribute in lower case - route = route?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(route)); - - var pos = route.IndexOf('/'); - var path = pos == 0 ? route : route.Substring(pos); - var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos)); - - //check if we can find the node in our xml cache - var id = NavigateRoute(preview, startNodeId, path, hideTopLevelNode); - return id > 0 ? GetById(preview, id) : null; - } - - private static XmlElement GetXmlElementChildWithLowestSortOrder(XmlNode element) - { - XmlElement elt = null; - var min = int.MaxValue; - foreach (var n in element.ChildNodes) - { - var e = n as XmlElement; - if (e == null) continue; - - var sortOrder = int.Parse(e.GetAttribute("sortOrder")); - if (sortOrder >= min) continue; - - min = sortOrder; - elt = e; - } - return elt; - } - - private int NavigateRoute(bool preview, int startNodeId, string path, bool hideTopLevelNode) - { - var xml = GetXml(preview); - XmlElement elt; - - // empty path - if (path == string.Empty || path == "/") - { - if (startNodeId > 0) - { - elt = xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture)); - return elt == null ? -1 : startNodeId; - } - - elt = GetXmlElementChildWithLowestSortOrder(xml.DocumentElement); - return elt == null ? -1 : int.Parse(elt.GetAttribute("id")); - } - - // non-empty path - elt = startNodeId <= 0 - ? xml.DocumentElement - : xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture)); - if (elt == null) return -1; - - var urlParts = path.Split(SlashChar, StringSplitOptions.RemoveEmptyEntries); - - if (hideTopLevelNode && startNodeId <= 0) - { - //Don't use OfType or Cast, this is critical code, all ChildNodes are XmlElement so explicitly cast - // https://gist.github.com/Shazwazza/04e2e5642a316f4a87e52dada2901198 - foreach (var n in elt.ChildNodes) - { - var e = n as XmlElement; - if (e == null) continue; - - var id = NavigateElementRoute(e, urlParts); - if (id > 0) return id; - } - - if (urlParts.Length > 1) - return -1; - } - - return NavigateElementRoute(elt, urlParts); - } - - private static int NavigateElementRoute(XmlElement elt, string[] urlParts) - { - var found = true; - var i = 0; - while (found && i < urlParts.Length) - { - found = false; - //Don't use OfType or Cast, this is critical code, all ChildNodes are XmlElement so explicitly cast - // https://gist.github.com/Shazwazza/04e2e5642a316f4a87e52dada2901198 - var sortOrder = -1; - foreach (var o in elt.ChildNodes) - { - var child = o as XmlElement; - if (child == null) continue; - - var noNode = child.GetAttributeNode("isDoc") == null; - if (noNode) continue; - if (child.GetAttribute("urlName") != urlParts[i]) continue; - - found = true; - - var so = int.Parse(child.GetAttribute("sortOrder")); - if (sortOrder >= 0 && so >= sortOrder) continue; - - sortOrder = so; - elt = child; - } - i++; - } - return found ? int.Parse(elt.GetAttribute("id")) : -1; - } - - string DetermineRouteById(bool preview, int contentId) - { - var node = GetById(preview, contentId); - if (node == null) return null; - - // walk up from that node until we hit a node with a domain, - // or we reach the content root, collecting URLs in the way - var pathParts = new List(); - var n = node; - var hasDomains = _domainCache.HasAssigned(n.Id); - while (hasDomains == false && n != null) // n is null at root - { - // get the url - var urlName = n.UrlSegment(TestHelper.VariationContextAccessor); - pathParts.Add(urlName); - - // move to parent node - n = n.Parent; - hasDomains = n != null && _domainCache.HasAssigned(n.Id); - } - - // no domain, respect HideTopLevelNodeFromPath for legacy purposes - if (hasDomains == false && _globalSettings.HideTopLevelNodeFromPath) - { - if (node.Parent == null) - { - var rootNode = GetByRoute(preview, "/", true); - if (rootNode == null) - throw new Exception("Failed to get node at /."); - if (rootNode.Id == node.Id) // remove only if we're the default node - pathParts.RemoveAt(pathParts.Count - 1); - } - else - { - pathParts.RemoveAt(pathParts.Count - 1); - } - } - - // assemble the route - pathParts.Reverse(); - var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc - var route = (n?.Id.ToString(CultureInfo.InvariantCulture) ?? "") + path; - - return route; - } - - #endregion - - #region XPath Strings - - static class XPathStrings - { - public const string Root = "/root"; - public const string RootDocuments = "/root/* [@isDoc]"; - } - - #endregion - - #region Converters - - private IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing) - { - return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache, _variationContextAccessor); - } - - private IEnumerable ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing) - { - return xmlNodes.Cast() - .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache, _variationContextAccessor)); - } - - #endregion - - #region Getters - - public override IPublishedContent GetById(bool preview, int nodeId) - { - return ConvertToDocument(GetXml(preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview); - } - - public override IPublishedContent GetById(bool preview, Guid nodeId) - { - // implement this, but in a more efficient way - //const string xpath = "//* [@isDoc and @key=$guid]"; - //return GetSingleByXPath(preview, xpath, new[] { new XPathVariable("guid", nodeId.ToString()) }); - - var keyMatch = nodeId.ToString(); - - var nav = GetXml(preview).CreateNavigator(); - if (nav.MoveToFirstChild() == false) return null; // from / to /root - if (nav.MoveToFirstChild() == false) return null; // from /root to /root/* - - while (true) - { - var isDoc = false; - string key = null; - - if (nav.HasAttributes) - { - nav.MoveToFirstAttribute(); - do - { - if (nav.Name == "isDoc") isDoc = true; - if (nav.Name == "key") key = nav.Value; - if (isDoc && key != null) break; - } while (nav.MoveToNextAttribute()); - nav.MoveToParent(); - } - - if (isDoc == false || key != keyMatch) - { - if (isDoc && nav.MoveToFirstChild()) - continue; - while (nav.MoveToNext(XPathNodeType.Element) == false) - if (nav.MoveToParent() == false || nav.NodeType == XPathNodeType.Root) return null; - continue; - } - - var elt = nav.UnderlyingObject as XmlNode; - return ConvertToDocument(elt, preview); - } - } - - public override IPublishedContent GetById(bool preview, Udi nodeId) - => throw new NotSupportedException(); - - public override bool HasById(bool preview, int contentId) - { - return GetXml(preview).CreateNavigator().MoveToId(contentId.ToString(CultureInfo.InvariantCulture)); - } - - public override IEnumerable GetAtRoot(bool preview, string culture = null) - { - return ConvertToDocuments(GetXml(preview).SelectNodes(XPathStrings.RootDocuments), preview); - } - - public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) - { - if (xpath == null) throw new ArgumentNullException(nameof(xpath)); - if (string.IsNullOrWhiteSpace(xpath)) return null; - - var xml = GetXml(preview); - var node = vars == null - ? xml.SelectSingleNode(xpath) - : xml.SelectSingleNode(xpath, vars); - return ConvertToDocument(node, preview); - } - - public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) - { - if (xpath == null) throw new ArgumentNullException(nameof(xpath)); - - var xml = GetXml(preview); - var node = vars == null - ? xml.SelectSingleNode(xpath) - : xml.SelectSingleNode(xpath, vars); - return ConvertToDocument(node, preview); - } - - public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) - { - if (xpath == null) throw new ArgumentNullException(nameof(xpath)); - if (string.IsNullOrWhiteSpace(xpath)) return Enumerable.Empty(); - - var xml = GetXml(preview); - var nodes = vars == null - ? xml.SelectNodes(xpath) - : xml.SelectNodes(xpath, vars); - return ConvertToDocuments(nodes, preview); - } - - public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) - { - if (xpath == null) throw new ArgumentNullException(nameof(xpath)); - - var xml = GetXml(preview); - var nodes = vars == null - ? xml.SelectNodes(xpath) - : xml.SelectNodes(xpath, vars); - return ConvertToDocuments(nodes, preview); - } - - public override bool HasContent(bool preview) - { - var xml = GetXml(preview); - var node = xml?.SelectSingleNode(XPathStrings.RootDocuments); - return node != null; - } - - public override XPathNavigator CreateNavigator(bool preview) - { - var xml = GetXml(preview); - return xml.CreateNavigator(); - } - - public override XPathNavigator CreateNodeNavigator(int id, bool preview) - { - // hackish - backward compatibility ;-( - - XPathNavigator navigator = null; - - if (preview) - { - var node = _xmlStore.GetPreviewXmlNode(id); - if (node != null) - { - navigator = node.CreateNavigator(); - } - } - else - { - var node = GetXml(false).GetElementById(id.ToInvariantString()); - if (node != null) - { - var doc = new XmlDocument(); - var clone = doc.ImportNode(node, false); - var props = node.SelectNodes("./* [not(@id)]"); - if (props == null) throw new Exception("oops"); - foreach (var n in props.Cast()) - clone.AppendChild(doc.ImportNode(n, true)); - navigator = node.CreateNavigator(); - } - } - - return navigator; - } - - #endregion - - #region Legacy Xml - - private readonly XmlStore _xmlStore; - private XmlDocument _xml; - private readonly PreviewContent _previewContent; - - internal XmlDocument GetXml(bool preview) - { - // not trying to be thread-safe here, that's not the point - - if (preview == false) - { - // if there's a current enlisted reader/writer, use its xml - var tempXml = _xmlStore.TempXml; - if (tempXml != null) return tempXml; - return _xml; - } - - // Xml cache does not support retrieving preview content when not previewing - if (_previewContent == null) - throw new InvalidOperationException("Cannot retrieve preview content when not previewing."); - - // PreviewContent tries to load the Xml once and if it fails, - // it invalidates itself and always return null for XmlContent. - var previewXml = _previewContent.XmlContent; - return previewXml ?? _xml; - } - - internal void Resync(XmlDocument xml) - { - _xml = xml; // re-capture - - // note: we're not resyncing "preview" because that would mean re-building the whole - // preview set which is costly, so basically when previewing, there will be no resync. - - // clear recursive properties cached by XmlPublishedContent.GetProperty - // assume that nothing else is going to cache IPublishedProperty items (else would need to do ByKeySearch) - // NOTE also clears all the media cache properties, which is OK (see media cache) - _appCache.ClearOfType(); - //_appCache.ClearCacheByKeySearch("XmlPublishedCache.PublishedContentCache:RecursiveProperty-"); - } - - #endregion - - #region XPathQuery - - static readonly char[] SlashChar = { '/' }; - - #endregion - - #region Content types - - public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Content, id); - - public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Content, alias); - - public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Content, key); - - #endregion - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs deleted file mode 100644 index c8e5bfaacf..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs +++ /dev/null @@ -1,698 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Threading; -using System.Xml.XPath; -using Examine; -using Examine.Search; -using Lucene.Net.Store; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Core.Xml; -using Umbraco.Cms.Infrastructure.Examine; -using Umbraco.Extensions; -using Umbraco.Web.Composing; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - /// - /// An IPublishedMediaStore that first checks for the media in Examine, and then reverts to the database - /// - /// - /// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly. - /// - internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache - { - private readonly IMediaService _mediaService; - private readonly IUserService _userService; - - // by default these are null unless specified by the ctor dedicated to tests - // when they are null the cache derives them from the ExamineManager, see - // method GetExamineManagerSafe(). - // - private readonly ISearcher _searchProvider; - private readonly XmlStore _xmlStore; - private readonly PublishedContentTypeCache _contentTypeCache; - private readonly IEntityXmlSerializer _entitySerializer; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IVariationContextAccessor _variationContextAccessor; - private readonly IExamineManager _examineManager = new ExamineManager(); - - // must be specified by the ctor - private readonly IAppCache _appCache; - - public PublishedMediaCache(XmlStore xmlStore, IMediaService mediaService, IUserService userService, - IAppCache appCache, PublishedContentTypeCache contentTypeCache, IEntityXmlSerializer entitySerializer, - IUmbracoContextAccessor umbracoContextAccessor, IVariationContextAccessor variationContextAccessor) - : base(false) - { - _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); - - _appCache = appCache; - _xmlStore = xmlStore; - _contentTypeCache = contentTypeCache; - _entitySerializer = entitySerializer; - _umbracoContextAccessor = umbracoContextAccessor; - _variationContextAccessor = variationContextAccessor; - } - - /// - /// Generally used for unit testing to use an explicit examine searcher - /// - /// - /// - /// - /// - /// - /// - internal PublishedMediaCache(IMediaService mediaService, IUserService userService, ISearcher searchProvider, IAppCache appCache, PublishedContentTypeCache contentTypeCache, IEntityXmlSerializer entitySerializer, IUmbracoContextAccessor umbracoContextAccessor) - : base(false) - { - _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); - _searchProvider = searchProvider ?? throw new ArgumentNullException(nameof(searchProvider)); - _appCache = appCache; - _contentTypeCache = contentTypeCache; - _entitySerializer = entitySerializer; - _umbracoContextAccessor = umbracoContextAccessor; - } - - static PublishedMediaCache() - { - InitializeCacheConfig(); - } - - public override IPublishedContent GetById(bool preview, int nodeId) - { - return GetUmbracoMedia(nodeId); - } - - public override IPublishedContent GetById(bool preview, Guid nodeId) - { - throw new NotImplementedException(); - } - - public override IPublishedContent GetById(bool preview, Udi nodeId) - => throw new NotSupportedException(); - - public override bool HasById(bool preview, int contentId) - { - return GetUmbracoMedia(contentId) != null; - } - - public override IEnumerable GetAtRoot(bool preview, string culture = null) - { - var searchProvider = GetSearchProviderSafe(); - - if (searchProvider != null) - { - try - { - // first check in Examine for the cache values - // +(+parentID:-1) +__IndexType:media - - var criteria = searchProvider.CreateQuery("media"); - var filter = criteria.ParentId(-1).Not().Field(UmbracoExamineFieldNames.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); - - var result = filter.Execute(); - if (result != null) - return result.Select(x => CreateFromCacheValues(ConvertFromSearchResult(x))); - } - catch (Exception ex) - { - if (ex is FileNotFoundException) - { - //Currently examine is throwing FileNotFound exceptions when we have a load balanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - // TODO: Need to fix examine in LB scenarios! - Current.Logger.LogError(ex, "Could not load data from Examine index for media"); - } - else if (ex is ObjectDisposedException) - { - //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot - //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. - Current.Logger.LogError(ex, "Could not load data from Examine index for media, the app domain is most likely in a shutdown state"); - } - else throw; - } - } - - //something went wrong, fetch from the db - - var rootMedia = _mediaService.GetRootMedia(); - return rootMedia.Select(m => GetUmbracoMedia(m.Id)); - } - - public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - //var navigator = CreateNavigator(preview); - //var iterator = navigator.Select(xpath, vars); - //return GetSingleByXPath(iterator); - } - - public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - //var navigator = CreateNavigator(preview); - //var iterator = navigator.Select(xpath, vars); - //return GetSingleByXPath(iterator); - } - - private IPublishedContent GetSingleByXPath(XPathNodeIterator iterator) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - //if (iterator.MoveNext() == false) return null; - //var idAttr = iterator.Current.GetAttribute("id", ""); - //int id; - //return int.TryParse(idAttr, out id) ? GetUmbracoMedia(id) : null; - } - - public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - //var navigator = CreateNavigator(preview); - //var iterator = navigator.Select(xpath, vars); - //return GetByXPath(iterator); - } - - public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - //var navigator = CreateNavigator(preview); - //var iterator = navigator.Select(xpath, vars); - //return GetByXPath(iterator); - } - - private IEnumerable GetByXPath(XPathNodeIterator iterator) - { - while (iterator.MoveNext()) - { - var idAttr = iterator.Current.GetAttribute("id", ""); - int id; - if (int.TryParse(idAttr, out id)) - yield return GetUmbracoMedia(id); - } - } - - public override XPathNavigator CreateNavigator(bool preview) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - //var doc = _xmlStore.GetMediaXml(); - //return doc.CreateNavigator(); - } - - public override XPathNavigator CreateNodeNavigator(int id, bool preview) - { - // preview is ignored for media cache - - // this code is mostly used when replacing old media.ToXml() code, and that code - // stored the XML attached to the media itself - so for some time in memory - so - // unless we implement some sort of cache here, we're probably degrading perfs. - - XPathNavigator navigator = null; - var node = _xmlStore.GetMediaXmlNode(id); - if (node != null) - { - navigator = node.CreateNavigator(); - } - return navigator; - } - - public override bool HasContent(bool preview) { throw new NotImplementedException(); } - - private ISearcher GetSearchProviderSafe() - { - if (_searchProvider != null) - return _searchProvider; - - try - { - return _examineManager.TryGetIndex(Constants.UmbracoIndexes.InternalIndexName, out var index) ? index.GetSearcher() : null; - } - catch (FileNotFoundException) - { - //Currently examine is throwing FileNotFound exceptions when we have a load balanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - // TODO: Need to fix examine in LB scenarios! - } - catch (NullReferenceException) - { - //This will occur when the search provider cannot be initialized. In newer examine versions the initialization is lazy and therefore - // the manager will return the singleton without throwing initialization errors, however if examine isn't configured correctly a null - // reference error will occur because the examine settings are null. - } - catch (ObjectDisposedException) - { - //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot - //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. - } - return null; - } - - private IPublishedContent GetUmbracoMedia(int id) - { - // this recreates an IPublishedContent and model each time - // it is called, but at least it should NOT hit the database - // nor Lucene each time, relying on the memory cache instead - - if (id <= 0) return null; // fail fast - - var cacheValues = GetCacheValues(id, GetUmbracoMediaCacheValues); - - return cacheValues == null ? null : CreateFromCacheValues(cacheValues); - } - - private CacheValues GetUmbracoMediaCacheValues(int id) - { - var searchProvider = GetSearchProviderSafe(); - - if (searchProvider != null) - { - try - { - // first check in Examine as this is WAY faster - // - // the filter will create a query like this: - // +(+__NodeId:3113 -__Path:-1,-21,*) +__IndexType:media - // - // note that since the use of the wildcard, it automatically escapes it in Lucene. - - var criteria = searchProvider.CreateQuery("media"); - var filter = criteria.Id(id.ToInvariantString()).Not().Field(UmbracoExamineFieldNames.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); - - var result = filter.Execute().FirstOrDefault(); - if (result != null) return ConvertFromSearchResult(result); - } - catch (Exception ex) - { - if (ex is FileNotFoundException) - { - //Currently examine is throwing FileNotFound exceptions when we have a load balanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - // TODO: Need to fix examine in LB scenarios! - Current.Logger.LogError(ex, "Could not load data from Examine index for media"); - } - else if (ex is ObjectDisposedException) - { - //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot - //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. - Current.Logger.LogError(ex, "Could not load data from Examine index for media, the app domain is most likely in a shutdown state"); - } - else throw; - } - } - - // don't log a warning here, as it can flood the log in case of eg a media picker referencing a media - // that has been deleted, hence is not in the Examine index anymore (for a good reason). try to get - // the media from the service, first - var media = _mediaService.GetById(id); - if (media == null || media.Trashed) return null; // not found, ok - - // so, the media was not found in Examine's index *yet* it exists, which probably indicates that - // the index is corrupted. Or not up-to-date. Log a warning, but only once, and only if seeing the - // error more that a number of times. - - var miss = Interlocked.CompareExchange(ref _examineIndexMiss, 0, 0); // volatile read - if (miss < ExamineIndexMissMax && Interlocked.Increment(ref _examineIndexMiss) == ExamineIndexMissMax) - Current.Logger.LogWarning("Failed ({ExamineIndexMissMax} times) to retrieve medias from Examine index and had to load" - + " them from DB. This may indicate that the Examine index is corrupted.", ExamineIndexMissMax); - - return ConvertFromIMedia(media); - } - - private const int ExamineIndexMissMax = 10; - private int _examineIndexMiss; - - internal CacheValues ConvertFromXPathNodeIterator(XPathNodeIterator media, int id) - { - if (media?.Current != null) - { - return media.Current.Name.InvariantEquals("error") - ? null - : ConvertFromXPathNavigator(media.Current); - } - - Current.Logger.LogWarning("Could not retrieve media {MediaId} from Examine index or from legacy library.GetMedia method", id); - - return null; - } - - internal CacheValues ConvertFromSearchResult(ISearchResult searchResult) - { - // note: fixing fields in 7.x, removed by Shan for 8.0 - - return new CacheValues - { - Values = searchResult.Values, - FromExamine = true - }; - } - - internal CacheValues ConvertFromXPathNavigator(XPathNavigator xpath, bool forceNav = false) - { - if (xpath == null) throw new ArgumentNullException(nameof(xpath)); - - var values = new Dictionary { { "nodeName", xpath.GetAttribute("nodeName", "") } }; - values["nodeTypeAlias"] = xpath.Name; - - var result = xpath.SelectChildren(XPathNodeType.Element); - //add the attributes e.g. id, parentId etc - if (result.Current != null && result.Current.HasAttributes) - { - if (result.Current.MoveToFirstAttribute()) - { - //checking for duplicate keys because of the 'nodeTypeAlias' might already be added above. - if (values.ContainsKey(result.Current.Name) == false) - { - values[result.Current.Name] = result.Current.Value; - } - while (result.Current.MoveToNextAttribute()) - { - if (values.ContainsKey(result.Current.Name) == false) - { - values[result.Current.Name] = result.Current.Value; - } - } - result.Current.MoveToParent(); - } - } - // because, migration - if (values.ContainsKey("key") == false) - values["key"] = Guid.Empty.ToString(); - //add the user props - while (result.MoveNext()) - { - if (result.Current != null && result.Current.HasAttributes == false) - { - var value = result.Current.Value; - if (string.IsNullOrEmpty(value)) - { - if (result.Current.HasAttributes || result.Current.SelectChildren(XPathNodeType.Element).Count > 0) - { - value = result.Current.OuterXml; - } - } - values[result.Current.Name] = value; - } - } - - return new CacheValues - { - Values = values, - XPath = forceNav ? xpath : null // outside of tests we do NOT want to cache the navigator! - }; - } - - internal CacheValues ConvertFromIMedia(IMedia media) - { - var values = new Dictionary(); - - var creator = _userService.GetProfileById(media.CreatorId); - var creatorName = creator == null ? "" : creator.Name; - - values["id"] = media.Id.ToString(); - values["key"] = media.Key.ToString(); - values["parentID"] = media.ParentId.ToString(); - values["level"] = media.Level.ToString(); - values["creatorID"] = media.CreatorId.ToString(); - values["creatorName"] = creatorName; - values["writerID"] = media.CreatorId.ToString(); - values["writerName"] = creatorName; - values["template"] = "0"; - values["urlName"] = ""; - values["sortOrder"] = media.SortOrder.ToString(); - values["createDate"] = media.CreateDate.ToString("yyyy-MM-dd HH:mm:ss"); - values["updateDate"] = media.UpdateDate.ToString("yyyy-MM-dd HH:mm:ss"); - values["nodeName"] = media.Name; - values["path"] = media.Path; - values["nodeType"] = media.ContentType.Id.ToString(); - values["nodeTypeAlias"] = media.ContentType.Alias; - - // add the user props - foreach (var prop in media.Properties) - values[prop.Alias] = prop.GetValue()?.ToString(); - - return new CacheValues - { - Values = values - }; - } - - /// - /// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists - /// in the results, if it does not, then we'll have to revert to looking up in the db. - /// - /// - /// - /// - private IPublishedProperty GetProperty(DictionaryPublishedContent dd, string alias) - { - //lets check if the alias does not exist on the document. - //NOTE: Examine will not index empty values and we do not output empty XML Elements to the cache - either of these situations - // would mean that the property is missing from the collection whether we are getting the value from Examine or from the library media cache. - if (dd.Properties.All(x => x.Alias.InvariantEquals(alias) == false)) - { - return null; - } - - if (dd.LoadedFromExamine) - { - //We are going to check for a special field however, that is because in some cases we store a 'Raw' - //value in the index such as for xml/html. - var rawValue = dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(UmbracoExamineFieldNames.RawFieldPrefix + alias)); - return rawValue - ?? dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - - //if its not loaded from examine, then just return the property - return dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - - /// - /// A Helper methods to return the children for media whether it is based on examine or xml - /// - /// - /// - /// - private IEnumerable GetChildrenMedia(int parentId, XPathNavigator xpath = null) - { - // if there *is* a navigator, directly look it up - if (xpath != null) - { - return ToIPublishedContent(parentId, xpath); - } - - // otherwise, try examine first, then re-look it up - var searchProvider = GetSearchProviderSafe(); - - if (searchProvider != null) - { - try - { - //first check in Examine as this is WAY faster - var criteria = searchProvider.CreateQuery("media"); - - var filter = criteria.ParentId(parentId).Not().Field(UmbracoExamineFieldNames.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()) - .OrderBy(new SortableField("sortOrder", SortType.Int)); - //the above filter will create a query like this, NOTE: That since the use of the wildcard, it automatically escapes it in Lucene. - //+(+parentId:3113 -__Path:-1,-21,*) +__IndexType:media - - // sort with the Sort field (updated for 8.0) - var results = filter.Execute(); - - if (results.Any()) - { - // var medias = results.Select(ConvertFromSearchResult); - var medias = results.Select(x => - { - int nid; - if (int.TryParse(x["__NodeId"], out nid) == false && int.TryParse(x["NodeId"], out nid) == false) - throw new Exception("Failed to extract NodeId from search result."); - var cacheValues = GetCacheValues(nid, id => ConvertFromSearchResult(x)); - return CreateFromCacheValues(cacheValues); - }); - - return medias; - } - - //if there's no result then return null. Previously we defaulted back to library.GetMedia below - //but this will always get called for when we are getting descendants since many items won't have - //children and then we are hitting the database again! - //So instead we're going to rely on Examine to have the correct results like it should. - return Enumerable.Empty(); - } - catch (FileNotFoundException) - { - //Currently examine is throwing FileNotFound exceptions when we have a load balanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - } - } - - // falling back to get media - // was library.GetMedia which had its own cache, but MediaService *also* caches - // so, library.GetMedia is gone and now we directly work with MediaService - // (code below copied from what library was doing) - var media = _mediaService.GetById(parentId); - if (media == null) - { - return Enumerable.Empty(); - } - - var serialized = _entitySerializer.Serialize(media, true); - - var mediaIterator = serialized.CreateNavigator().Select("/"); - - return mediaIterator.Current == null - ? Enumerable.Empty() - : ToIPublishedContent(parentId, mediaIterator.Current); - } - - - internal IEnumerable ToIPublishedContent(int parentId, XPathNavigator xpath) - { - var mediaList = new List(); - - // this is so bad, really - var item = xpath.Select("//*[@id='" + parentId + "']"); - if (item.Current == null) - return Enumerable.Empty(); - var items = item.Current.SelectChildren(XPathNodeType.Element); - - // and this does not work, because... meh - //var q = "//* [@id='" + parentId + "']/* [@id]"; - //var items = xpath.Select(q); - - foreach (XPathNavigator itemm in items) - { - int id; - if (int.TryParse(itemm.GetAttribute("id", ""), out id) == false) - continue; // uh? - var captured = itemm; - var cacheValues = GetCacheValues(id, idd => ConvertFromXPathNavigator(captured)); - mediaList.Add(CreateFromCacheValues(cacheValues)); - } - - return mediaList; - } - - - internal void Resync() - { - // clear recursive properties cached by XmlPublishedContent.GetProperty - // assume that nothing else is going to cache IPublishedProperty items (else would need to do ByKeySearch) - // NOTE all properties cleared when clearing the content cache (see content cache) - //_appCache.ClearCacheObjectTypes(); - //_appCache.ClearCacheByKeySearch("XmlPublishedCache.PublishedMediaCache:RecursiveProperty-"); - } - - #region Content types - - public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Media, id); - - public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Media, alias); - - public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Media, key); - - public override IEnumerable GetByContentType(IPublishedContentType contentType) - { - throw new NotSupportedException(); - } - - #endregion - - // REFACTORING - - // caching the basic atomic values - and the parent id - // but NOT caching actual parent nor children and NOT even - // the list of children ids - BUT caching the path - - internal class CacheValues - { - public IReadOnlyDictionary Values { get; set; } - public XPathNavigator XPath { get; set; } - public bool FromExamine { get; set; } - } - - public const string PublishedMediaCacheKey = "MediaCacheMeh."; - private const int PublishedMediaCacheTimespanSeconds = 4 * 60; // 4 mins - private static TimeSpan _publishedMediaCacheTimespan; - private static bool _publishedMediaCacheEnabled; - - private static void InitializeCacheConfig() - { - _publishedMediaCacheEnabled = true; - _publishedMediaCacheTimespan = TimeSpan.FromSeconds(PublishedMediaCacheTimespanSeconds); - } - - internal IPublishedContent CreateFromCacheValues(CacheValues cacheValues) - { - var content = new DictionaryPublishedContent( - cacheValues.Values, - parentId => parentId < 0 ? null : GetUmbracoMedia(parentId), - GetChildrenMedia, - GetProperty, - _appCache, - _variationContextAccessor, - _contentTypeCache, - cacheValues.XPath, // though, outside of tests, that should be null - cacheValues.FromExamine - ); - return content.CreateModel(Current.PublishedModelFactory); - } - - private static CacheValues GetCacheValues(int id, Func func) - { - if (_publishedMediaCacheEnabled == false) - return func(id); - - var cache = Current.AppCaches.RuntimeCache; - var key = PublishedMediaCacheKey + id; - return (CacheValues)cache.Get(key, () => func(id), _publishedMediaCacheTimespan); - } - - internal static void ClearCache(int id) - { - var cache = Current.AppCaches.RuntimeCache; - var sid = id.ToString(); - var key = PublishedMediaCacheKey + sid; - - // we do clear a lot of things... but the cache refresher is somewhat - // convoluted and it's hard to tell what to clear exactly ;-( - - // clear the parent - NOT (why?) - //var exist = (CacheValues) cache.GetCacheItem(key); - //if (exist != null) - // cache.ClearCacheItem(PublishedMediaCacheKey + GetValuesValue(exist.Values, "parentID")); - - // clear the item - cache.Clear(key); - - // clear all children - in case we moved and their path has changed - var fid = "/" + sid + "/"; - cache.ClearOfType((k, v) => - GetValuesValue(v.Values, "path", "__Path").Contains(fid)); - } - - private static string GetValuesValue(IReadOnlyDictionary d, params string[] keys) - { - string value = null; - var ignored = keys.Any(x => d.TryGetValue(x, out value)); - return value ?? ""; - } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMember.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMember.cs deleted file mode 100644 index a885af1475..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMember.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - /// - /// Exposes a member object as IPublishedContent - /// - public sealed class PublishedMember : PublishedContentBase - { - private readonly IMember _member; - private readonly IMembershipUser _membershipUser; - private readonly IPublishedProperty[] _properties; - private readonly IPublishedContentType _publishedMemberType; - - public PublishedMember( - IMember member, - IPublishedContentType publishedMemberType, - IVariationContextAccessor variationContextAccessor) : base(variationContextAccessor) - { - _member = member ?? throw new ArgumentNullException(nameof(member)); - _membershipUser = member; - _publishedMemberType = publishedMemberType ?? throw new ArgumentNullException(nameof(publishedMemberType)); - - // RawValueProperty is used for two things here - // - for the 'map properties' thing that we should really get rid of - // - for populating properties that every member should always have, and that we force-create - // if they are not part of the member type properties - in which case they are created as - // simple raw properties - which are completely invariant - - var properties = new List(); - foreach (var propertyType in _publishedMemberType.PropertyTypes) - { - var property = _member.Properties[propertyType.Alias]; - if (property == null) continue; - - properties.Add(new RawValueProperty(propertyType, this, property.GetValue())); - } - EnsureMemberProperties(properties); - _properties = properties.ToArray(); - } - - #region Membership provider member properties - - public string Email => _membershipUser.Email; - - public string UserName => _membershipUser.Username; - - public string Comments => _membershipUser.Comments; - - public bool IsApproved => _membershipUser.IsApproved; - - public bool IsLockedOut => _membershipUser.IsLockedOut; - - public DateTime LastLockoutDate => _membershipUser.LastLockoutDate; - - public DateTime CreationDate => _membershipUser.CreateDate; - - public DateTime LastLoginDate => _membershipUser.LastLoginDate; - - public DateTime LastPasswordChangeDate => _membershipUser.LastPasswordChangeDate; - - #endregion - - #region IPublishedContent - - public override PublishedItemType ItemType => PublishedItemType.Member; - - public override bool IsDraft(string culture = null) => false; - - public override bool IsPublished(string culture = null) => true; - - public override IPublishedContent Parent => null; - - public override IEnumerable Children => Enumerable.Empty(); - - public override IEnumerable ChildrenForAllCultures => Enumerable.Empty(); - - public override IEnumerable Properties => _properties; - - public override IPublishedProperty GetProperty(string alias) - { - return _properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - - private void EnsureMemberProperties(List properties) - { - var aliases = properties.Select(x => x.Alias).ToList(); - - EnsureMemberProperty(properties, aliases, nameof(IMember.Email), Email); - EnsureMemberProperty(properties, aliases, nameof(IMember.Username), UserName); - EnsureMemberProperty(properties, aliases, nameof(IMember.Comments), Comments); - EnsureMemberProperty(properties, aliases, nameof(IMember.IsApproved), IsApproved); - EnsureMemberProperty(properties, aliases, nameof(IMember.IsLockedOut), IsLockedOut); - EnsureMemberProperty(properties, aliases, nameof(IMember.LastLockoutDate), LastLockoutDate); - EnsureMemberProperty(properties, aliases, nameof(IMember.CreateDate), CreateDate); - EnsureMemberProperty(properties, aliases, nameof(IMember.LastLoginDate), LastLoginDate); - EnsureMemberProperty(properties, aliases, nameof(IMember.LastPasswordChangeDate), LastPasswordChangeDate); - } - - private void EnsureMemberProperty(List properties, List aliases, string alias, object value) - { - // if the property already has a value, nothing to do - if (aliases.Contains(alias)) return; - - // if not a property type, ignore - var propertyType = ContentType.GetPropertyType(alias); - if (propertyType == null) return; - - // create a raw-value property - // note: throws if propertyType variations is not InvariantNeutral - var property = new RawValueProperty(propertyType, this, value); - properties.Add(property); - } - - public override IPublishedContentType ContentType => _publishedMemberType; - - public override int Id => _member.Id; - - public override Guid Key => _member.Key; - - public override int? TemplateId => throw new NotSupportedException(); - - public override int SortOrder => 0; - - public override string Name => _member.Name; - - public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); - - public override string UrlSegment => throw new NotSupportedException(); - - public override int WriterId => _member.CreatorId; - - public override int CreatorId => _member.CreatorId; - - public override string Path => _member.Path; - - public override DateTime CreateDate => _member.CreateDate; - - public override DateTime UpdateDate => _member.UpdateDate; - - public override int Level => _member.Level; - - public DateTime LastPasswordChangedDate => throw new NotImplementedException(); - - #endregion - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs deleted file mode 100644 index 1faa0ee948..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Extensions; -using Umbraco.Web.Composing; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - class PublishedMemberCache : IPublishedMemberCache - { - private readonly PublishedContentTypeCache _contentTypeCache; - private readonly IVariationContextAccessor _variationContextAccessor; - - public PublishedMemberCache(PublishedContentTypeCache contentTypeCache, IVariationContextAccessor variationContextAccessor) - { - _contentTypeCache = contentTypeCache; - _variationContextAccessor = variationContextAccessor; - } - - public IPublishedContent Get(IMember member) - { - var type = _contentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId); - return new PublishedMember(member, type, _variationContextAccessor) - .CreateModel(Current.PublishedModelFactory); - } - - #region Content types - - public IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Member, id); - - public IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Member, alias); - - #endregion - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshot.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshot.cs deleted file mode 100644 index 11abce0e64..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshot.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.PublishedCache; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - /// - /// Implements a published snapshot. - /// - class PublishedSnapshot : IPublishedSnapshot - { - /// - /// Initializes a new instance of the class with a content cache - /// and a media cache. - /// - public PublishedSnapshot( - PublishedContentCache contentCache, - PublishedMediaCache mediaCache, - PublishedMemberCache memberCache, - DomainCache domainCache) - { - Content = contentCache; - Media = mediaCache; - Members = memberCache; - Domains = domainCache; - } - - /// - public IPublishedContentCache Content { get; } - - /// - public IPublishedMediaCache Media { get; } - - /// - public IPublishedMemberCache Members { get; } - - /// - public IDomainCache Domains { get; } - - /// - public IAppCache SnapshotCache => null; - - /// - public IAppCache ElementsCache => null; - - /// - public IDisposable ForcedPreview(bool preview, Action callback = null) - { - // the XML cache does not support forcing preview, really, so, just pretend... - return new ForcedPreviewObject(); - } - - private class ForcedPreviewObject : DisposableObjectSlim - { - protected override void DisposeResources() - { } - } - - public void Dispose() - { } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/RoutesCache.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/RoutesCache.cs deleted file mode 100644 index 71f2f421ff..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/RoutesCache.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - // Note: RoutesCache closely follows the caching strategy dating from v4, which - // is obviously broken in many ways (eg it's a global cache but relying to some - // extend to the content cache, which itself is local to each request...). - // Not going to fix it anyway. - - class RoutesCache - { - private ConcurrentDictionary _routes; - private ConcurrentDictionary _nodeIds; - - // NOTE - // RoutesCache is cleared by - // - ContentTypeCacheRefresher, whenever anything happens to any content type - // - DomainCacheRefresher, whenever anything happens to any domain - // - XmlStore, whenever anything happens to the XML cache - - /// - /// Initializes a new instance of the class. - /// - public RoutesCache() - { - Clear(); - } - - /// - /// Used ONLY for unit tests - /// - /// - internal IDictionary GetCachedRoutes() - { - return _routes; - } - - /// - /// Used ONLY for unit tests - /// - /// - internal IDictionary GetCachedIds() - { - return _nodeIds; - } - - #region Public - - /// - /// Stores a route for a node. - /// - /// The node identified. - /// The route. - /// A value indicating whether the value can be trusted for inbound routing. - public void Store(int nodeId, string route, bool trust) - { - _routes.AddOrUpdate(nodeId, i => route, (i, s) => route); - if (trust) - _nodeIds.AddOrUpdate(route, i => nodeId, (i, s) => nodeId); - } - - /// - /// Gets a route for a node. - /// - /// The node identifier. - /// The route for the node, else null. - public string GetRoute(int nodeId) - { - string val; - _routes.TryGetValue(nodeId, out val); - return val; - } - - /// - /// Gets a node for a route. - /// - /// The route. - /// The node identified for the route, else zero. - public int GetNodeId(string route) - { - int val; - _nodeIds.TryGetValue(route, out val); - return val; - } - - /// - /// Clears the route for a node. - /// - /// The node identifier. - public void ClearNode(int nodeId) - { - string route; - if (_routes.TryRemove(nodeId, out route)) - { - int id; - _nodeIds.TryRemove(route, out id); - } - } - - /// - /// Clears all routes. - /// - public void Clear() - { - _routes = new ConcurrentDictionary(); - _nodeIds = new ConcurrentDictionary(); - } - - #endregion - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs deleted file mode 100644 index fe560e4c93..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Xml; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Scoping; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - // TODO: should be a ScopeContextualBase - internal class SafeXmlReaderWriter : IDisposable - { - private readonly bool _scoped; - private readonly Action _refresh; - private readonly Action _apply; - private IDisposable _releaser; - private bool _applyChanges; - private XmlDocument _xml, _origXml; - private bool _using; - private bool _registerXmlChange; - - // the default enlist priority is 100 - // enlist with a lower priority to ensure that anything "default" has a clean xml - private const int EnlistPriority = 60; - private const string EnlistKey = "safeXmlReaderWriter"; - - private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action refresh, Action apply, bool isWriter, bool scoped) - { - _releaser = releaser; - _refresh = refresh; - _apply = apply; - _scoped = scoped; - - IsWriter = isWriter; - - _xml = IsWriter ? Clone(xml) : xml; - } - - public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider) - { - return scopeProvider?.Context?.GetEnlisted(EnlistKey); - } - - public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, SystemLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) - { - var scopeContext = scopeProvider.Context; - - // no real scope = just create a reader/writer instance - if (scopeContext == null) - { - // obtain exclusive access to xml and create reader/writer - var releaser = xmlLock.Lock(); - return new SafeXmlReaderWriter(releaser, xml, refresh, apply, writer, false); - } - - // get or create an enlisted reader/writer - var rw = scopeContext.Enlist(EnlistKey, - () => // creator - { - // obtain exclusive access to xml and create reader/writer - var releaser = xmlLock.Lock(); - return new SafeXmlReaderWriter(releaser, xml, refresh, apply, writer, true); - }, - (completed, item) => // action - { - item.DisposeForReal(completed); - }, EnlistPriority); - - // ensure it's not already in-use - should never happen, just being super safe - if (rw._using) - throw new InvalidOperationException("panic: used."); - rw._using = true; - - return rw; - } - - public bool IsWriter { get; private set; } - - public void UpgradeToWriter(bool auto) - { - if (IsWriter) - throw new InvalidOperationException("Already a writer."); - IsWriter = true; - - _xml = Clone(_xml); - } - - // for tests - internal static Action Cloning { get; set; } - - private XmlDocument Clone(XmlDocument xml) - { - Cloning?.Invoke(); - if (_origXml != null) - throw new Exception("panic."); - _origXml = xml; - return (XmlDocument) xml?.CloneNode(true); - } - - public XmlDocument Xml - { - get => _xml; - set - { - if (IsWriter == false) - throw new InvalidOperationException("Not a writer."); - _xml = value; - } - } - - // registerXmlChange indicates whether to do what should be done when Xml changes, - // that is, to request that the file be written to disk - something we don't want - // to do if we're committing Xml precisely after we've read from disk! - public void AcceptChanges(bool registerXmlChange = true) - { - if (IsWriter == false) - throw new InvalidOperationException("Not a writer."); - - _applyChanges = true; - _registerXmlChange |= registerXmlChange; - } - - private void DisposeForReal(bool completed) - { - if (IsWriter) - { - // apply changes, or restore the original xml for the current request - if (_applyChanges && completed) - _apply(_xml, _registerXmlChange); - else - _refresh(_origXml); - } - - // release the lock - _releaser.Dispose(); - _releaser = null; - } - - public void Dispose() - { - _using = false; - - if (_scoped == false) - { - // really dispose - DisposeForReal(true); - } - else - { - // don't really dispose, - // just apply the changes for the current request - _refresh(_xml); - } - } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/UmbracoContextCache.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/UmbracoContextCache.cs deleted file mode 100644 index d1a3340b06..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/UmbracoContextCache.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Concurrent; -using System.Runtime.CompilerServices; -using Umbraco.Cms.Core.Web; -using Umbraco.Web; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - static class UmbracoContextCache - { - static readonly ConditionalWeakTable> Caches - = new ConditionalWeakTable>(); - - public static ConcurrentDictionary Current - { - get - { - var umbracoContext = Umbraco.Web.Composing.Current.UmbracoContext; - - // will get or create a value - // a ConditionalWeakTable is thread-safe - // does not prevent the context from being disposed, and then the dictionary will be disposed too - return umbracoContext == null ? null : Caches.GetOrCreateValue(umbracoContext); - } - } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs deleted file mode 100644 index aa90f0dbfa..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ /dev/null @@ -1,441 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; -using System.Xml.Serialization; -using System.Xml.XPath; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Extensions; -using Umbraco.Web.Composing; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - /// - /// Represents an IPublishedContent which is created based on an Xml structure. - /// - [Serializable] - [XmlType(Namespace = "http://umbraco.org/webservices/")] - internal class XmlPublishedContent : PublishedContentBase - { - private XmlPublishedContent( - XmlNode xmlNode, - bool isPreviewing, - IAppCache appCache, - PublishedContentTypeCache contentTypeCache, - IVariationContextAccessor variationContextAccessor): base(variationContextAccessor) - { - _xmlNode = xmlNode; - _isPreviewing = isPreviewing; - - _appCache = appCache; - _contentTypeCache = contentTypeCache; - _variationContextAccessor = variationContextAccessor; - } - - private readonly XmlNode _xmlNode; - private readonly bool _isPreviewing; - private readonly IAppCache _appCache; // at snapshot/request level (see PublishedContentCache) - private readonly PublishedContentTypeCache _contentTypeCache; - private readonly IVariationContextAccessor _variationContextAccessor; - - private readonly object _initializeLock = new object(); - - private bool _nodeInitialized; - private bool _parentInitialized; - private bool _childrenInitialized; - - private IEnumerable _children = Enumerable.Empty(); - private IPublishedContent _parent; - - private IPublishedContentType _contentType; - private Dictionary _properties; - - private int _id; - private Guid _key; - private int _template; - private string _name; - private string _docTypeAlias; - private int _docTypeId; - private int _writerId; - private int _creatorId; - private string _urlName; - private string _path; - private DateTime _createDate; - private DateTime _updateDate; - private int _sortOrder; - private int _level; - private bool _isDraft; - - - public override IEnumerable Children - { - get - { - EnsureNodeInitialized(andChildren: true); - return _children; - } - } - - public override IEnumerable ChildrenForAllCultures => Children; - - public override IPublishedProperty GetProperty(string alias) - { - EnsureNodeInitialized(); - IPublishedProperty property; - return _properties.TryGetValue(alias, out property) ? property : null; - } - - public override PublishedItemType ItemType => PublishedItemType.Content; - - public override IPublishedContent Parent - { - get - { - EnsureNodeInitialized(andParent: true); - return _parent; - } - } - - public override int Id - { - get - { - EnsureNodeInitialized(); - return _id; - } - } - - public override Guid Key - { - get - { - EnsureNodeInitialized(); - return _key; - } - } - - public override int? TemplateId - { - get - { - EnsureNodeInitialized(); - return _template; - } - } - - public override int SortOrder - { - get - { - EnsureNodeInitialized(); - return _sortOrder; - } - } - - public override string Name - { - get - { - EnsureNodeInitialized(); - return _name; - } - } - - private Dictionary _cultures; - - private Dictionary GetCultures() - { - EnsureNodeInitialized(); - return new Dictionary { { "", new PublishedCultureInfo("", _name, _urlName, _updateDate) } }; - } - - public override IReadOnlyDictionary Cultures => _cultures ?? (_cultures = GetCultures()); - - public override int WriterId - { - get - { - EnsureNodeInitialized(); - return _writerId; - } - } - - public override int CreatorId - { - get - { - EnsureNodeInitialized(); - return _creatorId; - } - } - - public override string Path - { - get - { - EnsureNodeInitialized(); - return _path; - } - } - - public override DateTime CreateDate - { - get - { - EnsureNodeInitialized(); - return _createDate; - } - } - - public override DateTime UpdateDate - { - get - { - EnsureNodeInitialized(); - return _updateDate; - } - } - - public override string UrlSegment - { - get - { - EnsureNodeInitialized(); - return _urlName; - } - } - - public override int Level - { - get - { - EnsureNodeInitialized(); - return _level; - } - } - - public override bool IsDraft(string culture = null) - { - EnsureNodeInitialized(); - return _isDraft; // bah - } - - public override bool IsPublished(string culture = null) - { - EnsureNodeInitialized(); - return true; // Intentionally not implemented, because the XmlPublishedContent should not support this. - } - - public override IEnumerable Properties - { - get - { - EnsureNodeInitialized(); - return _properties.Values; - } - } - - public override IPublishedContentType ContentType - { - get - { - EnsureNodeInitialized(); - return _contentType; - } - } - - private void InitializeParent() - { - var parent = _xmlNode?.ParentNode; - if (parent == null) return; - - if (parent.Attributes?.GetNamedItem("isDoc") != null) - _parent = Get(parent, _isPreviewing, _appCache, _contentTypeCache, _variationContextAccessor); - - _parentInitialized = true; - } - - private void EnsureNodeInitialized(bool andChildren = false, bool andParent = false) - { - // In *theory* XmlPublishedContent are a per-request thing, and so should not - // end up being involved into multi-threaded situations - however, it's been - // reported that some users ended up seeing 100% CPU due to infinite loops in - // the properties dictionary in InitializeNode, which would indicate that the - // dictionary *is* indeed involved in some multi-threaded operation. No idea - // what users are doing that cause this, but let's be friendly and use a true - // lock around initialization. - - lock (_initializeLock) - { - if (_nodeInitialized == false) InitializeNode(); - if (andChildren && _childrenInitialized == false) InitializeChildren(); - if (andParent && _parentInitialized == false) InitializeParent(); - } - } - - private void InitializeNode() - { - InitializeNode(this, _xmlNode, _isPreviewing, - out _id, out _key, out _template, out _sortOrder, out _name, - out _urlName, out _creatorId, out _writerId, out _docTypeAlias, out _docTypeId, out _path, - out _createDate, out _updateDate, out _level, out _isDraft, out _contentType, out _properties, - _contentTypeCache.Get); - - _nodeInitialized = true; - } - - // internal for some benchmarks - internal static void InitializeNode(XmlPublishedContent node, XmlNode xmlNode, bool isPreviewing, - out int id, out Guid key, out int template, out int sortOrder, out string name, out string urlName, - out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path, - out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, - out IPublishedContentType contentType, out Dictionary properties, - Func getPublishedContentType) - { - //initialize the out params with defaults: - docTypeAlias = null; - id = template = sortOrder = template = creatorId = writerId = docTypeId = level = default(int); - key = default(Guid); - name = docTypeAlias = urlName = path = null; - createDate = updateDate = default(DateTime); - isDraft = false; - contentType = null; - properties = null; - - if (xmlNode == null) return; - - if (xmlNode.Attributes != null) - { - id = int.Parse(xmlNode.Attributes.GetNamedItem("id").Value); - if (xmlNode.Attributes.GetNamedItem("key") != null) // because, migration - key = Guid.Parse(xmlNode.Attributes.GetNamedItem("key").Value); - if (xmlNode.Attributes.GetNamedItem("template") != null) - template = int.Parse(xmlNode.Attributes.GetNamedItem("template").Value); - if (xmlNode.Attributes.GetNamedItem("sortOrder") != null) - sortOrder = int.Parse(xmlNode.Attributes.GetNamedItem("sortOrder").Value); - if (xmlNode.Attributes.GetNamedItem("nodeName") != null) - name = xmlNode.Attributes.GetNamedItem("nodeName").Value; - if (xmlNode.Attributes.GetNamedItem("urlName") != null) - urlName = xmlNode.Attributes.GetNamedItem("urlName").Value; - - //Added the actual userID, as a user cannot be looked up via full name only... - if (xmlNode.Attributes.GetNamedItem("creatorID") != null) - creatorId = int.Parse(xmlNode.Attributes.GetNamedItem("creatorID").Value); - if (xmlNode.Attributes.GetNamedItem("writerID") != null) - writerId = int.Parse(xmlNode.Attributes.GetNamedItem("writerID").Value); - - docTypeAlias = xmlNode.Name; - - if (xmlNode.Attributes.GetNamedItem("nodeType") != null) - docTypeId = int.Parse(xmlNode.Attributes.GetNamedItem("nodeType").Value); - if (xmlNode.Attributes.GetNamedItem("path") != null) - path = xmlNode.Attributes.GetNamedItem("path").Value; - if (xmlNode.Attributes.GetNamedItem("createDate") != null) - createDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("createDate").Value); - if (xmlNode.Attributes.GetNamedItem("updateDate") != null) - updateDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("updateDate").Value); - if (xmlNode.Attributes.GetNamedItem("level") != null) - level = int.Parse(xmlNode.Attributes.GetNamedItem("level").Value); - - isDraft = xmlNode.Attributes.GetNamedItem("isDraft") != null; - } - - //dictionary to store the property node data - var propertyNodes = new Dictionary(); - - foreach (XmlNode n in xmlNode.ChildNodes) - { - var e = n as XmlElement; - if (e == null) continue; - if (e.HasAttribute("isDoc") == false) - { - PopulatePropertyNodes(propertyNodes, e, false); - } - else break; //we are not longer on property elements - } - - //lookup the content type and create the properties collection - try - { - contentType = getPublishedContentType(PublishedItemType.Content, docTypeAlias); - - } - catch (InvalidOperationException e) - { - // TODO: enable! - //content.Instance.RefreshContentFromDatabase(); - throw new InvalidOperationException($"{e.Message}. This usually indicates that the content cache is corrupt; the content cache has been rebuilt in an attempt to self-fix the issue."); - } - - //fill in the property collection - properties = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var propertyType in contentType.PropertyTypes) - { - var val = propertyNodes.TryGetValue(propertyType.Alias.ToLowerInvariant(), out XmlNode n) - ? new XmlPublishedProperty(propertyType, node, isPreviewing, n) - : new XmlPublishedProperty(propertyType, node, isPreviewing); - - properties[propertyType.Alias] = val; - } - } - - private static void PopulatePropertyNodes(IDictionary propertyNodes, XmlNode n, bool legacy) - { - var attrs = n.Attributes; - if (attrs == null) return; - - var alias = legacy - ? attrs.GetNamedItem("alias").Value - : n.Name; - propertyNodes[alias.ToLowerInvariant()] = n; - } - - private void InitializeChildren() - { - if (_xmlNode == null) return; - - // load children - const string childXPath = "* [@isDoc]"; - var nav = _xmlNode.CreateNavigator(); - var expr = nav.Compile(childXPath); - //expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); - var iterator = nav.Select(expr); - - _children = iterator.Cast() - .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing, _appCache, _contentTypeCache, _variationContextAccessor)) - .OrderBy(x => x.SortOrder) - .ToList(); - - _childrenInitialized = true; - } - - /// - /// Gets an IPublishedContent corresponding to an Xml cache node. - /// - /// The Xml node. - /// A value indicating whether we are previewing or not. - /// A cache. - /// A content type cache. - /// A umbraco context accessor - /// - /// The IPublishedContent corresponding to the Xml cache node. - /// Maintains a per-request cache of IPublishedContent items in order to make - /// sure that we create only one instance of each for the duration of a request. The - /// returned IPublishedContent is a model, if models are enabled. - public static IPublishedContent Get(XmlNode node, bool isPreviewing, IAppCache appCache, - PublishedContentTypeCache contentTypeCache, IVariationContextAccessor variationContextAccessor) - { - // only 1 per request - - var attrs = node.Attributes; - var id = attrs?.GetNamedItem("id").Value; - if (id.IsNullOrWhiteSpace()) throw new InvalidOperationException("Node has no ID attribute."); - var key = CacheKeyPrefix + id; // dont bother with preview, wont change during request in Xml cache - return (IPublishedContent) appCache.Get(key, () => (new XmlPublishedContent(node, isPreviewing, appCache, contentTypeCache, variationContextAccessor)).CreateModel(Current.PublishedModelFactory)); - } - - private const string CacheKeyPrefix = "CONTENTCACHE_XMLPUBLISHEDCONTENT_"; - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs deleted file mode 100644 index d0e71e7829..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Xml; -using System.Xml.Serialization; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Xml; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - /// - /// Represents an IDocumentProperty which is created based on an Xml structure. - /// - [Serializable] - [XmlType(Namespace = "http://umbraco.org/webservices/")] - internal class XmlPublishedProperty : PublishedPropertyBase - { - private readonly string _sourceValue; // the raw, xml node value - - // Xml cache not using XPath value... and as for the rest... - // we're single threaded here, keep it simple - private object _objectValue; - private bool _objectValueComputed; - private readonly bool _isPreviewing; - private readonly IPublishedContent _content; - - /// - /// Gets the raw value of the property. - /// - public override object GetSourceValue(string culture = null, string segment = null) => _sourceValue; - - // in the Xml cache, everything is a string, and to have a value - // you want to have a non-null, non-empty string. - public override bool HasValue(string culture = null, string segment = null) => _sourceValue.Trim().Length > 0; - - public override object GetValue(string culture = null, string segment = null) - { - // NOT caching the source (intermediate) value since we'll never need it - // everything in Xml cache is per-request anyways - // also, properties should not be shared between requests and therefore - // are single threaded, so the following code should be safe & fast - - if (_objectValueComputed) return _objectValue; - var inter = PropertyType.ConvertSourceToInter(_content, _sourceValue, _isPreviewing); - // initial reference cache level always is .Content - _objectValue = PropertyType.ConvertInterToObject(_content, PropertyCacheLevel.Element, inter, _isPreviewing); - _objectValueComputed = true; - return _objectValue; - } - - public override object GetXPathValue(string culture = null, string segment = null) { throw new NotImplementedException(); } - - public XmlPublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, XmlNode propertyXmlData) - : this(propertyType, content, isPreviewing) - { - if (propertyXmlData == null) - throw new ArgumentNullException(nameof(propertyXmlData), "Property xml source is null"); - _sourceValue = XmlHelper.GetNodeValue(propertyXmlData); - } - - public XmlPublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, string propertyData) - : this(propertyType, content, isPreviewing) - { - if (propertyData == null) - throw new ArgumentNullException(nameof(propertyData)); - _sourceValue = propertyData; - } - - public XmlPublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing) - : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored - { - _sourceValue = string.Empty; - _content = content; - _isPreviewing = isPreviewing; - } - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs deleted file mode 100644 index 580a4078f8..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Runtime; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Web; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - /// - /// Implements a published snapshot service. - /// - internal class XmlPublishedSnapshotService : IPublishedSnapshotService - { - private readonly XmlStore _xmlStore; - private readonly RoutesCache _routesCache; - private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; - private readonly PublishedContentTypeCache _contentTypeCache; - private readonly IDomainService _domainService; - private readonly IMediaService _mediaService; - private readonly IUserService _userService; - private readonly IAppCache _requestCache; - private readonly GlobalSettings _globalSettings; - private readonly IDefaultCultureAccessor _defaultCultureAccessor; - private readonly IEntityXmlSerializer _entitySerializer; - private readonly IVariationContextAccessor _variationContextAccessor; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - - #region Constructors - - // used in WebBootManager + tests - public XmlPublishedSnapshotService( - ServiceContext serviceContext, - IPublishedContentTypeFactory publishedContentTypeFactory, - IScopeProvider scopeProvider, - IAppCache requestCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, - IVariationContextAccessor variationContextAccessor, - IUmbracoContextAccessor umbracoContextAccessor, - IDocumentRepository documentRepository, - IMediaRepository mediaRepository, - IMemberRepository memberRepository, - IDefaultCultureAccessor defaultCultureAccessor, - ILoggerFactory loggerFactory, - GlobalSettings globalSettings, - IHostingEnvironment hostingEnvironment, - IApplicationShutdownRegistry hostingLifetime, - IShortStringHelper shortStringHelper, - IEntityXmlSerializer entitySerializer, - MainDom mainDom, - bool testing = false, - bool enableRepositoryEvents = true) - : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, - publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor, - documentRepository, mediaRepository, memberRepository, - defaultCultureAccessor, - loggerFactory, globalSettings, hostingEnvironment, hostingLifetime, shortStringHelper, entitySerializer, null, mainDom, testing, enableRepositoryEvents) - { - _umbracoContextAccessor = umbracoContextAccessor; - } - - // used in some tests - internal XmlPublishedSnapshotService( - ServiceContext serviceContext, - IPublishedContentTypeFactory publishedContentTypeFactory, - IScopeProvider scopeProvider, - IAppCache requestCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, - IVariationContextAccessor variationContextAccessor, - IUmbracoContextAccessor umbracoContextAccessor, - IDocumentRepository documentRepository, - IMediaRepository mediaRepository, - IMemberRepository memberRepository, - IDefaultCultureAccessor defaultCultureAccessor, - ILoggerFactory loggerFactory, - GlobalSettings globalSettings, - IHostingEnvironment hostingEnvironment, - IApplicationShutdownRegistry hostingLifetime, - IShortStringHelper shortStringHelper, - IEntityXmlSerializer entitySerializer, - PublishedContentTypeCache contentTypeCache, - MainDom mainDom, - bool testing, - bool enableRepositoryEvents) - { - _routesCache = new RoutesCache(); - _publishedContentTypeFactory = publishedContentTypeFactory; - _contentTypeCache = contentTypeCache - ?? new PublishedContentTypeCache(serviceContext.ContentTypeService, serviceContext.MediaTypeService, serviceContext.MemberTypeService, publishedContentTypeFactory, loggerFactory.CreateLogger()); - - _xmlStore = new XmlStore(serviceContext.ContentTypeService, serviceContext.ContentService, scopeProvider, _routesCache, - _contentTypeCache, publishedSnapshotAccessor, mainDom, testing, enableRepositoryEvents, - documentRepository, mediaRepository, memberRepository, entitySerializer, hostingEnvironment, hostingLifetime, shortStringHelper); - - _domainService = serviceContext.DomainService; - _mediaService = serviceContext.MediaService; - _userService = serviceContext.UserService; - _defaultCultureAccessor = defaultCultureAccessor; - _variationContextAccessor = variationContextAccessor; - _requestCache = requestCache; - _umbracoContextAccessor = umbracoContextAccessor; - _globalSettings = globalSettings; - _entitySerializer = entitySerializer; - } - - public void Dispose() - { - _xmlStore.Dispose(); - } - - #endregion - - public IPublishedSnapshot CreatePublishedSnapshot(string previewToken) - { - // use _requestCache to store recursive properties lookup, etc. both in content - // and media cache. Life span should be the current request. Or, ideally - // the current caches, but that would mean creating an extra cache (StaticCache - // probably) so better use RequestCache. - - var domainCache = new DomainCache(_domainService, _defaultCultureAccessor); - - return new PublishedSnapshot( - new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _contentTypeCache, _routesCache, _variationContextAccessor, previewToken), - new PublishedMediaCache(_xmlStore, _mediaService, _userService, _requestCache, _contentTypeCache, _entitySerializer, _umbracoContextAccessor, _variationContextAccessor), - new PublishedMemberCache(_contentTypeCache, _variationContextAccessor), - domainCache); - } - - #region Xml specific - - /// - /// Gets the underlying XML store. - /// - public XmlStore XmlStore => _xmlStore; - - /// - /// Gets the underlying RoutesCache. - /// - public RoutesCache RoutesCache => _routesCache; - - public bool VerifyContentAndPreviewXml() - { - return XmlStore.VerifyContentAndPreviewXml(); - } - - public void RebuildContentAndPreviewXml() - { - XmlStore.RebuildContentAndPreviewXml(); - } - - public bool VerifyMediaXml() - { - return XmlStore.VerifyMediaXml(); - } - - public void RebuildMediaXml() - { - XmlStore.RebuildMediaXml(); - } - - public bool VerifyMemberXml() - { - return XmlStore.VerifyMemberXml(); - } - - public void RebuildMemberXml() - { - XmlStore.RebuildMemberXml(); - } - - #endregion - - #region Change management - - public void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) - { - _xmlStore.Notify(payloads, out draftChanged, out publishedChanged); - } - - public void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) - { - foreach (var payload in payloads) - PublishedMediaCache.ClearCache(payload.Id); - - anythingChanged = true; - } - - public void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) - { - _xmlStore.Notify(payloads); - if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) - _routesCache.Clear(); - } - - public void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) - { - _publishedContentTypeFactory.NotifyDataTypeChanges(payloads.Select(x => x.Id).ToArray()); - _xmlStore.Notify(payloads); - } - - public void Notify(DomainCacheRefresher.JsonPayload[] payloads) - { - _routesCache.Clear(); - } - - #endregion - - public void Rebuild(int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, IReadOnlyCollection memberTypeIds = null) { } - - public Task CollectAsync() => Task.CompletedTask; - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs deleted file mode 100644 index af0848010d..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs +++ /dev/null @@ -1,2058 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml; -using Microsoft.Extensions.Logging; -using Moq; -using NPoco; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Runtime; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Changes; -using Umbraco.Cms.Core.Services.Implement; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Xml; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Composing; -using Umbraco.Web.Scheduling; -using File = System.IO.File; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - /// - /// Represents the Xml storage for the Xml published cache. - /// - /// - /// One instance of is instantiated by the and - /// then passed to all instances that are created (one per request). - /// This class should *not* be public. - /// - internal class XmlStore : - IDisposable, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler - { - private readonly IDocumentRepository _documentRepository; - private readonly IMediaRepository _mediaRepository; - private readonly IMemberRepository _memberRepository; - private readonly IEntityXmlSerializer _entitySerializer; - private readonly IApplicationShutdownRegistry _hostingLifetime; - private readonly IShortStringHelper _shortStringHelper; - private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - private readonly PublishedContentTypeCache _contentTypeCache; - private readonly RoutesCache _routesCache; - private readonly IContentTypeService _contentTypeService; - private readonly IContentService _contentService; - private readonly IScopeProvider _scopeProvider; - - private XmlStoreFilePersister _persisterTask; - private volatile bool _released; - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The default constructor will boot the cache, load data from file or database, /// wire events in order to manage changes, etc. - public XmlStore(IContentTypeService contentTypeService, IContentService contentService, IScopeProvider scopeProvider, RoutesCache routesCache, PublishedContentTypeCache contentTypeCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, MainDom mainDom, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IEntityXmlSerializer entitySerializer, IHostingEnvironment hostingEnvironment, IApplicationShutdownRegistry hostingLifetime, IShortStringHelper shortStringHelper) - : this(contentTypeService, contentService, scopeProvider, routesCache, contentTypeCache, publishedSnapshotAccessor, mainDom, false, false, documentRepository, mediaRepository, memberRepository, entitySerializer, hostingEnvironment, hostingLifetime, shortStringHelper) - { } - - // internal for unit tests - // no file nor db, no config check - // TODO: er, we DO have a DB? - internal XmlStore(IContentTypeService contentTypeService, IContentService contentService, IScopeProvider scopeProvider, RoutesCache routesCache, PublishedContentTypeCache contentTypeCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, MainDom mainDom, - bool testing, bool enableRepositoryEvents, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IEntityXmlSerializer entitySerializer, IHostingEnvironment hostingEnvironment, IApplicationShutdownRegistry hostingLifetime, IShortStringHelper shortStringHelper) - { - if (testing == false) - EnsureConfigurationIsValid(); - - _contentTypeService = contentTypeService; - _contentService = contentService; - _scopeProvider = scopeProvider; - _routesCache = routesCache; - _contentTypeCache = contentTypeCache; - _publishedSnapshotAccessor = publishedSnapshotAccessor; - _documentRepository = documentRepository; - _mediaRepository = mediaRepository; - _memberRepository = memberRepository; - _entitySerializer = entitySerializer; - _hostingLifetime = hostingLifetime; - _shortStringHelper = shortStringHelper; - - _xmlFileName = TestHelper.IOHelper.MapPath(SystemFiles.GetContentCacheXml(hostingEnvironment)); - - if (testing) - { - _xmlFileEnabled = false; - } - else - { - InitializeFilePersister(mainDom); - } - - Initialize(testing, enableRepositoryEvents); - } - - // internal for unit tests - // initialize with an xml document - // no events, no file nor db, no config check - internal XmlStore(XmlDocument xmlDocument, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IHostingEnvironment hostingEnvironment) - { - _xmlDocument = xmlDocument; - _documentRepository = documentRepository; - _mediaRepository = mediaRepository; - _memberRepository = memberRepository; - _xmlFileEnabled = false; - _xmlFileName = TestHelper.IOHelper.MapPath(SystemFiles.GetContentCacheXml(hostingEnvironment)); - // do not plug events, we may not have what it takes to handle them - } - - // internal for unit tests - // initialize with a function returning an xml document - // no events, no file nor db, no config check - internal XmlStore(Func getXmlDocument, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IHostingEnvironment hostingEnvironment) - { - _documentRepository = documentRepository; - _mediaRepository = mediaRepository; - _memberRepository = memberRepository; - GetXmlDocument = getXmlDocument ?? throw new ArgumentNullException(nameof(getXmlDocument)); - _xmlFileEnabled = false; - _xmlFileName = TestHelper.IOHelper.MapPath(SystemFiles.GetContentCacheXml(hostingEnvironment)); - // do not plug events, we may not have what it takes to handle them - } - - private void InitializeFilePersister(MainDom mainDom) - { - if (SyncToXmlFile == false) return; - - var loggerFactory = Current.LoggerFactory; - - // there's always be one task keeping a ref to the runner - // so it's safe to just create it as a local var here - var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions - { - LongRunning = true, - KeepAlive = true, - Hosted = false // main domain will take care of stopping the runner (see below) - }, loggerFactory.CreateLogger>(), _hostingLifetime); - - // create (and add to runner) - _persisterTask = new XmlStoreFilePersister(runner, this, loggerFactory.CreateLogger()); - - var registered = mainDom.Register( - null, - () => - { - // once released, the cache still works but does not write to file anymore, - // which is OK with database server messenger but will cause data loss with - // another messenger... - - runner.Shutdown(false, true); // wait until flushed - _persisterTask = null; // fail fast - _released = true; - }); - - // failed to become the main domain, we will never use the file - if (registered == false) - runner.Shutdown(false, true); - - _released = registered == false; - } - - private void Initialize(bool testing, bool enableRepositoryEvents) - { - - // not so soon! if eg installing we may not be able to load content yet - // so replace this by LazyInitializeContent() called in Xml ppty getter - //InitializeContent(); - } - - private void LazyInitializeContent() - { - if (_xml != null) return; - - // and populate the cache - using (var safeXml = GetSafeXmlWriter()) - { - if (_xml != null) return; // double-check - - // if we don't use the file then LoadXmlLocked will not even - // read from the file and will go straight to database - LoadXmlLocked(safeXml, out bool registerXmlChange); - - // if we use the file and registerXmlChange is true this will - // write to file, else it will not - safeXml.AcceptChanges(registerXmlChange); - } - } - - public void Dispose() - { - } - - #endregion - - #region Configuration - - // gathering configuration options here to document what they mean - - private readonly bool _xmlFileEnabled = true; - - // whether the disk cache is enabled - private bool XmlFileEnabled => true; - - // whether the disk cache is enabled and to update the disk cache when xml changes - private bool SyncToXmlFile => true; - - // whether the disk cache is enabled and to reload from disk cache if it changes - private bool SyncFromXmlFile => false; - - // whether _xml is immutable or not (achieved by cloning before changing anything) - private static bool XmlIsImmutable => true; - - // whether to keep version of everything (incl. medias & members) in cmsPreviewXml - // for audit purposes - false by default, not in umbracoSettings.config - // whether to... no idea what that one does - // it is false by default and not in UmbracoSettings.config anymore - ignoring - /* - private static bool GlobalPreviewStorageEnabled - { - get { return UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled; } - } - */ - - // ensures config is valid - private void EnsureConfigurationIsValid() - { - if (SyncToXmlFile && SyncFromXmlFile) - throw new Exception("Cannot run with both ContinouslyUpdateXmlDiskCache and XmlContentCheckForDiskChanges being true."); - - if (XmlIsImmutable == false) - //Current.Logger.LogWarning("Running with CloneXmlContent being false is a bad idea."); - Current.Logger.LogWarning("CloneXmlContent is false - ignored, we always clone."); - - // note: if SyncFromXmlFile then we should also disable / warn that local edits are going to cause issues... - } - - #endregion - - #region Xml - - /// - /// Gets or sets the delegate used to retrieve the Xml content, used for unit tests, else should - /// be null and then the default content will be used. For non-preview content only. - /// - /// - /// The default content ONLY works when in the context an Http Request mostly because the - /// 'content' object heavily relies on HttpContext, SQL connections and a bunch of other stuff - /// that when run inside of a unit test fails. - /// - public Func GetXmlDocument { get; set; } - - private XmlDocument _xmlDocument; // supplied xml document (for tests) - private volatile XmlDocument _xml; // master xml document - private readonly SystemLock _xmlLock = new SystemLock(); // protects _xml - - // to be used by PublishedContentCache only - // for non-preview content only - public XmlDocument Xml - { - get - { - if (_xml != null) - return _xml; - - if (_xmlDocument != null) - { - _xml = _xmlDocument; - _xmlDocument = null; - return _xml; - } - - if (GetXmlDocument != null) - return _xml = GetXmlDocument(); - - LazyInitializeContent(); - ReloadXmlFromFileIfChanged(); - return _xml; - } - } - - // Gets the temp. Xml managed by SafeXmlReaderWrite, if any - public XmlDocument TempXml => SafeXmlReaderWriter.Get(_scopeProvider)?.Xml; - - // assumes xml lock - private void SetXmlLocked(XmlDocument xml, bool registerXmlChange) - { - // this is the ONLY place where we write to _xml - _xml = xml; - - _routesCache?.Clear(); // anytime we set _xml - - if (registerXmlChange == false || SyncToXmlFile == false) - return; - - _persisterTask = _persisterTask?.Touch(); - } - - private static XmlDocument EnsureSchema(string contentTypeAlias, XmlDocument xml) - { - string subset = null; - - // get current doctype - var n = xml.FirstChild; - while (n.NodeType != XmlNodeType.DocumentType && n.NextSibling != null) - n = n.NextSibling; - if (n.NodeType == XmlNodeType.DocumentType) - subset = ((XmlDocumentType)n).InternalSubset; - - // ensure it contains the content type - if (subset != null && subset.Contains($"")) - return xml; - - // alas, that does not work, replacing a doctype is ignored and GetElementById fails - // - //// remove current doctype, set new doctype - //xml.RemoveChild(n); - //subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); - //var doctype = xml.CreateDocumentType("root", null, null, subset); - //xml.InsertAfter(doctype, xml.FirstChild); - - var xml2 = new XmlDocument(); - subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); - var doctype = xml2.CreateDocumentType("root", null, null, subset); - xml2.AppendChild(doctype); - xml2.AppendChild(xml2.ImportNode(xml.DocumentElement, true)); - return xml2; - } - - private static void InitializeXml(XmlDocument xml, string dtd) - { - // prime the xml document with an inline dtd and a root element - xml.LoadXml(string.Format("{0}{1}{0}", Environment.NewLine, dtd)); - } - - /// - /// Generates the complete (simplified) XML DTD. - /// - /// The DTD as a string - private string GetDtd() - { - var dtd = new StringBuilder(); - dtd.AppendLine(" x.Alias.ToSafeAlias(_shortStringHelper)).WhereNotNull(); - foreach (var alias in aliases) - { - dtdInner.AppendLine($""); - dtdInner.AppendLine($""); - } - dtd.Append(dtdInner); - } - catch (Exception ex) - { - Current.Logger.LogError(ex, "Failed to build a DTD for the Xml cache."); - } - - dtd.AppendLine("]>"); - return dtd.ToString(); - } - - // try to load from file, otherwise database - // assumes xml lock (file is always locked) - private void LoadXmlLocked(SafeXmlReaderWriter safeXml, out bool registerXmlChange) - { - Current.Logger.LogDebug("Loading Xml..."); - - // try to get it from the file - if (XmlFileEnabled && (safeXml.Xml = LoadXmlFromFile()) != null) - { - registerXmlChange = false; // loaded from disk, do NOT write back to disk! - return; - } - - // get it from the database, and register - LoadXmlTreeFromDatabaseLocked(safeXml); - registerXmlChange = true; - } - - public XmlNode GetMediaXmlNode(int mediaId) - { - // there's only one version for medias - - const string sql = @"SELECT umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.Level, -cmsContentXml.xml, 1 AS published -FROM umbracoNode -JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) -WHERE umbracoNode.nodeObjectType = @nodeObjectType -AND (umbracoNode.id=@id)"; - - XmlDto xmlDto; - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.MediaTree); - var xmlDtos = scope.Database.Query(sql, - new - { - nodeObjectType = Constants.ObjectTypes.Media, - id = mediaId - }); - xmlDto = xmlDtos.FirstOrDefault(); - scope.Complete(); - } - - if (xmlDto == null) return null; - - var doc = new XmlDocument(); - var xml = doc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); - return xml; - } - - public XmlNode GetMemberXmlNode(int memberId) - { - // there's only one version for members - - const string sql = @"SELECT umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.Level, -cmsContentXml.xml, 1 AS published -FROM umbracoNode -JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) -WHERE umbracoNode.nodeObjectType = @nodeObjectType -AND (umbracoNode.id=@id)"; - - XmlDto xmlDto; - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.MemberTree); - var xmlDtos = scope.Database.Query(sql, - new - { - nodeObjectType = Constants.ObjectTypes.Member, - id = memberId - }); - xmlDto = xmlDtos.FirstOrDefault(); - scope.Complete(); - } - - if (xmlDto == null) return null; - - var doc = new XmlDocument(); - var xml = doc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); - return xml; - } - - private static readonly string PreviewXmlNodeSql = $@"SELECT umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.Level, -cmsPreviewXml.xml, {Constants.DatabaseSchema.Tables.Document}.published -FROM umbracoNode -JOIN cmsPreviewXml ON (cmsPreviewXml.nodeId=umbracoNode.id) -JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) -WHERE umbracoNode.nodeObjectType = @nodeObjectType -AND (umbracoNode.id=@id)"; - - public XmlNode GetPreviewXmlNode(int contentId) - { - var sql = PreviewXmlNodeSql; - - XmlDto xmlDto; - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.ContentTree); - var xmlDtos = scope.Database.Query(sql, - new - { - nodeObjectType = Constants.ObjectTypes.Document, - id = contentId - }); - xmlDto = xmlDtos.FirstOrDefault(); - scope.Complete(); - } - if (xmlDto == null) return null; - - var doc = new XmlDocument(); - var xml = doc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); - if (xml?.Attributes == null) return null; - - if (xmlDto.Published == false) - xml.Attributes.Append(doc.CreateAttribute("isDraft")); - return xml; - } - - public XmlDocument GetMediaXml() - { - // this is not efficient at all, not cached, nothing - // just here to replicate what uQuery was doing and show it can be done - // but really - should not be used - - return LoadMoreXmlFromDatabase(Constants.ObjectTypes.Media); - } - - public XmlDocument GetMemberXml() - { - // this is not efficient at all, not cached, nothing - // just here to replicate what uQuery was doing and show it can be done - // but really - should not be used - - return LoadMoreXmlFromDatabase(Constants.ObjectTypes.Member); - } - - public XmlDocument GetPreviewXml(int contentId, bool includeSubs) - { - var content = _contentService.GetById(contentId); - - var doc = (XmlDocument)Xml.Clone(); - if (content == null) return doc; - - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.ContentTree); - var sqlSyntax = scope.SqlContext.SqlSyntax; - - var sql = ReadCmsPreviewXmlSql1; - sql += " @path LIKE " + sqlSyntax.GetConcat("umbracoNode.Path", "',%"); // concat(umbracoNode.path, ',%') - if (includeSubs) sql += " OR umbracoNode.path LIKE " + sqlSyntax.GetConcat("@path", "',%"); // concat(@path, ',%') - sql += ReadCmsPreviewXmlSql2; - - var xmlDtos = scope.Database.Query(sql, - new - { - nodeObjectType = Constants.ObjectTypes.Document, - path = content.Path, - }); - - foreach (var xmlDto in xmlDtos) - { - var xml = xmlDto.XmlNode = doc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); - if (xml?.Attributes == null) continue; - if (xmlDto.Published == false) - xml.Attributes.Append(doc.CreateAttribute("isDraft")); - doc = AddOrUpdateXmlNode(doc, xmlDto); - } - - scope.Complete(); - } - - return doc; - } - - // NOTE - // - this is NOT a reader/writer lock and each lock is exclusive - // - these locks are NOT reentrant / recursive - // - // should we have async versions that would do: ? - // var releaser = await _xmlLock.LockAsync(); - // - // TODO: not sure about the "resync current published snapshot" thing here, see 7.6... - - // gets a locked safe read access to the main xml - private SafeXmlReaderWriter GetSafeXmlReader() - { - return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xml, - ResyncCurrentPublishedSnapshot, - (xml, registerXmlChange) => - { - SetXmlLocked(xml, registerXmlChange); - ResyncCurrentPublishedSnapshot(xml); - }, false); - } - - // gets a locked safe write access to the main xml (cloned) - private SafeXmlReaderWriter GetSafeXmlWriter() - { - return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xml, - ResyncCurrentPublishedSnapshot, - (xml, registerXmlChange) => - { - SetXmlLocked(xml, registerXmlChange); - ResyncCurrentPublishedSnapshot(xml); - }, true); - } - - private const string ChildNodesXPath = "./* [@id]"; - private const string DataNodesXPath = "./* [not(@id)]"; - - #endregion - - #region File - - private readonly string _xmlFileName; - private DateTime _lastFileRead; // last time the file was read - private DateTime _nextFileCheck; // last time we checked whether the file was changed - - public void EnsureFilePermission() - { - // TODO: but do we really have a store, initialized, at that point? - var filename = _xmlFileName + ".temp"; - File.WriteAllText(filename, "TEMP"); - File.Delete(filename); - } - - // not used - just try to read the file - //private bool XmlFileExists - //{ - // get - // { - // // check that the file exists and has content (is not empty) - // var fileInfo = new FileInfo(_xmlFileName); - // return fileInfo.Exists && fileInfo.Length > 0; - // } - //} - - private DateTime XmlFileLastWriteTime - { - get - { - var fileInfo = new FileInfo(_xmlFileName); - return fileInfo.Exists ? fileInfo.LastWriteTimeUtc : DateTime.MinValue; - } - } - - // invoked by XmlStoreFilePersister ONLY and that one manages the MainDom, ie it - // will NOT try to save once the current app domain is not the main domain anymore - // (no need to test _released) - internal void SaveXmlToFile() - { - Current.Logger.LogInformation("Save Xml to file..."); - - try - { - var xml = _xml; // capture (atomic + volatile), immutable anyway - if (xml == null) return; - - // delete existing file, if any - DeleteXmlFile(); - - // ensure cache directory exists - var directoryName = Path.GetDirectoryName(_xmlFileName); - if (directoryName == null) - throw new Exception($"Invalid XmlFileName \"{_xmlFileName}\"."); - if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) - Directory.CreateDirectory(directoryName); - - // save - using (var fs = new FileStream(_xmlFileName, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize: 4096, useAsync: true)) - { - var bytes = Encoding.UTF8.GetBytes(SaveXmlToString(xml)); - fs.Write(bytes, 0, bytes.Length); - } - - Current.Logger.LogInformation("Saved Xml to file."); - } - catch (Exception ex) - { - // if something goes wrong remove the file - DeleteXmlFile(); - - Current.Logger.LogError(ex, "Failed to save Xml to file '{FileName}'.", _xmlFileName); - } - } - - // invoked by XmlStoreFilePersister ONLY and that one manages the MainDom, ie it - // will NOT try to save once the current app domain is not the main domain anymore - // (no need to test _released) - internal async Task SaveXmlToFileAsync() - { - Current.Logger.LogInformation("Save Xml to file..."); - - try - { - var xml = _xml; // capture (atomic + volatile), immutable anyway - if (xml == null) return; - - // delete existing file, if any - DeleteXmlFile(); - - // ensure cache directory exists - var directoryName = Path.GetDirectoryName(_xmlFileName); - if (directoryName == null) - throw new Exception($"Invalid XmlFileName \"{_xmlFileName}\"."); - if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) - Directory.CreateDirectory(directoryName); - - // save - using (var fs = new FileStream(_xmlFileName, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize: 4096, useAsync: true)) - { - var bytes = Encoding.UTF8.GetBytes(SaveXmlToString(xml)); - await fs.WriteAsync(bytes, 0, bytes.Length); - } - - Current.Logger.LogInformation("Saved Xml to file."); - } - catch (Exception ex) - { - // if something goes wrong remove the file - DeleteXmlFile(); - - Current.Logger.LogError(ex, "Failed to save Xml to file '{FileName}'.", _xmlFileName); - } - } - - private static string SaveXmlToString(XmlDocument xml) - { - // using that one method because we want to have proper indent - // and in addition, writing async is never fully async because - // although the writer is async, xml.WriteTo() will not async - - // that one almost works but... "The elements are indented as long as the element - // does not contain mixed content. Once the WriteString or WriteWhitespace method - // is called to write out a mixed element content, the XmlWriter stops indenting. - // The indenting resumes once the mixed content element is closed." - says MSDN - // about XmlWriterSettings.Indent - - // so ImportContentBase must also make sure of ignoring whitespaces! - - var sb = new StringBuilder(); - using (var xmlWriter = XmlWriter.Create(sb, new XmlWriterSettings - { - Indent = true, - Encoding = Encoding.UTF8, - //OmitXmlDeclaration = true - })) - { - //xmlWriter.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""); - xml.WriteTo(xmlWriter); // already contains the xml declaration - } - return sb.ToString(); - } - - private XmlDocument LoadXmlFromFile() - { - // do NOT try to load if we are not the main domain anymore - if (_released) return null; - - Current.Logger.LogInformation("Load Xml from file..."); - - try - { - var xml = new XmlDocument(); - using (var fs = new FileStream(_xmlFileName, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - xml.Load(fs); - } - _lastFileRead = DateTime.UtcNow; - Current.Logger.LogInformation("Loaded Xml from file."); - return xml; - } - catch (FileNotFoundException) - { - Current.Logger.LogWarning("Failed to load Xml, file does not exist."); - return null; - } - catch (Exception ex) - { - Current.Logger.LogError(ex, "Failed to load Xml from file '{FileName}'.", _xmlFileName); - try - { - DeleteXmlFile(); - } - catch - { - // don't make it worse: could be that we failed to read because we cannot - // access the file, in which case we won't be able to delete it either - } - return null; - } - } - - private void DeleteXmlFile() - { - if (File.Exists(_xmlFileName) == false) return; - File.SetAttributes(_xmlFileName, FileAttributes.Normal); - File.Delete(_xmlFileName); - } - - private void ReloadXmlFromFileIfChanged() - { - if (SyncFromXmlFile == false) return; - - var now = DateTime.UtcNow; - if (now < _nextFileCheck) return; - - // time to check - _nextFileCheck = now.AddSeconds(1); // check every 1s - if (XmlFileLastWriteTime <= _lastFileRead) return; - - Current.Logger.LogDebug("Xml file change detected, reloading."); - - // time to read - - using (var safeXml = GetSafeXmlWriter()) - { - LoadXmlLocked(safeXml, out bool registerXmlChange); // updates _lastFileRead - safeXml.AcceptChanges(registerXmlChange); - } - } - - #endregion - - #region Database - - private static readonly string ReadTreeCmsContentXmlSql = $@"SELECT - umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, - cmsContentXml.xml, cmsContentXml.rv, {Constants.DatabaseSchema.Tables.Document}.published -FROM umbracoNode -JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) -JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) -WHERE umbracoNode.nodeObjectType = @nodeObjectType AND {Constants.DatabaseSchema.Tables.Document}.published=1 -ORDER BY umbracoNode.level, umbracoNode.sortOrder"; - - private static readonly string ReadBranchCmsContentXmlSql = $@"SELECT - umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, - cmsContentXml.xml, cmsContentXml.rv, {Constants.DatabaseSchema.Tables.Document}.published -FROM umbracoNode -JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) -JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) -WHERE umbracoNode.nodeObjectType = @nodeObjectType AND {Constants.DatabaseSchema.Tables.Document}.published=1 AND (umbracoNode.id = @id OR umbracoNode.path LIKE @path) -ORDER BY umbracoNode.level, umbracoNode.sortOrder"; - - private static readonly string ReadCmsContentXmlForContentTypesSql = $@"SELECT - umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, - cmsContentXml.xml, cmsContentXml.rv, {Constants.DatabaseSchema.Tables.Document}.published -FROM umbracoNode -JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) -JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) -JOIN {Constants.DatabaseSchema.Tables.Content} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) -WHERE umbracoNode.nodeObjectType = @nodeObjectType AND {Constants.DatabaseSchema.Tables.Document}.published=1 AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ids) -ORDER BY umbracoNode.level, umbracoNode.sortOrder"; - - private const string ReadMoreCmsContentXmlSql = @"SELECT - umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, - cmsContentXml.xml, cmsContentXml.rv, 1 AS published -FROM umbracoNode -JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) -WHERE umbracoNode.nodeObjectType = @nodeObjectType -ORDER BY umbracoNode.level, umbracoNode.sortOrder"; - - private static readonly string ReadCmsPreviewXmlSql1 = $@"SELECT - umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, - cmsPreviewXml.xml, cmsPreviewXml.rv, {Constants.DatabaseSchema.Tables.Document}.published -FROM umbracoNode -JOIN cmsPreviewXml ON (cmsPreviewXml.nodeId=umbracoNode.id) -JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) -WHERE umbracoNode.nodeObjectType = @nodeObjectType AND {Constants.DatabaseSchema.Tables.Document}.published=1 -AND (umbracoNode.path=@path OR"; // @path LIKE concat(umbracoNode.path, ',%')"; - - private const string ReadCmsPreviewXmlSql2 = @") -ORDER BY umbracoNode.level, umbracoNode.sortOrder"; - - // ReSharper disable once ClassNeverInstantiated.Local - private class XmlDto - { - // ReSharper disable UnusedAutoPropertyAccessor.Local - - public int Id { get; set; } - public long Rv { get; set; } - public int ParentId { get; set; } - //public int SortOrder { get; set; } - public int Level { get; set; } - public string Path { get; set; } - public string Xml { get; set; } - public bool Published { get; set; } - - [Ignore] - public XmlNode XmlNode { get; set; } - - // ReSharper restore UnusedAutoPropertyAccessor.Local - } - - // assumes xml lock - private void LoadXmlTreeFromDatabaseLocked(SafeXmlReaderWriter safeXml) - { - // initialize the document ready for the composition of content - var xml = new XmlDocument(); - InitializeXml(xml, GetDtd()); - - XmlNode parent = null; - var parentId = 0; - - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.ContentTree); - - // get xml - var xmlDtos = scope.Database.Query(ReadTreeCmsContentXmlSql, - new { nodeObjectType = Constants.ObjectTypes.Document }); - - foreach (var xmlDto in xmlDtos) - { - xmlDto.XmlNode = ImportContent(xml, xmlDto); // parse into a DOM node - - if (parent == null || parentId != xmlDto.ParentId) - { - parent = xmlDto.ParentId == -1 - ? xml.DocumentElement - : xml.GetElementById(xmlDto.ParentId.ToInvariantString()); - - if (parent == null) continue; - - parentId = xmlDto.ParentId; - } - - parent.AppendChild(xmlDto.XmlNode); - } - - scope.Complete(); - } - - safeXml.Xml = xml; - } - - private XmlDocument LoadMoreXmlFromDatabase(Guid nodeObjectType) - { - var xmlDoc = new XmlDocument(); - - using (var scope = _scopeProvider.CreateScope()) - { - if (nodeObjectType == Constants.ObjectTypes.Document) - scope.ReadLock(Constants.Locks.ContentTree); - else if (nodeObjectType == Constants.ObjectTypes.Media) - scope.ReadLock(Constants.Locks.MediaTree); - else if (nodeObjectType == Constants.ObjectTypes.Member) - scope.ReadLock(Constants.Locks.MemberTree); - - var xmlDtos = scope.Database.Query(ReadMoreCmsContentXmlSql, - new { /*@nodeObjectType =*/ nodeObjectType }); - - // Initialize the document ready for the final composition of content - InitializeXml(xmlDoc, string.Empty); - - XmlNode parent = null; - var parentId = 0; - - foreach (var xmlDto in xmlDtos) - { - // and parse it into a DOM node - var node = xmlDoc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml), new XmlReaderSettings - { - IgnoreWhitespace = true - })); - - if (parent == null || parentId != xmlDto.ParentId) - { - parent = xmlDto.ParentId == -1 - ? xmlDoc.DocumentElement - : xmlDoc.GetElementById(xmlDto.ParentId.ToInvariantString()); - - if (parent == null) - continue; - - parentId = xmlDto.ParentId; - } - - parent.AppendChild(node); - } - - scope.Complete(); - } - - return xmlDoc; - } - - // internal - used by umbraco.content.RefreshContentFromDatabase[Async] - internal void ReloadXmlFromDatabase() - { - // event - cancel - - // nobody should work on the Xml while we load - using (var safeXml = GetSafeXmlWriter()) - { - LoadXmlTreeFromDatabaseLocked(safeXml); - safeXml.AcceptChanges(); - } - } - - #endregion - - #region Handle Distributed Notifications for Memory Xml - - // NOT using events, see notes in IPublishedCachesService - - public void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) - { - draftChanged = publishedChanged = false; - if (_xml == null) return; // not initialized yet! - - draftChanged = true; // by default - we don't track drafts - publishedChanged = false; - - // process all changes on one xml clone - using (var safeXml = GetSafeXmlWriter()) - { - foreach (var payload in payloads) - { - Current.Logger.LogDebug("Notified {ChangeTypes} for content {ContentId}", payload.ChangeTypes, payload.Id); - - if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) - { - LoadXmlTreeFromDatabaseLocked(safeXml); - publishedChanged = true; - continue; - } - - if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) - { - var toRemove = safeXml.Xml.GetElementById(payload.Id.ToInvariantString()); - if (toRemove != null) - { - if (toRemove.ParentNode == null) throw new Exception("oops"); - toRemove.ParentNode.RemoveChild(toRemove); - publishedChanged = true; - } - continue; - } - - if (payload.ChangeTypes.HasTypesNone(TreeChangeTypes.RefreshNode | TreeChangeTypes.RefreshBranch)) - { - // ?! - continue; - } - - var content = _contentService.GetById(payload.Id); - var current = safeXml.Xml.GetElementById(payload.Id.ToInvariantString()); - - if (content == null || content.Published == false || content.Trashed) - { - // no published version - Current.Logger.LogDebug("Notified, content {ContentId} has no published version.", payload.Id); - - if (current != null) - { - // remove from xml if exists - if (current.ParentNode == null) throw new Exception("oops"); - current.ParentNode.RemoveChild(current); - publishedChanged = true; - } - - continue; - } - - // else we have a published version - - // get xml - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.ContentTree); - - // that query is yielding results so will only load what's needed - var xmlDtos = scope.Database.Query(ReadBranchCmsContentXmlSql, - new - { - nodeObjectType = Constants.ObjectTypes.Document, - path = content.Path + ",%", - id = content.Id - }); - - // 'using' the enumerator ensures that the enumeration is properly terminated even if abandoned - // otherwise, it would leak an open reader & an un-released database connection - // see PetaPoco.Query(Type[] types, Delegate cb, string sql, params object[] args) - // and read http://blogs.msdn.com/b/oldnewthing/archive/2008/08/14/8862242.aspx - // - using (var dtos = xmlDtos.GetEnumerator()) - { - if (dtos.MoveNext() == false) - { - // gone fishing, remove (possible race condition) - Current.Logger.LogDebug("Notified, content {ContentId} gone fishing.", payload.Id); - - if (current != null) - { - // remove from xml if exists - if (current.ParentNode == null) throw new Exception("oops"); - current.ParentNode.RemoveChild(current); - publishedChanged = true; - } - continue; - } - - if (dtos.Current.Id != content.Id) - throw new Exception("oops"); // first one should be 'current' - var currentDto = dtos.Current; - - // note: if anything eg parentId or path or level has changed, then rv has changed too - var currentRv = current == null ? -1 : int.Parse(current.Attributes["rv"].Value); - - // if exists and unchanged and not refreshing the branch, skip entirely - if (current != null - && currentRv == currentDto.Rv - && payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch) == false) - continue; - - currentDto.XmlNode = ImportContent(safeXml.Xml, currentDto); - - // note: Examine would not be able to do the path trick below, and we cannot help for - // unpublished content, so it *is* possible that Examine is inconsistent for a while, - // though events should get it consistent eventually. - - // note: if path has changed we must do a branch refresh, even if the event is not requiring - // it, otherwise we would update the local node and not its children, who would then have - // inconsistent level (and path) attributes. - - var refreshBranch = current == null - || payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch) - || current.Attributes["path"].Value != currentDto.Path; - - if (refreshBranch) - { - // remove node if exists - if (current != null) - { - if (current.ParentNode == null) throw new Exception("oops"); - current.ParentNode.RemoveChild(current); - } - - // insert node - var newParent = currentDto.ParentId == -1 - ? safeXml.Xml.DocumentElement - : safeXml.Xml.GetElementById(currentDto.ParentId.ToInvariantString()); - if (newParent == null) continue; - newParent.AppendChild(currentDto.XmlNode); - XmlHelper.SortNode(newParent, ChildNodesXPath, currentDto.XmlNode, - x => x.AttributeValue("sortOrder")); - - // add branch (don't try to be clever) - while (dtos.MoveNext()) - { - // dtos are ordered by sortOrder already - var dto = dtos.Current; - - // if node is already there, somewhere, remove - var n = safeXml.Xml.GetElementById(dto.Id.ToInvariantString()); - if (n != null) - { - if (n.ParentNode == null) throw new Exception("oops"); - n.ParentNode.RemoveChild(n); - } - - // find parent, add node - var p = safeXml.Xml.GetElementById(dto.ParentId.ToInvariantString()); // branch, so parentId > 0 - // takes care of out-of-sync & masked - p?.AppendChild(dto.XmlNode); - } - } - else - { - // in-place - safeXml.Xml = AddOrUpdateXmlNode(safeXml.Xml, currentDto); - } - } - - scope.Complete(); - } - - publishedChanged = true; - } - - if (publishedChanged) - safeXml.AcceptChanges(); - } - } - - public void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) - { - if (_xml == null) return; // not initialized yet! - - // see ContentTypeServiceBase - // in all cases we just want to clear the content type cache - // the type will be reloaded if/when needed - foreach (var payload in payloads) - _contentTypeCache.ClearContentType(payload.Id); - - // process content types / content cache - // only those that have been changed - with impact on content - RefreshMain - // for those that have been removed, content is removed already - var ids = payloads - .Where(x => x.ItemType == typeof(IContentType).Name && x.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain)) - .Select(x => x.Id) - .ToArray(); - - foreach (var payload in payloads) - Current.Logger.LogDebug("Notified {ChangeTypes} for content type {ContentTypeId}", payload.ChangeTypes, payload.Id); - - if (ids.Length > 0) // must have refreshes, not only removes - RefreshContentTypes(ids); - - // ignore media and member types - we're not caching them - } - - public void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) - { - if (_xml == null) return; // not initialized yet! - - // see above - // in all cases we just want to clear the content type cache - // the types will be reloaded if/when needed - foreach (var payload in payloads) - _contentTypeCache.ClearDataType(payload.Id); - - foreach (var payload in payloads) - Current.Logger.LogDebug("Notified {RemovedStatus} for data type {payload.Id}", - payload.Removed ? "Removed" : "Refreshed", - payload.Id); - - // that's all we need to do as the changes have NO impact whatsoever on the Xml content - - // ignore media and member types - we're not caching them - } - - private void ResyncCurrentPublishedSnapshot(XmlDocument xml) - { - var publishedSnapshot = (PublishedSnapshot) _publishedSnapshotAccessor.PublishedSnapshot; - if (publishedSnapshot == null) return; - ((PublishedContentCache) publishedSnapshot.Content).Resync(xml); - ((PublishedMediaCache) publishedSnapshot.Media).Resync(); - - // not trying to resync members or domains, which are not cached really - } - - #endregion - - #region Manage change - - private void RefreshContentTypes(IEnumerable ids) - { - using (var safeXml = GetSafeXmlWriter()) - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.ContentTree); - var xmlDtos = scope.Database.Query(ReadCmsContentXmlForContentTypesSql, - new { nodeObjectType = Constants.ObjectTypes.Document, /*@ids =*/ ids }); - - foreach (var xmlDto in xmlDtos) - { - xmlDto.XmlNode = safeXml.Xml.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); - safeXml.Xml = AddOrUpdateXmlNode(safeXml.Xml, xmlDto); - } - - scope.Complete(); - safeXml.AcceptChanges(); - } - } - - // nothing to do, we have no cache - //private void RefreshMediaTypes(IEnumerable ids) - //{ } - - // nothing to do, we have no cache - //private void RefreshMemberTypes(IEnumerable ids) - //{ } - - // adds or updates a node (docNode) into a cache (xml) - private static XmlDocument AddOrUpdateXmlNode(XmlDocument xml, XmlDto xmlDto) - { - // sanity checks - var docNode = xmlDto.XmlNode; - if (xmlDto.Id != docNode.AttributeValue("id")) - throw new ArgumentException("Values of id and docNode/@id are different."); - if (xmlDto.ParentId != docNode.AttributeValue("parentID")) - throw new ArgumentException("Values of parentId and docNode/@parentID are different."); - - // find the document in the cache - XmlNode currentNode = xml.GetElementById(xmlDto.Id.ToInvariantString()); - - // if the document is not there already then it's a new document - // we must make sure that its document type exists in the schema - if (currentNode == null) - { - var xml2 = EnsureSchema(docNode.Name, xml); - if (ReferenceEquals(xml, xml2) == false) - docNode = xml2.ImportNode(docNode, true); - xml = xml2; - } - - // find the parent - XmlNode parentNode = xmlDto.Level == 1 - ? xml.DocumentElement - : xml.GetElementById(xmlDto.ParentId.ToInvariantString()); - - // no parent = cannot do anything - if (parentNode == null) - return xml; - - // insert/move the node under the parent - if (currentNode == null) - { - // document not there, new node, append - currentNode = docNode; - parentNode.AppendChild(currentNode); - } - else - { - // document found... we could just copy the currentNode children nodes over under - // docNode, then remove currentNode and insert docNode... the code below tries to - // be clever and faster, though only benchmarking could tell whether it's worth the - // pain... - - // first copy current parent ID - so we can compare with target parent - var moving = currentNode.AttributeValue("parentID") != xmlDto.ParentId; - - if (docNode.Name == currentNode.Name) - { - // name has not changed, safe to just update the current node - // by transferring values eg copying the attributes, and importing the data elements - TransferValuesFromDocumentXmlToPublishedXml(docNode, currentNode); - - // if moving, move the node to the new parent - // else it's already under the right parent - // (but maybe the sort order has been updated) - if (moving) - parentNode.AppendChild(currentNode); // remove then append to parentNode - } - else - { - // name has changed, must use docNode (with new name) - // move children nodes from currentNode to docNode (already has properties) - var children = currentNode.SelectNodes(ChildNodesXPath); - if (children == null) throw new Exception("oops"); - foreach (XmlNode child in children) - docNode.AppendChild(child); // remove then append to docNode - - // and put docNode in the right place - if parent has not changed, then - // just replace, else remove currentNode and insert docNode under the right parent - // (but maybe not at the right position due to sort order) - if (moving) - { - if (currentNode.ParentNode == null) throw new Exception("oops"); - currentNode.ParentNode.RemoveChild(currentNode); - parentNode.AppendChild(docNode); - } - else - { - // replacing might screw the sort order - parentNode.ReplaceChild(docNode, currentNode); - } - - currentNode = docNode; - } - } - - var attrs = currentNode.Attributes; - if (attrs == null) throw new Exception("oops."); - - var attr = attrs["rv"] ?? attrs.Append(xml.CreateAttribute("rv")); - attr.Value = xmlDto.Rv.ToString(CultureInfo.InvariantCulture); - - attr = attrs["path"] ?? attrs.Append(xml.CreateAttribute("path")); - attr.Value = xmlDto.Path; - - // if the nodes are not ordered, must sort - // (see U4-509 + has to work with ReplaceChild too) - //XmlHelper.SortNodesIfNeeded(parentNode, childNodesXPath, x => x.AttributeValue("sortOrder")); - - // but... - // if we assume that nodes are always correctly sorted - // then we just need to ensure that currentNode is at the right position. - // should be faster that moving all the nodes around. - XmlHelper.SortNode(parentNode, ChildNodesXPath, currentNode, x => x.AttributeValue("sortOrder")); - return xml; - } - - private static void TransferValuesFromDocumentXmlToPublishedXml(XmlNode documentNode, XmlNode publishedNode) - { - // remove all attributes from the published node - if (publishedNode.Attributes == null) throw new Exception("oops"); - publishedNode.Attributes.RemoveAll(); - - // remove all data nodes from the published node - var dataNodes = publishedNode.SelectNodes(DataNodesXPath); - if (dataNodes == null) throw new Exception("oops"); - foreach (XmlNode n in dataNodes) - publishedNode.RemoveChild(n); - - // append all attributes from the document node to the published node - if (documentNode.Attributes == null) throw new Exception("oops"); - foreach (XmlAttribute att in documentNode.Attributes) - ((XmlElement)publishedNode).SetAttribute(att.Name, att.Value); - - // find the first child node, if any - var childNodes = publishedNode.SelectNodes(ChildNodesXPath); - if (childNodes == null) throw new Exception("oops"); - var firstChildNode = childNodes.Count == 0 ? null : childNodes[0]; - - // append all data nodes from the document node to the published node - dataNodes = documentNode.SelectNodes(DataNodesXPath); - if (dataNodes == null) throw new Exception("oops"); - foreach (XmlNode n in dataNodes) - { - if (publishedNode.OwnerDocument == null) throw new Exception("oops"); - var imported = publishedNode.OwnerDocument.ImportNode(n, true); - if (firstChildNode == null) - publishedNode.AppendChild(imported); - else - publishedNode.InsertBefore(imported, firstChildNode); - } - } - - private static XmlNode ImportContent(XmlDocument xml, XmlDto dto) - { - var node = xml.ReadNode(XmlReader.Create(new StringReader(dto.Xml), new XmlReaderSettings - { - IgnoreWhitespace = true - })); - - if (node == null) throw new Exception("oops"); - if (node.Attributes == null) throw new Exception("oops"); - - var attr = xml.CreateAttribute("rv"); - attr.Value = dto.Rv.ToString(CultureInfo.InvariantCulture); - node.Attributes.Append(attr); - - attr = xml.CreateAttribute("path"); - attr.Value = dto.Path; - node.Attributes.Append(attr); - - return node; - } - - #endregion - - #region Handle Repository Events For Database Xml - - // we need them to be "repository" events ie to trigger from within the repository transaction, - // because they need to be consistent with the content that is being refreshed/removed - and that - // should be guaranteed by a DB transaction - // it is not the case at the moment, instead a global lock is used whenever content is modified - well, - // almost: rollback or unpublish do not implement it - nevertheless - - public void Handle(ContentDeletingNotification notification) - { - foreach (IContent entity in notification.DeletedEntities) - { - // We used to do args.Scope.Database, but can't any more because it's not supported by the notification pattern - OnRemovedEntity(null, entity); - } - } - - public void Handle(MediaDeletingNotification notification) - { - foreach (IMedia entity in notification.DeletedEntities) - { - // We used to do args.Scope.Database, but can't any more because it's not supported by the notification pattern - OnRemovedEntity(null, entity); - } - } - - public void Handle(MemberDeletingNotification notification) - { - foreach (IMember entity in notification.DeletedEntities) - { - // We used to do args.Scope.Database, but can't any more because it's not supported by the notification pattern - OnRemovedEntity(null, entity); - } - } - - private static void OnRemovedEntity(IUmbracoDatabase db, IContentBase item) - { - var parms = new { id = item.Id }; - db.Execute("DELETE FROM cmsContentXml WHERE nodeId=@id", parms); - db.Execute("DELETE FROM cmsPreviewXml WHERE nodeId=@id", parms); - - // note: could be optimized by using "WHERE nodeId IN (...)" delete clauses - } - - public void Handle(ContentDeletingVersionsNotification notification) - { - OnRemovedVersion(null, notification.Id, notification.SpecificVersion); - } - - public void Handle(MediaDeletingVersionsNotification notification) - { - OnRemovedVersion(null, notification.Id, notification.SpecificVersion); - } - - private static void OnRemovedVersion(IUmbracoDatabase db, int entityId, int versionId) - { - // we do not version cmsPreviewXml anymore - nothing to do here - } - - private static readonly string[] PropertiesImpactingAllVersions = { "SortOrder", "ParentId", "Level", "Path", "Trashed" }; - - private static bool HasChangesImpactingAllVersions(IContent icontent) - { - var content = (Content)icontent; - - // UpdateDate will be dirty - // Published may be dirty if saving a Published entity - // so cannot do this (would always be true): - //return content.IsEntityDirty(); - - // have to be more precise & specify properties - return PropertiesImpactingAllVersions.Any(content.IsPropertyDirty); - } - - public void Handle(ContentRefreshNotification notification) - { - var db = Mock.Of(); // Notification no longer carries the scope, so we can't get the DB - var entity = notification.Entity; - - // serialize edit values for preview - var editXml = _entitySerializer.Serialize(entity, false).ToDataString(); - - // change below to write only one row - not one per version - var dto1 = new PreviewXmlDto - { - NodeId = entity.Id, - Xml = editXml - }; - OnRepositoryRefreshed(db, dto1); - - // if unpublishing, remove from table - - if (((Content) entity).PublishedState == PublishedState.Unpublishing) - { - db.Execute("DELETE FROM cmsContentXml WHERE nodeId=@id", new { id = entity.Id }); - return; - } - - // need to update the published xml if we're saving the published version, - // or having an impact on that version - we update the published xml even when masked - - // TODO: in the repo... either its 'unpublished' and 'publishing', or 'published' and 'published', this has changed! - // TODO: what are we serializing really? which properties? - - // if not publishing, no change to published xml - if (((Content) entity).PublishedState != PublishedState.Publishing) - return; - - // serialize published values for content cache - var publishedXml = _entitySerializer.Serialize(entity, true).ToDataString(); - var dto2 = new ContentXmlDto { NodeId = entity.Id, Xml = publishedXml }; - OnRepositoryRefreshed(db, dto2); - - } - - public void Handle(MediaRefreshNotification notification) - { - var db = Mock.Of(); // Notification no longer carries the scope, so we can't get the DB - var entity = notification.Entity; - - // for whatever reason we delete some xml when the media is trashed - // at least that's what the MediaService implementation did - if (entity.Trashed) - db.Execute("DELETE FROM cmsContentXml WHERE nodeId=@id", new { id = entity.Id }); - - var xml = _entitySerializer.Serialize(entity).ToDataString(); - - var dto1 = new ContentXmlDto { NodeId = entity.Id, Xml = xml }; - OnRepositoryRefreshed(db, dto1); - } - - public void Handle(MemberRefreshNotification notification) - { - var db = Mock.Of(); // Notification no longer carries the scope, so we can't get the DB - var entity = notification.Entity; - - var xml = _entitySerializer.Serialize(entity).ToDataString(); - - var dto1 = new ContentXmlDto { NodeId = entity.Id, Xml = xml }; - OnRepositoryRefreshed(db, dto1); - } - - private static void OnRepositoryRefreshed(IUmbracoDatabase db, ContentXmlDto dto) - { - // use a custom SQL to update row version on each update - //db.InsertOrUpdate(dto); - - db.InsertOrUpdate(dto, - "SET xml=@xml, rv=rv+1 WHERE nodeId=@id", - new - { - xml = dto.Xml, - id = dto.NodeId - }); - } - - private static void OnRepositoryRefreshed(IUmbracoDatabase db, PreviewXmlDto dto) - { - // cannot simply update because of PetaPoco handling of the composite key ;-( - // read http://stackoverflow.com/questions/11169144/how-to-modify-petapoco-class-to-work-with-composite-key-comprising-of-non-numeri - // it works in https://github.com/schotime/PetaPoco and then https://github.com/schotime/NPoco but not here - // - // not important anymore as we don't manage version anymore, - // but: - // - // also - // use a custom SQL to update row version on each update - //db.InsertOrUpdate(dto); - - db.InsertOrUpdate(dto, - "SET xml=@xml, rv=rv+1 WHERE nodeId=@id", - new - { - xml = dto.Xml, - id = dto.NodeId, - }); - } - - public void Handle(ContentTypeRefreshedNotification notification) - { - const ContentTypeChangeTypes types // only for those that have been refreshed - = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther | ContentTypeChangeTypes.Create; - var contentTypeIds = notification.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); - if (contentTypeIds.Any()) - RebuildContentAndPreviewXml(contentTypeIds: contentTypeIds); - } - - public void Handle(MediaTypeRefreshedNotification notification) - { - const ContentTypeChangeTypes types // only for those that have been refreshed - = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther | ContentTypeChangeTypes.Create; - var mediaTypeIds = notification.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); - if (mediaTypeIds.Any()) - { - RebuildMediaXml(contentTypeIds: mediaTypeIds); - } - } - - public void Handle(MemberTypeRefreshedNotification notification) - { - const ContentTypeChangeTypes types // only for those that have been refreshed - = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther | ContentTypeChangeTypes.Create; - var memberTypeIds = notification.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); - if (memberTypeIds.Any()) - RebuildMemberXml(contentTypeIds: memberTypeIds); - } - - #endregion - - #region Rebuild Database Xml - - // RepositoryCacheMode.Scoped because we do NOT want to use the L2 cache that may be out-of-sync - // hopefully this does not cause issues and we're not nested in another scope w/different mode - // TODO: well, guess what? - // original code made sure the repository used no cache - // now we're using the Scoped scope cache mode - // and then? - - public void RebuildContentAndPreviewXml(int groupSize = 5000, IEnumerable contentTypeIds = null) - { - var contentTypeIdsA = contentTypeIds?.ToArray(); - - using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) - { - scope.WriteLock(Constants.Locks.ContentTree); - RebuildContentXmlLocked(scope, groupSize, contentTypeIdsA); - RebuildPreviewXmlLocked(scope, groupSize, contentTypeIdsA); - scope.Complete(); - } - } - - public void RebuildContentXml(int groupSize = 5000, IEnumerable contentTypeIds = null) - { - using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) - { - scope.WriteLock(Constants.Locks.ContentTree); - RebuildContentXmlLocked(scope, groupSize, contentTypeIds); - scope.Complete(); - } - } - - // assumes content tree lock - private void RebuildContentXmlLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) - { - var contentTypeIdsA = contentTypeIds?.ToArray(); - var contentObjectType = Constants.ObjectTypes.Document; - var db = scope.Database; - - // remove all - if anything fails the transaction will rollback - if (contentTypeIds == null || contentTypeIdsA.Length == 0) - { - // must support SQL-CE - // db.Execute(@"DELETE cmsContentXml - //FROM cmsContentXml - //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) - //WHERE umbracoNode.nodeObjectType=@objType", - db.Execute(@"DELETE FROM cmsContentXml -WHERE cmsContentXml.nodeId IN ( - SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType -)", - new { objType = contentObjectType }); - } - else - { - // assume number of ctypes won't blow IN(...) - // must support SQL-CE - // db.Execute(@"DELETE cmsContentXml - //FROM cmsContentXml - //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) - //JOIN {Constants.DatabaseSchema.Tables.Content} ON (cmsContentXml.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) - //WHERE umbracoNode.nodeObjectType=@objType - //AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)", - db.Execute($@"DELETE FROM cmsContentXml -WHERE cmsContentXml.nodeId IN ( - SELECT id FROM umbracoNode - JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id - WHERE umbracoNode.nodeObjectType=@objType - AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) -)", - new { objType = contentObjectType, ctypes = contentTypeIdsA }); - } - - // insert back - if anything fails the transaction will rollback - var query = scope.SqlContext.Query().Where(x => x.Published); - if (contentTypeIds != null && contentTypeIdsA.Length > 0) - query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) - - long pageIndex = 0; - long processed = 0; - long total; - do - { - var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); - const bool published = true; // contentXml contains published content! - var items = descendants.Select(c => new ContentXmlDto { NodeId = c.Id, Xml = - _entitySerializer.Serialize(c, published).ToDataString() }).ToArray(); - db.BulkInsertRecords(items); - processed += items.Length; - } while (processed < total); - } - - public void RebuildPreviewXml(int groupSize = 5000, IEnumerable contentTypeIds = null) - { - using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) - { - scope.WriteLock(Constants.Locks.ContentTree); - RebuildPreviewXmlLocked(scope, groupSize, contentTypeIds); - scope.Complete(); - scope.Complete(); - } - } - - // assumes content tree lock - private void RebuildPreviewXmlLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) - { - var contentTypeIdsA = contentTypeIds?.ToArray(); - var contentObjectType = Constants.ObjectTypes.Document; - var db = scope.Database; - - // remove all - if anything fails the transaction will rollback - if (contentTypeIds == null || contentTypeIdsA.Length == 0) - { - // must support SQL-CE - // db.Execute(@"DELETE cmsPreviewXml - //FROM cmsPreviewXml - //JOIN umbracoNode ON (cmsPreviewXml.nodeId=umbracoNode.Id) - //WHERE umbracoNode.nodeObjectType=@objType", - db.Execute(@"DELETE FROM cmsPreviewXml -WHERE cmsPreviewXml.nodeId IN ( - SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType -)", - new { objType = contentObjectType }); - } - else - { - // assume number of ctypes won't blow IN(...) - // must support SQL-CE - // db.Execute(@"DELETE cmsPreviewXml - //FROM cmsPreviewXml - //JOIN umbracoNode ON (cmsPreviewXml.nodeId=umbracoNode.Id) - //JOIN {Constants.DatabaseSchema.Tables.Content} ON (cmsPreviewXml.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) - //WHERE umbracoNode.nodeObjectType=@objType - //AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)", - db.Execute($@"DELETE FROM cmsPreviewXml -WHERE cmsPreviewXml.nodeId IN ( - SELECT id FROM umbracoNode - JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id - WHERE umbracoNode.nodeObjectType=@objType - AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) -)", - new { objType = contentObjectType, ctypes = contentTypeIdsA }); - } - - // insert back - if anything fails the transaction will rollback - var query = scope.SqlContext.Query(); - if (contentTypeIds != null && contentTypeIdsA.Length > 0) - query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) - - long pageIndex = 0; - long processed = 0; - long total; - do - { - // .GetPagedResultsByQuery implicitly adds ({Constants.DatabaseSchema.Tables.Document}.newest = 1) which - // is what we want for preview (ie latest version of a content, published or not) - var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); - const bool published = true; // previewXml contains edit content! - var items = descendants.Select(c => new PreviewXmlDto - { - NodeId = c.Id, - Xml = _entitySerializer.Serialize(c, published).ToDataString() - }).ToArray(); - db.BulkInsertRecords(items); - processed += items.Length; - } while (processed < total); - } - - public void RebuildMediaXml(int groupSize = 5000, IEnumerable contentTypeIds = null) - { - using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) - { - scope.WriteLock(Constants.Locks.MediaTree); - RebuildMediaXmlLocked(scope, groupSize, contentTypeIds); - scope.Complete(); - } - } - - // assumes media tree lock - public void RebuildMediaXmlLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) - { - var contentTypeIdsA = contentTypeIds?.ToArray(); - var mediaObjectType = Constants.ObjectTypes.Media; - var db = scope.Database; - - // remove all - if anything fails the transaction will rollback - if (contentTypeIds == null || contentTypeIdsA.Length == 0) - { - // must support SQL-CE - // db.Execute(@"DELETE cmsContentXml - //FROM cmsContentXml - //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) - //WHERE umbracoNode.nodeObjectType=@objType", - db.Execute(@"DELETE FROM cmsContentXml -WHERE cmsContentXml.nodeId IN ( - SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType -)", - new { objType = mediaObjectType }); - } - else - { - // assume number of ctypes won't blow IN(...) - // must support SQL-CE - // db.Execute(@"DELETE cmsContentXml - //FROM cmsContentXml - //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) - //JOIN {Constants.DatabaseSchema.Tables.Content} ON (cmsContentXml.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) - //WHERE umbracoNode.nodeObjectType=@objType - //AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)", - db.Execute($@"DELETE FROM cmsContentXml -WHERE cmsContentXml.nodeId IN ( - SELECT id FROM umbracoNode - JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id - WHERE umbracoNode.nodeObjectType=@objType - AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) -)", - new { objType = mediaObjectType, ctypes = contentTypeIdsA }); - } - - // insert back - if anything fails the transaction will rollback - var query = scope.SqlContext.Query(); - if (contentTypeIds != null && contentTypeIdsA.Length > 0) - query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) - - long pageIndex = 0; - long processed = 0; - long total; - do - { - var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); - var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = - _entitySerializer.Serialize(m).ToDataString() }).ToArray(); - db.BulkInsertRecords(items); - processed += items.Length; - } while (processed < total); - } - - public void RebuildMemberXml(int groupSize = 5000, IEnumerable contentTypeIds = null) - { - using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) - { - scope.WriteLock(Constants.Locks.MemberTree); - RebuildMemberXmlLocked(scope, groupSize, contentTypeIds); - scope.Complete(); - } - } - - // assumes member tree lock - public void RebuildMemberXmlLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) - { - var contentTypeIdsA = contentTypeIds?.ToArray(); - var memberObjectType = Constants.ObjectTypes.Member; - var db = scope.Database; - - // remove all - if anything fails the transaction will rollback - if (contentTypeIds == null || contentTypeIdsA.Length == 0) - { - // must support SQL-CE - // db.Execute(@"DELETE cmsContentXml - //FROM cmsContentXml - //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) - //WHERE umbracoNode.nodeObjectType=@objType", - db.Execute(@"DELETE FROM cmsContentXml -WHERE cmsContentXml.nodeId IN ( - SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType -)", - new { objType = memberObjectType }); - } - else - { - // assume number of ctypes won't blow IN(...) - // must support SQL-CE - // db.Execute(@"DELETE cmsContentXml - //FROM cmsContentXml - //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) - //JOIN {Constants.DatabaseSchema.Tables.Content} ON (cmsContentXml.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) - //WHERE umbracoNode.nodeObjectType=@objType - //AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)", - db.Execute($@"DELETE FROM cmsContentXml -WHERE cmsContentXml.nodeId IN ( - SELECT id FROM umbracoNode - JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id - WHERE umbracoNode.nodeObjectType=@objType - AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) -)", - new { objType = memberObjectType, ctypes = contentTypeIdsA }); - } - - // insert back - if anything fails the transaction will rollback - var query = scope.SqlContext.Query(); - if (contentTypeIds != null && contentTypeIdsA.Length > 0) - query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) - - long pageIndex = 0; - long processed = 0; - long total; - do - { - var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); - var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = _entitySerializer.Serialize(m).ToDataString() }).ToArray(); - db.BulkInsertRecords(items); - processed += items.Length; - } while (processed < total); - } - - public bool VerifyContentAndPreviewXml() - { - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.ContentTree); - var ok = VerifyContentAndPreviewXmlLocked(scope); - scope.Complete(); - return ok; - } - } - - // assumes content tree lock - private static bool VerifyContentAndPreviewXmlLocked(IScope scope) - { - // every published content item should have a corresponding row in cmsContentXml - // every content item should have a corresponding row in cmsPreviewXml - // and that row should have the key="..." attribute - - var contentObjectType = Constants.ObjectTypes.Document; - var db = scope.Database; - - var count = db.ExecuteScalar($@"SELECT COUNT(*) -FROM umbracoNode -JOIN {Constants.DatabaseSchema.Tables.Document} ON (umbracoNode.id={Constants.DatabaseSchema.Tables.Document}.nodeId and {Constants.DatabaseSchema.Tables.Document}.published=1) -LEFT JOIN cmsContentXml ON (umbracoNode.id=cmsContentXml.nodeId) -WHERE umbracoNode.nodeObjectType=@objType -AND cmsContentXml.nodeId IS NULL OR cmsContentXml.xml NOT LIKE '% key=""' -", new { objType = contentObjectType }); - - if (count > 0) return false; - - count = db.ExecuteScalar(@"SELECT COUNT(*) -FROM umbracoNode -LEFT JOIN cmsPreviewXml ON (umbracoNode.id=cmsPreviewXml.nodeId) -WHERE umbracoNode.nodeObjectType=@objType -AND cmsPreviewXml.nodeId IS NULL OR cmsPreviewXml.xml NOT LIKE '% key=""' -", new { objType = contentObjectType }); - - return count == 0; - } - - public bool VerifyMediaXml() - { - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.MediaTree); - var ok = VerifyMediaXmlLocked(scope); - scope.Complete(); - return ok; - } - } - - // assumes media tree lock - public bool VerifyMediaXmlLocked(IScope scope) - { - // every non-trashed media item should have a corresponding row in cmsContentXml - // and that row should have the key="..." attribute - // TODO: where's the trashed test here? - - var mediaObjectType = Constants.ObjectTypes.Media; - var db = scope.Database; - - var count = db.ExecuteScalar($@"SELECT COUNT(*) -FROM umbracoNode -JOIN {Constants.DatabaseSchema.Tables.Document} ON (umbracoNode.id={Constants.DatabaseSchema.Tables.Document}.nodeId and {Constants.DatabaseSchema.Tables.Document}.published=1) -LEFT JOIN cmsContentXml ON (umbracoNode.id=cmsContentXml.nodeId) -WHERE umbracoNode.nodeObjectType=@objType -AND cmsContentXml.nodeId IS NULL OR cmsContentXml.xml NOT LIKE '% key=""' -", new { objType = mediaObjectType }); - - return count == 0; - } - - public bool VerifyMemberXml() - { - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.MemberTree); - var ok = VerifyMemberXmlLocked(scope); - scope.Complete(); - return ok; - } - } - - // assumes member tree lock - public bool VerifyMemberXmlLocked(IScope scope) - { - // every member item should have a corresponding row in cmsContentXml - - var memberObjectType = Constants.ObjectTypes.Member; - var db = scope.Database; - - var count = db.ExecuteScalar(@"SELECT COUNT(*) -FROM umbracoNode -LEFT JOIN cmsContentXml ON (umbracoNode.id=cmsContentXml.nodeId) -WHERE umbracoNode.nodeObjectType=@objType -AND cmsContentXml.nodeId IS NULL -", new { objType = memberObjectType }); - - return count == 0; - } - - #endregion - } -} diff --git a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs b/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs deleted file mode 100644 index 6029a069cb..0000000000 --- a/tests/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System; -using System.Threading; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core; -using Umbraco.Web.Scheduling; - -namespace Umbraco.Tests.LegacyXmlPublishedCache -{ - /// - /// This is the background task runner that persists the xml file to the file system - /// - /// - /// This is used so that all file saving is done on a web aware worker background thread and all logic is performed async so this - /// process will not interfere with any web requests threads. This is also done as to not require any global locks and to ensure that - /// if multiple threads are performing publishing tasks that the file will be persisted in accordance with the final resulting - /// xml structure since the file writes are queued. - /// - internal class XmlStoreFilePersister : LatchedBackgroundTaskBase - { - private readonly IBackgroundTaskRunner _runner; - private readonly ILogger _logger; - private readonly XmlStore _store; - private readonly object _locko = new object(); - private bool _released; - private Timer _timer; - private DateTime _initialTouch; - private readonly SystemLock _runLock = new SystemLock(); // ensure we run once at a time - - // note: - // as long as the runner controls the runs, we know that we run once at a time, but - // when the AppDomain goes down and the runner has completed and yet the persister is - // asked to save, then we need to run immediately - but the runner may be running, so - // we need to make sure there's no collision - hence _runLock - - private const int WaitMilliseconds = 4000; // save the cache 4s after the last change (ie every 4s min) - private const int MaxWaitMilliseconds = 30000; // save the cache after some time (ie no more than 30s of changes) - - // save the cache when the app goes down - public override bool RunsOnShutdown => _timer != null; - - // initialize the first instance, which is inactive (not touched yet) - public XmlStoreFilePersister(IBackgroundTaskRunner runner, XmlStore store, ILogger logger) - : this(runner, store, logger, false) - { } - - // initialize further instances, which are active (touched) - private XmlStoreFilePersister(IBackgroundTaskRunner runner, XmlStore store, ILogger logger, bool touched) - { - _runner = runner; - _store = store; - _logger = logger; - - if (_runner.TryAdd(this) == false) - { - _runner = null; // runner's down - _released = true; // don't mess with timer - return; - } - - // runner could decide to run it anytime now - - if (touched == false) return; - - _logger.LogDebug("Created, save in {WaitMilliseconds}ms.", WaitMilliseconds); - _initialTouch = DateTime.Now; - _timer = new Timer(_ => TimerRelease()); - _timer.Change(WaitMilliseconds, 0); - } - - public XmlStoreFilePersister Touch() - { - // if _released is false then we're going to setup a timer - // then the runner wants to shutdown & run immediately - // this sets _released to true & the timer will trigger eventually & who cares? - // if _released is true, either it's a normal release, or - // a runner shutdown, in which case we won't be able to - // add a new task, and so we'll run immediately - - var ret = this; - var runNow = false; - - lock (_locko) - { - if (_released) // our timer has triggered OR the runner is shutting down - { - _logger.LogDebug("Touched, was released..."); - - // release: has run or is running, too late, return a new task (adds itself to runner) - if (_runner == null) - { - _logger.LogDebug("Runner is down, run now."); - runNow = true; - } - else - { - _logger.LogDebug("Create new..."); - ret = new XmlStoreFilePersister(_runner, _store, _logger, true); - if (ret._runner == null) - { - // could not enlist with the runner, runner is completed, must run now - _logger.LogDebug("Runner is down, run now."); - runNow = true; - } - } - } - - else if (_timer == null) // we don't have a timer yet - { - _logger.LogDebug("Touched, was idle, start and save in {WaitMilliseconds}ms.", WaitMilliseconds); - _initialTouch = DateTime.Now; - _timer = new Timer(_ => TimerRelease()); - _timer.Change(WaitMilliseconds, 0); - } - - else // we have a timer - { - // change the timer to trigger in WaitMilliseconds unless we've been touched first more - // than MaxWaitMilliseconds ago and then leave the time unchanged - - if (DateTime.Now - _initialTouch < TimeSpan.FromMilliseconds(MaxWaitMilliseconds)) - { - _logger.LogDebug("Touched, was waiting, can delay, save in {WaitMilliseconds}ms.", WaitMilliseconds); - _timer.Change(WaitMilliseconds, 0); - } - else - { - _logger.LogDebug("Touched, was waiting, cannot delay."); - } - } - } - - // note: this comes from 7.x where it was not possible to lock the entire content service - // in our case, the XmlStore configures everything so that it is not possible to access content - // when going down, so this should never happen. - - if (runNow) - //Run(); - _logger.LogWarning("Cannot write now because we are going down, changes may be lost."); - - return ret; // this, by default, unless we created a new one - } - - private void TimerRelease() - { - lock (_locko) - { - _logger.LogDebug("Timer: release."); - _released = true; - - Release(); - } - } - - public override bool IsAsync => false; - - public override void Run() - { - lock (_locko) - { - _logger.LogDebug("Run now (sync)."); - // not really needed but safer (it's only us invoking Run, but the method is public...) - _released = true; - } - - using (_runLock.Lock()) - { - _store.SaveXmlToFile(); - } - } - - protected override void DisposeResources() - { - base.DisposeResources(); - - // stop the timer - if (_timer == null) return; - _timer.Change(Timeout.Infinite, Timeout.Infinite); - _timer.Dispose(); - } - } -} diff --git a/tests/Umbraco.Tests/Models/ContentXmlTest.cs b/tests/Umbraco.Tests/Models/ContentXmlTest.cs deleted file mode 100644 index 184bec85c2..0000000000 --- a/tests/Umbraco.Tests/Models/ContentXmlTest.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Linq; -using System.Xml.Linq; -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Tests.Testing; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.Models -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class ContentXmlTest : TestWithDatabaseBase - { - [Test] - public void Can_Generate_Xml_Representation_Of_Content() - { - // Arrange - var contentType = MockedContentTypes.CreateTextPageContentType(); - ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! - ServiceContext.ContentTypeService.Save(contentType); - - var content = MockedContent.CreateTextpageContent(contentType, "Root Home", -1); - ServiceContext.ContentService.Save(content, Constants.Security.SuperUserId); - - var nodeName = content.ContentType.Alias.ToSafeAlias(ShortStringHelper); - var urlName = content.GetUrlSegment(ShortStringHelper, new[]{new DefaultUrlSegmentProvider(ShortStringHelper) }); - - // Act - XElement element = content.ToXml(Factory.GetRequiredService()); - - // Assert - Assert.That(element, Is.Not.Null); - Assert.That(element.Name.LocalName, Is.EqualTo(nodeName)); - Assert.AreEqual(content.Id.ToString(), (string)element.Attribute("id")); - Assert.AreEqual(content.ParentId.ToString(), (string)element.Attribute("parentID")); - Assert.AreEqual(content.Level.ToString(), (string)element.Attribute("level")); - Assert.AreEqual(content.CreatorId.ToString(), (string)element.Attribute("creatorID")); - Assert.AreEqual(content.SortOrder.ToString(), (string)element.Attribute("sortOrder")); - Assert.AreEqual(content.CreateDate.ToString("s"), (string)element.Attribute("createDate")); - Assert.AreEqual(content.UpdateDate.ToString("s"), (string)element.Attribute("updateDate")); - Assert.AreEqual(content.Name, (string)element.Attribute("nodeName")); - Assert.AreEqual(urlName, (string)element.Attribute("urlName")); - Assert.AreEqual(content.Path, (string)element.Attribute("path")); - Assert.AreEqual("", (string)element.Attribute("isDoc")); - Assert.AreEqual(content.ContentType.Id.ToString(), (string)element.Attribute("nodeType")); - Assert.AreEqual(content.GetCreatorProfile(ServiceContext.UserService).Name, (string)element.Attribute("creatorName")); - Assert.AreEqual(content.GetWriterProfile(ServiceContext.UserService).Name, (string)element.Attribute("writerName")); - Assert.AreEqual(content.WriterId.ToString(), (string)element.Attribute("writerID")); - Assert.AreEqual(content.TemplateId.ToString(), (string)element.Attribute("template")); - - Assert.AreEqual(content.Properties["title"].GetValue().ToString(), element.Elements("title").Single().Value); - Assert.AreEqual(content.Properties["bodyText"].GetValue().ToString(), element.Elements("bodyText").Single().Value); - Assert.AreEqual(content.Properties["keywords"].GetValue().ToString(), element.Elements("keywords").Single().Value); - Assert.AreEqual(content.Properties["description"].GetValue().ToString(), element.Elements("description").Single().Value); - } - } -} diff --git a/tests/Umbraco.Tests/Models/MediaXmlTest.cs b/tests/Umbraco.Tests/Models/MediaXmlTest.cs deleted file mode 100644 index 6899ddca65..0000000000 --- a/tests/Umbraco.Tests/Models/MediaXmlTest.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Linq; -using System.Xml.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.Models -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class MediaXmlTest : TestWithDatabaseBase - { - [Test] - public void Can_Generate_Xml_Representation_Of_Media() - { - // Arrange - var mediaType = MockedContentTypes.CreateImageMediaType("image2"); - ServiceContext.MediaTypeService.Save(mediaType); - - // reference, so static ctor runs, so event handlers register - // and then, this will reset the width, height... because the file does not exist, of course ;-( - var loggerFactory = NullLoggerFactory.Instance; - var scheme = Mock.Of(); - var contentSettings = new ContentSettings(); - - var mediaFileManager = new MediaFileManager(Mock.Of(), scheme, - loggerFactory.CreateLogger(), ShortStringHelper); - var ignored = new FileUploadPropertyEditor(DataValueEditorFactory, mediaFileManager, Microsoft.Extensions.Options.Options.Create(contentSettings), DataTypeService, LocalizationService, LocalizedTextService, UploadAutoFillProperties, ContentService); - - var media = MockedMedia.CreateMediaImage(mediaType, -1); - media.WriterId = -1; // else it's zero and that's not a user and it breaks the tests - ServiceContext.MediaService.Save(media, Constants.Security.SuperUserId); - - // so we have to force-reset these values because the property editor has cleared them - media.SetValue(Constants.Conventions.Media.Width, "200"); - media.SetValue(Constants.Conventions.Media.Height, "200"); - media.SetValue(Constants.Conventions.Media.Bytes, "100"); - media.SetValue(Constants.Conventions.Media.Extension, "png"); - - var nodeName = media.ContentType.Alias.ToSafeAlias(ShortStringHelper); - var urlName = media.GetUrlSegment(ShortStringHelper, new[] { new DefaultUrlSegmentProvider(ShortStringHelper) }); - - // Act - XElement element = media.ToXml(Factory.GetRequiredService()); - - // Assert - Assert.That(element, Is.Not.Null); - Assert.That(element.Name.LocalName, Is.EqualTo(nodeName)); - Assert.AreEqual(media.Id.ToString(), (string)element.Attribute("id")); - Assert.AreEqual(media.ParentId.ToString(), (string)element.Attribute("parentID")); - Assert.AreEqual(media.Level.ToString(), (string)element.Attribute("level")); - Assert.AreEqual(media.SortOrder.ToString(), (string)element.Attribute("sortOrder")); - Assert.AreEqual(media.CreateDate.ToString("s"), (string)element.Attribute("createDate")); - Assert.AreEqual(media.UpdateDate.ToString("s"), (string)element.Attribute("updateDate")); - Assert.AreEqual(media.Name, (string)element.Attribute("nodeName")); - Assert.AreEqual(urlName, (string)element.Attribute("urlName")); - Assert.AreEqual(media.Path, (string)element.Attribute("path")); - Assert.AreEqual("", (string)element.Attribute("isDoc")); - Assert.AreEqual(media.ContentType.Id.ToString(), (string)element.Attribute("nodeType")); - Assert.AreEqual(media.GetCreatorProfile(ServiceContext.UserService).Name, (string)element.Attribute("writerName")); - Assert.AreEqual(media.CreatorId.ToString(), (string)element.Attribute("writerID")); - Assert.IsNull(element.Attribute("template")); - - Assert.AreEqual(media.Properties[Constants.Conventions.Media.File].GetValue().ToString(), element.Elements(Constants.Conventions.Media.File).Single().Value); - Assert.AreEqual(media.Properties[Constants.Conventions.Media.Width].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Width).Single().Value); - Assert.AreEqual(media.Properties[Constants.Conventions.Media.Height].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Height).Single().Value); - Assert.AreEqual(media.Properties[Constants.Conventions.Media.Bytes].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Bytes).Single().Value); - Assert.AreEqual(media.Properties[Constants.Conventions.Media.Extension].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Extension).Single().Value); - } - } -} diff --git a/tests/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs b/tests/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs deleted file mode 100644 index 4da04e5e17..0000000000 --- a/tests/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Data.SqlClient; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.Mappers; -using Umbraco.Tests.TestHelpers; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.Persistence.FaultHandling -{ - [TestFixture, Ignore("fixme - ignored test")] - public class ConnectionRetryTest - { - [Test] - public void Cant_Connect_To_SqlDatabase_With_Invalid_User() - { - const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=x;password=umbraco"; - const string providerName = Constants.DbProviderNames.SqlServer; - var factory = new UmbracoDatabaseFactory(Mock.Of>(), NullLoggerFactory.Instance, connectionString, providerName, new Lazy(() => Mock.Of()), TestHelper.DbProviderFactoryCreator, TestHelper.DatabaseSchemaCreatorFactory); - - using (var database = factory.CreateDatabase()) - { - Assert.Throws( - () => database.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES")); - } - } - - [Test] - public void Cant_Connect_To_SqlDatabase_Because_Of_Network() - { - const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco"; - const string providerName = Constants.DbProviderNames.SqlServer; - var factory = new UmbracoDatabaseFactory(Mock.Of>(), NullLoggerFactory.Instance, connectionString, providerName, new Lazy(() => Mock.Of()), TestHelper.DbProviderFactoryCreator, TestHelper.DatabaseSchemaCreatorFactory); - - using (var database = factory.CreateDatabase()) - { - Assert.Throws( - () => database.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES")); - } - } - } -} diff --git a/tests/Umbraco.Tests/Persistence/Mappers/MapperTestBase.cs b/tests/Umbraco.Tests/Persistence/Mappers/MapperTestBase.cs deleted file mode 100644 index 446d407077..0000000000 --- a/tests/Umbraco.Tests/Persistence/Mappers/MapperTestBase.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using Microsoft.Extensions.Options; -using Moq; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.Mappers; -using Umbraco.Cms.Persistence.SqlCe; - -namespace Umbraco.Tests.Persistence.Mappers -{ - public class MapperTestBase - { - protected Lazy MockSqlContext() - { - var sqlContext = Mock.Of(); - var syntax = new SqlCeSyntaxProvider(Options.Create(new GlobalSettings())); - Mock.Get(sqlContext).Setup(x => x.SqlSyntax).Returns(syntax); - return new Lazy(() => sqlContext); - } - - protected MapperConfigurationStore CreateMaps() - => new MapperConfigurationStore(); - } -} diff --git a/tests/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs b/tests/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs deleted file mode 100644 index c27218e1b3..0000000000 --- a/tests/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System; -using System.Linq; -using NUnit.Framework; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services.Implement; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.Services; -using Umbraco.Tests.TestHelpers.Entities; - -namespace Umbraco.Tests.Persistence.NPocoTests -{ - // FIXME: npoco - what shall we do with those tests? - // - [TestFixture, Ignore("fixme - ignored test")] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class PetaPocoCachesTest : TestWithSomeContentBase - { - -#if DEBUG - /// - /// This tests the peta poco caches - /// - /// - /// This test WILL fail. This is because we cannot stop PetaPoco from creating more cached items for queries such as - /// ContentTypeRepository.GetAll(1,2,3,4); - /// when combined with other GetAll queries that pass in an array of Ids, each query generated for different length - /// arrays will produce a unique query which then gets added to the cache. - /// - /// This test confirms this, if you analyze the DIFFERENCE output below you can see why the cached queries grow. - /// - //[Test] - //public void Check_Peta_Poco_Caches() - //{ - // var result = new List>>(); - - // Database.PocoData.UseLongKeys = true; - - // for (int i = 0; i < 2; i++) - // { - // int id1, id2, id3; - // string alias; - // CreateStuff(out id1, out id2, out id3, out alias); - // QueryStuff(id1, id2, id3, alias); - - // double totalBytes1; - // IEnumerable keys; - // Console.Write(PocoData.PrintDebugCacheReport(out totalBytes1, out keys)); - - // result.Add(new Tuple>(totalBytes1, keys.Count(), keys)); - // } - - // for (int index = 0; index < result.Count; index++) - // { - // var tuple = result[index]; - // Console.WriteLine("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2); - // if (index != 0) - // { - // Console.WriteLine("----------------DIFFERENCE---------------------"); - // var diff = tuple.Item3.Except(result[index - 1].Item3); - // foreach (var d in diff) - // { - // Console.WriteLine(d); - // } - // } - - // } - - // var allByteResults = result.Select(x => x.Item1).Distinct(); - // var totalKeys = result.Select(x => x.Item2).Distinct(); - - // Assert.AreEqual(1, allByteResults.Count()); - // Assert.AreEqual(1, totalKeys.Count()); - //} - - //[Test] - //public void Verify_Memory_Expires() - //{ - // Database.PocoData.SlidingExpirationSeconds = 2; - - // var managedCache = new Database.ManagedCache(); - - // int id1, id2, id3; - // string alias; - // CreateStuff(out id1, out id2, out id3, out alias); - // QueryStuff(id1, id2, id3, alias); - - // var count1 = managedCache.GetCache().GetCount(); - // Console.WriteLine("Keys = " + count1); - // Assert.Greater(count1, 0); - - // Thread.Sleep(10000); - - // var count2 = managedCache.GetCache().GetCount(); - // Console.WriteLine("Keys = " + count2); - // Assert.Less(count2, count1); - //} - - private void QueryStuff(int id1, int id2, int id3, string alias1) - { - var contentService = ServiceContext.ContentService; - - ServiceContext.TagService.GetTagsForEntity(id1); - - ServiceContext.TagService.GetAllContentTags(); - - ServiceContext.TagService.GetTagsForEntity(id2); - - ServiceContext.TagService.GetTagsForEntity(id3); - - contentService.CountDescendants(id3); - - contentService.CountChildren(id3); - - contentService.Count(documentTypeAlias: alias1); - - contentService.Count(); - - contentService.GetById(Guid.NewGuid()); - - contentService.GetByLevel(2); - - contentService.GetVersions(id3); - - contentService.GetRootContent(); - - contentService.GetContentForExpiration(DateTime.Now); - - contentService.GetContentForRelease(DateTime.Now); - - ((ContentService)contentService).GetPublishedDescendants(new Content("Test", -1, new ContentType(ShortStringHelper, -1)) - { - Id = id1, - Path = "-1," + id1 - }); - - contentService.GetVersion(1234); - } - - private void CreateStuff(out int id1, out int id2, out int id3, out string alias) - { - var contentService = ServiceContext.ContentService; - var serializer = new JsonNetSerializer(); - - var ctAlias = "umbTextpage" + Guid.NewGuid().ToString("N"); - alias = ctAlias; - - for (int i = 0; i < 20; i++) - { - contentService.CreateAndSave("Test", -1, "umbTextpage", 0); - } - var contentTypeService = ServiceContext.ContentTypeService; - var contentType = MockedContentTypes.CreateSimpleContentType(ctAlias, "test Doc Type"); - contentTypeService.Save(contentType); - for (int i = 0; i < 20; i++) - { - contentService.CreateAndSave("Test", -1, ctAlias, 0); - } - var parent = contentService.CreateAndSave("Test", -1, ctAlias, 0); - id1 = parent.Id; - - for (int i = 0; i < 20; i++) - { - contentService.CreateAndSave("Test", parent, ctAlias); - } - IContent current = parent; - for (int i = 0; i < 20; i++) - { - current = contentService.CreateAndSave("Test", current, ctAlias); - } - contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory" + Guid.NewGuid().ToString("N"), "Mandatory Doc Type", true); - contentType.PropertyGroups.First().PropertyTypes.Add( - new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext, "tags") - { - DataTypeId = 1041 - }); - contentTypeService.Save(contentType); - var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); - content1.AssignTags(PropertyEditorCollection.Value, DataTypeService, serializer, "tags", new[] { "hello", "world", "some", "tags" }); - content1.PublishCulture(CultureImpact.Invariant); - contentService.SaveAndPublish(content1); - id2 = content1.Id; - - var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); - content2.AssignTags(PropertyEditorCollection.Value, DataTypeService, serializer, "tags", new[] { "hello", "world", "some", "tags" }); - content2.PublishCulture(CultureImpact.Invariant); - contentService.SaveAndPublish(content2); - id3 = content2.Id; - - contentService.MoveToRecycleBin(content1); - } -#endif - } -} diff --git a/tests/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs b/tests/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs deleted file mode 100644 index fcf64641c8..0000000000 --- a/tests/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs +++ /dev/null @@ -1,199 +0,0 @@ -// fixme - does it make any sense to keep these tests here? -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using NPoco; -//using NUnit.Framework; -//using Umbraco.Core; -//using Umbraco.Core.Models; -//using Umbraco.Core.Persistence.Dtos; -//using Umbraco.Core.Persistence.Repositories; -//using Umbraco.Core.Persistence.Repositories.Implement; -//using Umbraco.Tests.TestHelpers; -//using Umbraco.Tests.Testing; - -//namespace Umbraco.Tests.Persistence.Querying -//{ -// [TestFixture] -// [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] -// public class ContentTypeSqlMappingTests : TestWithDatabaseBase -// { -// [Test] -// public void Can_Map_Content_Type_Templates_And_Allowed_Types() -// { -// IDictionary.AssociatedTemplate>> allAssociatedTemplates; -// IDictionary> allParentContentTypeIds; -// IContentType[] contentTypes; - -// using (var scope = ScopeProvider.CreateScope()) -// { -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("umbracoNode")))); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 55554, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,55554", SortOrder = 1, UniqueId = new Guid("87D1EAB6-AB27-4852-B3DF-DE8DBA4A1AA0"), Text = "Template 1", NodeObjectType = Constants.ObjectTypes.Template, CreateDate = DateTime.Now }); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 55555, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,55555", SortOrder = 1, UniqueId = new Guid("3390BDF4-C974-4211-AA95-3812A8CE7C46"), Text = "Template 2", NodeObjectType = Constants.ObjectTypes.Template, CreateDate = DateTime.Now }); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99997, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99997", SortOrder = 0, UniqueId = new Guid("BB3241D5-6842-4EFA-A82A-5F56885CF528"), Text = "Test Content Type 1", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99998, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99998", SortOrder = 0, UniqueId = new Guid("EEA66B06-302E-49BA-A8B2-EDF07248BC59"), Text = "Test Content Type 2", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99999, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99999", SortOrder = 0, UniqueId = new Guid("C45CC083-BB27-4C1C-B448-6F703CC9B799"), Text = "Test Content Type 2", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("umbracoNode")))); - -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsTemplate")))); -// scope.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55554, Alias = "testTemplate1", PrimaryKey = 22221}); -// scope.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55555, Alias = "testTemplate2", PrimaryKey = 22222 }); -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsTemplate")))); - -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsContentType")))); -// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88887, NodeId = 99997, Alias = "TestContentType1", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); -// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88888, NodeId = 99998, Alias = "TestContentType2", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); -// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88889, NodeId = 99999, Alias = "TestContentType3", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsContentType")))); - -// scope.Database.Insert(new ContentTypeTemplateDto { ContentTypeNodeId = 99997, IsDefault = true, TemplateNodeId = 55555 }); -// scope.Database.Insert(new ContentTypeTemplateDto { ContentTypeNodeId = 99997, IsDefault = false, TemplateNodeId = 55554 }); - -// scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99998, Id = 99997, SortOrder = 1 }); -// scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99999, Id = 99997, SortOrder = 2}); - -// scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99999, ParentId = 99997}); -// scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99998, ParentId = 99997 }); - -// contentTypes = ContentTypeQueryMapper.GetContentTypes( -// scope.Database, out allAssociatedTemplates, out allParentContentTypeIds) -// .Where(x => new[] { 99997, 99998 }.Contains(x.Id)) -// .ToArray(); - -// scope.Complete(); -// } - -// var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); -// Assert.IsNotNull(contentType1); - -// var associatedTemplates1 = allAssociatedTemplates[contentType1.Id]; -// var parentContentTypes1 = allParentContentTypeIds[contentType1.Id]; - -// Assert.AreEqual(2, contentType1.AllowedContentTypes.Count()); -// Assert.AreEqual(2, associatedTemplates1.Count()); -// Assert.AreEqual(0, parentContentTypes1.Count()); - -// var contentType2 = contentTypes.SingleOrDefault(x => x.Id == 99998); -// Assert.IsNotNull(contentType2); - -// var associatedTemplates2 = allAssociatedTemplates[contentType2.Id]; -// var parentContentTypes2 = allParentContentTypeIds[contentType2.Id]; - -// Assert.AreEqual(0, contentType2.AllowedContentTypes.Count()); -// Assert.AreEqual(0, associatedTemplates2.Count()); -// Assert.AreEqual(1, parentContentTypes2.Count()); -// } - -// [Test] -// public void Can_Map_Media_Type_And_Allowed_Types() -// { -// IDictionary> allParentContentTypeIds; -// IMediaType[] contentTypes; - -// using (var scope = ScopeProvider.CreateScope()) -// { -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("umbracoNode")))); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99997, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99997", SortOrder = 0, UniqueId = new Guid("BB3241D5-6842-4EFA-A82A-5F56885CF528"), Text = "Test Media Type 1", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99998, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99998", SortOrder = 0, UniqueId = new Guid("EEA66B06-302E-49BA-A8B2-EDF07248BC59"), Text = "Test Media Type 2", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99999, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99999", SortOrder = 0, UniqueId = new Guid("C45CC083-BB27-4C1C-B448-6F703CC9B799"), Text = "Test Media Type 2", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("umbracoNode")))); - -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsContentType")))); -// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88887, NodeId = 99997, Alias = "TestContentType1", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); -// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88888, NodeId = 99998, Alias = "TestContentType2", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); -// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88889, NodeId = 99999, Alias = "TestContentType3", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsContentType")))); - -// scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99998, Id = 99997, SortOrder = 1 }); -// scope.Database.Insert(new ContentTypeAllowedContentTypeDto { AllowedId = 99999, Id = 99997, SortOrder = 2 }); - -// scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99999, ParentId = 99997 }); -// scope.Database.Insert(new ContentType2ContentTypeDto { ChildId = 99998, ParentId = 99997 }); - -// contentTypes = ContentTypeQueryMapper.MapMediaTypes( -// scope.Database, SqlSyntax, out allParentContentTypeIds) -// .Where(x => (new[] { 99997, 99998 }).Contains(x.Id)) -// .ToArray(); - -// scope.Complete(); -// } - -// var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); -// Assert.IsNotNull(contentType1); - -// var parentContentTypes1 = allParentContentTypeIds[contentType1.Id]; - -// Assert.AreEqual(2, contentType1.AllowedContentTypes.Count()); -// Assert.AreEqual(0, parentContentTypes1.Count()); - -// var contentType2 = contentTypes.SingleOrDefault(x => x.Id == 99998); -// Assert.IsNotNull(contentType2); - -// var parentContentTypes2 = allParentContentTypeIds[contentType2.Id]; - -// Assert.AreEqual(0, contentType2.AllowedContentTypes.Count()); -// Assert.AreEqual(1, parentContentTypes2.Count()); - -// } - -// [Test] -// public void Can_Map_All_Property_Groups_And_Types() -// { -// IDictionary allPropTypeCollection; -// IDictionary allPropGroupCollection; - -// using (var scope = ScopeProvider.CreateScope()) -// { -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("umbracoNode")))); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 55555, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,55555", SortOrder = 1, UniqueId = new Guid("3390BDF4-C974-4211-AA95-3812A8CE7C46"), Text = "Test Data Type", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); -// scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 99999, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,99999", SortOrder = 0, UniqueId = new Guid("129241F0-D24E-4FC3-92D1-BC2D48B7C431"), Text = "Test Content Type", NodeObjectType = Constants.ObjectTypes.DocumentType, CreateDate = DateTime.Now }); -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("umbracoNode")))); - -// scope.Database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 55555, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); - -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsContentType")))); -// scope.Database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 88888, NodeId = 99999, Alias = "TestContentType", Icon = "icon-folder", Thumbnail = "folder.png", IsContainer = false, AllowAtRoot = true }); -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsContentType")))); - -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsPropertyTypeGroup")))); -// scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77776, UniqueId = 77776.ToGuid(), ContentTypeNodeId = 99999, Text = "Group1", SortOrder = 1 }); -// scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77777, UniqueId = 77777.ToGuid(), ContentTypeNodeId = 99999, Text = "Group2", SortOrder = 2 }); -// scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77778, UniqueId = 77778.ToGuid(), ContentTypeNodeId = 99999, Text = "Group3", SortOrder = 3 }); -// scope.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77779, UniqueId = 77779.ToGuid(), ContentTypeNodeId = 99999, Text = "Group4", SortOrder = 4 }); -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsPropertyTypeGroup")))); - -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntax.GetQuotedTableName("cmsPropertyType")))); -// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66662, UniqueId = 66662.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77776, Alias = "property1", Name = "Property 1", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); -// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66663, UniqueId = 66663.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77776, Alias = "property2", Name = "Property 2", SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); -// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66664, UniqueId = 66664.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77777, Alias = "property3", Name = "Property 3", SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); -// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66665, UniqueId = 66665.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77777, Alias = "property4", Name = "Property 4", SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); -// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66666, UniqueId = 66666.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = null, Alias = "property5", Name = "Property 5", SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); -// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66667, UniqueId = 66667.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77778, Alias = "property6", Name = "Property 6", SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); -// scope.Database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 66668, UniqueId = 66668.ToGuid(), DataTypeId = 55555, ContentTypeId = 99999, PropertyTypeGroupId = 77778, Alias = "property7", Name = "Property 7", SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); -// scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntax.GetQuotedTableName("cmsPropertyType")))); - -// ContentTypeQueryMapper.MapGroupsAndProperties_(new[] { 99999 }, scope.Database, SqlSyntax, true, out allPropTypeCollection, out allPropGroupCollection); - -// scope.Complete(); -// } - -// var propGroupCollection = allPropGroupCollection[99999]; -// var propTypeCollection = allPropTypeCollection[99999]; - -// Assert.AreEqual(4, propGroupCollection.Count); -// Assert.AreEqual(2, propGroupCollection["Group1"].PropertyTypes.Count); -// Assert.IsTrue(propGroupCollection["Group1"].PropertyTypes.Contains("property1")); -// Assert.IsTrue(propGroupCollection["Group1"].PropertyTypes.Contains("property2")); -// Assert.AreEqual(2, propGroupCollection["Group2"].PropertyTypes.Count); -// Assert.IsTrue(propGroupCollection["Group2"].PropertyTypes.Contains("property3")); -// Assert.IsTrue(propGroupCollection["Group2"].PropertyTypes.Contains("property4")); -// Assert.AreEqual(2, propGroupCollection["Group3"].PropertyTypes.Count); -// Assert.IsTrue(propGroupCollection["Group3"].PropertyTypes.Contains("property6")); -// Assert.IsTrue(propGroupCollection["Group3"].PropertyTypes.Contains("property7")); -// Assert.AreEqual(0, propGroupCollection["Group4"].PropertyTypes.Count); - -// Assert.AreEqual(1, propTypeCollection.Count); -// Assert.IsTrue(propTypeCollection.Contains("property5")); -// } -// } -//} diff --git a/tests/Umbraco.Tests/Plugins/PluginManagerTests.cs b/tests/Umbraco.Tests/Plugins/PluginManagerTests.cs deleted file mode 100644 index 44879eae2f..0000000000 --- a/tests/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ /dev/null @@ -1,392 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using Moq; -using NUnit.Framework; -using SqlCE4Umbraco; -using umbraco; -using umbraco.businesslogic; -using umbraco.cms.businesslogic; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Profiling; -using Umbraco.Core.PropertyEditors; -using umbraco.DataLayer; -using umbraco.editorControls; -using umbraco.interfaces; -using umbraco.MacroEngines; -using umbraco.uicontrols; -using Umbraco.Web; -using Umbraco.Web.PropertyEditors; - -namespace Umbraco.Tests.Plugins -{ - - [TestFixture] - public class PluginManagerTests - { - private PluginManager _manager; - [SetUp] - public void Initialize() - { - //this ensures its reset - _manager = new PluginManager(new ActivatorServiceProvider(), new NullCacheProvider(), - new ProfilingLogger(Mock.Of(), Mock.Of())); - - //for testing, we'll specify which assemblies are scanned for the PluginTypeResolver - //TODO: Should probably update this so it only searches this assembly and add custom types to be found - _manager.AssembliesToScan = new[] - { - this.GetType().Assembly, - typeof(ApplicationStartupHandler).Assembly, - typeof(SqlCEHelper).Assembly, - typeof(CMSNode).Assembly, - typeof(System.Guid).Assembly, - typeof(NUnit.Framework.Assert).Assembly, - typeof(Microsoft.CSharp.CSharpCodeProvider).Assembly, - typeof(System.Xml.NameTable).Assembly, - typeof(System.Configuration.GenericEnumConverter).Assembly, - typeof(System.Web.SiteMap).Assembly, - typeof(TabPage).Assembly, - typeof(System.Web.Mvc.ActionResult).Assembly, - typeof(TypeFinder).Assembly, - typeof(ISqlHelper).Assembly, - typeof(ICultureDictionary).Assembly, - typeof(UmbracoContext).Assembly, - typeof(BaseDataType).Assembly - }; - } - - [TearDown] - public void TearDown() - { - _manager = null; - } - - private DirectoryInfo PrepareFolder() - { - var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; - var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "PluginManager", Guid.NewGuid().ToString("N"))); - foreach (var f in dir.GetFiles()) - { - f.Delete(); - } - return dir; - } - - //[Test] - //public void Scan_Vs_Load_Benchmark() - //{ - // var pluginManager = new PluginManager(false); - // var watch = new Stopwatch(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); - // watch.Reset(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var refreshers = pluginManager.ResolveTypes(false); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds); - //} - - ////NOTE: This test shows that Type.GetType is 100% faster than Assembly.Load(..).GetType(...) so we'll use that :) - //[Test] - //public void Load_Type_Benchmark() - //{ - // var watch = new Stopwatch(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); - // watch.Reset(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") - // .GetType("umbraco.macroCacheRefresh"); - // var type3 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") - // .GetType("umbraco.templateCacheRefresh"); - // var type4 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") - // .GetType("umbraco.presentation.cache.MediaLibraryRefreshers"); - // var type5 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") - // .GetType("umbraco.presentation.cache.pageRefresher"); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds); - // watch.Reset(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); - //} - - [Test] - public void Detect_Legacy_Plugin_File_List() - { - var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache"); - - var filePath= Path.Combine(tempFolder, string.Format("umbraco-plugins.{0}.list", NetworkHelper.FileSafeMachineName)); - - File.WriteAllText(filePath, @" - - - - -"); - - Assert.IsEmpty(_manager.ReadCache()); // uber-legacy cannot be read - - File.Delete(filePath); - - File.WriteAllText(filePath, @" - - - - -"); - - Assert.IsEmpty(_manager.ReadCache()); // legacy cannot be read - - File.Delete(filePath); - - File.WriteAllText(filePath, @"IContentFinder - -MyContentFinder -AnotherContentFinder - -"); - - Assert.IsNotNull(_manager.ReadCache()); // works - } - - [Test] - public void Create_Cached_Plugin_File() - { - var types = new[] { typeof (PluginManager), typeof (PluginManagerTests), typeof (UmbracoContext) }; - - var typeList1 = new PluginManager.TypeList(typeof (object), null); - foreach (var type in types) typeList1.Add(type); - _manager.AddTypeList(typeList1); - _manager.WriteCache(); - - var plugins = _manager.TryGetCached(typeof (object), null); - var diffType = _manager.TryGetCached(typeof (object), typeof (ObsoleteAttribute)); - - Assert.IsTrue(plugins.Success); - //this will be false since there is no cache of that type resolution kind - Assert.IsFalse(diffType.Success); - - Assert.AreEqual(3, plugins.Result.Count()); - var shouldContain = types.Select(x => x.AssemblyQualifiedName); - //ensure they are all found - Assert.IsTrue(plugins.Result.ContainsAll(shouldContain)); - } - - [Test] - public void Get_Plugins_Hash() - { - //Arrange - var dir = PrepareFolder(); - var d1 = dir.CreateSubdirectory("1"); - var d2 = dir.CreateSubdirectory("2"); - var d3 = dir.CreateSubdirectory("3"); - var d4 = dir.CreateSubdirectory("4"); - var f1 = new FileInfo(Path.Combine(d1.FullName, "test1.dll")); - var f2 = new FileInfo(Path.Combine(d1.FullName, "test2.dll")); - var f3 = new FileInfo(Path.Combine(d2.FullName, "test1.dll")); - var f4 = new FileInfo(Path.Combine(d2.FullName, "test2.dll")); - var f5 = new FileInfo(Path.Combine(d3.FullName, "test1.dll")); - var f6 = new FileInfo(Path.Combine(d3.FullName, "test2.dll")); - var f7 = new FileInfo(Path.Combine(d4.FullName, "test1.dll")); - f1.CreateText().Close(); - f2.CreateText().Close(); - f3.CreateText().Close(); - f4.CreateText().Close(); - f5.CreateText().Close(); - f6.CreateText().Close(); - f7.CreateText().Close(); - var list1 = new[] { f1, f2, f3, f4, f5, f6 }; - var list2 = new[] { f1, f3, f5 }; - var list3 = new[] { f1, f3, f5, f7 }; - - //Act - var hash1 = PluginManager.GetFileHash(list1, new ProfilingLogger(Mock.Of(), Mock.Of())); - var hash2 = PluginManager.GetFileHash(list2, new ProfilingLogger(Mock.Of(), Mock.Of())); - var hash3 = PluginManager.GetFileHash(list3, new ProfilingLogger(Mock.Of(), Mock.Of())); - - //Assert - Assert.AreNotEqual(hash1, hash2); - Assert.AreNotEqual(hash1, hash3); - Assert.AreNotEqual(hash2, hash3); - - Assert.AreEqual(hash1, PluginManager.GetFileHash(list1, new ProfilingLogger(Mock.Of(), Mock.Of()))); - } - - [Test] - public void Ensure_Only_One_Type_List_Created() - { - var foundTypes1 = _manager.ResolveFindMeTypes(); - var foundTypes2 = _manager.ResolveFindMeTypes(); - Assert.AreEqual(1, _manager.TypeLists.Count(x => x.BaseType == typeof(IFindMe) && x.AttributeType == null)); - } - - [Test] - public void Resolves_Assigned_Mappers() - { - var foundTypes1 = _manager.ResolveAssignedMapperTypes(); - Assert.AreEqual(31, foundTypes1.Count()); - } - - [Test] - public void Resolves_Types() - { - var foundTypes1 = _manager.ResolveFindMeTypes(); - Assert.AreEqual(2, foundTypes1.Count()); - } - - [Test] - public void Resolves_Attributed_Trees() - { - var trees = _manager.ResolveAttributedTrees(); - Assert.AreEqual(5, trees.Count()); - } - - [Test] - public void Resolves_Actions() - { - var actions = _manager.ResolveActions(); - Assert.AreEqual(38, actions.Count()); - } - - [Test] - public void Resolves_Trees() - { - var trees = _manager.ResolveTrees(); - Assert.AreEqual(33, trees.Count()); - } - - [Test] - public void Resolves_Applications() - { - var apps = _manager.ResolveApplications(); - Assert.AreEqual(7, apps.Count()); - } - - [Test] - public void Resolves_DataTypes() - { - var types = _manager.ResolveDataTypes(); - Assert.AreEqual(35, types.Count()); - } - - [Test] - public void Resolves_RazorDataTypeModels() - { - var types = _manager.ResolveRazorDataTypeModels(); - Assert.AreEqual(2, types.Count()); - } - - [Test] - public void Resolves_RestExtensions() - { - var types = _manager.ResolveRestExtensions(); - Assert.AreEqual(2, types.Count()); - } - - [Test] - public void Resolves_XsltExtensions() - { - var types = _manager.ResolveXsltExtensions(); - Assert.AreEqual(3, types.Count()); - } - - /// - /// This demonstrates this issue: http://issues.umbraco.org/issue/U4-3505 - the TypeList was returning a list of assignable types - /// not explicit types which is sort of ideal but is confusing so we'll do it the less confusing way. - /// - [Test] - public void TypeList_Resolves_Explicit_Types() - { - var types = new HashSet(); - - var propEditors = new PluginManager.TypeList(typeof (PropertyEditor), null); - propEditors.Add(typeof(LabelPropertyEditor)); - types.Add(propEditors); - - var found = types.SingleOrDefault(x => x.BaseType == typeof (PropertyEditor) && x.AttributeType == null); - - Assert.IsNotNull(found); - - //This should not find a type list of this type - var shouldNotFind = types.SingleOrDefault(x => x.BaseType == typeof (IParameterEditor) && x.AttributeType == null); - - Assert.IsNull(shouldNotFind); - } - - [XsltExtension("Blah.Blah")] - public class MyXsltExtension - { - - } - - - [Umbraco.Web.BaseRest.RestExtension("Blah")] - public class MyRestExtesion - { - - } - - public interface IFindMe : IDiscoverable - { - - } - - public class FindMe1 : IFindMe - { - - } - - public class FindMe2 : IFindMe - { - - } - - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/tests/Umbraco.Tests/PublishedContent/NuCacheTests.cs deleted file mode 100644 index 3f1bd09639..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ /dev/null @@ -1,316 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Logging; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Changes; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure.PublishedCache; -using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Cms.Tests.Common; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing.Objects; -using Umbraco.Web.Composing; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public class NuCacheTests - { - private IPublishedSnapshotService _snapshotService; - private IVariationContextAccessor _variationAccesor; - private IContentCacheDataSerializerFactory _contentNestedDataSerializerFactory; - private ContentType _contentType; - private PropertyType _propertyType; - - [TearDown] - public void Teardown() - { - _snapshotService?.Dispose(); - } - - private void Init() - { - var factory = Mock.Of(); - Current.Factory = factory; - - var publishedModelFactory = new NoopPublishedModelFactory(); - Mock.Get(factory).Setup(x => x.GetService(typeof(IPublishedModelFactory))).Returns(publishedModelFactory); - Mock.Get(factory).Setup(x => x.GetService(typeof(IPublishedValueFallback))).Returns(new NoopPublishedValueFallback()); - - // create a content node kit - var kit = new ContentNodeKit - { - ContentTypeId = 2, - Node = new ContentNode(1, Guid.NewGuid(), 0, "-1,1", 0, -1, DateTime.Now, 0), - DraftData = new ContentData - { - Name = "It Works2!", - Published = false, - TemplateId = 0, - VersionId = 2, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary { { "prop", new[] - { - new PropertyData { Culture = "", Segment = "", Value = "val2" }, - new PropertyData { Culture = "fr-FR", Segment = "", Value = "val-fr2" }, - new PropertyData { Culture = "en-UK", Segment = "", Value = "val-uk2" }, - new PropertyData { Culture = "dk-DA", Segment = "", Value = "val-da2" }, - new PropertyData { Culture = "de-DE", Segment = "", Value = "val-de2" } - } } }, - CultureInfos = new Dictionary - { - // draft data = everything, and IsDraft indicates what's edited - { "fr-FR", new CultureVariation { Name = "name-fr2", IsDraft = true, Date = new DateTime(2018, 01, 03, 01, 00, 00) } }, - { "en-UK", new CultureVariation { Name = "name-uk2", IsDraft = true, Date = new DateTime(2018, 01, 04, 01, 00, 00) } }, - { "dk-DA", new CultureVariation { Name = "name-da2", IsDraft = true, Date = new DateTime(2018, 01, 05, 01, 00, 00) } }, - { "de-DE", new CultureVariation { Name = "name-de1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) } } - } - }, - PublishedData = new ContentData - { - Name = "It Works1!", - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = DateTime.Now, - WriterId = 0, - Properties = new Dictionary { { "prop", new[] - { - new PropertyData { Culture = "", Segment = "", Value = "val1" }, - new PropertyData { Culture = "fr-FR", Segment = "", Value = "val-fr1" }, - new PropertyData { Culture = "en-UK", Segment = "", Value = "val-uk1" } - } } }, - CultureInfos = new Dictionary - { - // published data = only what's actually published, and IsDraft has to be false - { "fr-FR", new CultureVariation { Name = "name-fr1", IsDraft = false, Date = new DateTime(2018, 01, 01, 01, 00, 00) } }, - { "en-UK", new CultureVariation { Name = "name-uk1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) } }, - { "de-DE", new CultureVariation { Name = "name-de1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) } } - } - } - }; - - // create a data source for NuCache - var dataSource = new TestDataSource(kit); - _contentNestedDataSerializerFactory = new JsonContentNestedDataSerializerFactory(); - - var runtime = Mock.Of(); - Mock.Get(runtime).Setup(x => x.Level).Returns(RuntimeLevel.Run); - - var serializer = new ConfigurationEditorJsonSerializer(); - - // create data types, property types and content types - var dataType = new DataType(new VoidEditor("Editor", Mock.Of()), serializer) { Id = 3 }; - - var dataTypes = new[] - { - dataType - }; - - _propertyType = new PropertyType(TestHelper.ShortStringHelper, "Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Culture }; - _contentType = new ContentType(TestHelper.ShortStringHelper, -1) { Id = 2, Alias = "alias-ct", Variations = ContentVariation.Culture }; - _contentType.AddPropertyType(_propertyType); - - var contentTypes = new[] - { - _contentType - }; - - var contentTypeService = new Mock(); - contentTypeService.Setup(x => x.GetAll()).Returns(contentTypes); - contentTypeService.Setup(x => x.GetAll(It.IsAny())).Returns(contentTypes); - - var mediaTypeService = new Mock(); - mediaTypeService.Setup(x => x.GetAll()).Returns(Enumerable.Empty()); - mediaTypeService.Setup(x => x.GetAll(It.IsAny())).Returns(Enumerable.Empty()); - - var contentTypeServiceBaseFactory = new Mock(); - contentTypeServiceBaseFactory.Setup(x => x.For(It.IsAny())).Returns(contentTypeService.Object); - - var dataTypeService = Mock.Of(); - Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes); - - // create a service context - var serviceContext = ServiceContext.CreatePartial( - dataTypeService: dataTypeService, - memberTypeService: Mock.Of(), - memberService: Mock.Of(), - contentTypeService: contentTypeService.Object, - mediaTypeService: mediaTypeService.Object, - localizationService: Mock.Of(), - domainService: Mock.Of() - ); - - // create a scope provider - var scopeProvider = Mock.Of(); - Mock.Get(scopeProvider) - .Setup(x => x.CreateScope( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(Mock.Of); - - // create a published content type factory - var contentTypeFactory = new PublishedContentTypeFactory( - publishedModelFactory, - new PropertyValueConverterCollection(Array.Empty()), - dataTypeService); - - // create a variation accessor - _variationAccesor = new TestVariationContextAccessor(); - - var typeFinder = TestHelper.GetTypeFinder(); - - var globalSettings = new GlobalSettings(); - var nuCacheSettings = new NuCacheSettings(); - - // at last, create the complete NuCache snapshot service! - var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; - _snapshotService = new PublishedSnapshotService( - options, - null, - serviceContext, - contentTypeFactory, - new TestPublishedSnapshotAccessor(), - _variationAccesor, - Mock.Of(), - NullLoggerFactory.Instance, - scopeProvider, - dataSource, - new TestDefaultCultureAccessor(), - Microsoft.Extensions.Options.Options.Create(globalSettings), - Mock.Of(), - publishedModelFactory, - TestHelper.GetHostingEnvironment(), - Microsoft.Extensions.Options.Options.Create(nuCacheSettings), - _contentNestedDataSerializerFactory); - - // invariant is the current default - _variationAccesor.VariationContext = new VariationContext(); - - Mock.Get(factory).Setup(x => x.GetService(typeof(IVariationContextAccessor))).Returns(_variationAccesor); - } - - [Test] - public void StandaloneVariations() - { - // this test implements a full standalone NuCache (based upon a test IDataSource, does not - // use any local db files, does not rely on any database) - and tests variations - - Init(); - - // get a snapshot, get a published content - var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - var publishedContent = snapshot.Content.GetById(1); - - Assert.IsNotNull(publishedContent); - Assert.AreEqual("val1", publishedContent.Value(Mock.Of(), "prop")); - Assert.AreEqual("val-fr1", publishedContent.Value(Mock.Of(), "prop", "fr-FR")); - Assert.AreEqual("val-uk1", publishedContent.Value(Mock.Of(), "prop", "en-UK")); - - Assert.IsNull(publishedContent.Name(_variationAccesor)); // no invariant name for varying content - Assert.AreEqual("name-fr1", publishedContent.Name(_variationAccesor, "fr-FR")); - Assert.AreEqual("name-uk1", publishedContent.Name(_variationAccesor, "en-UK")); - - var draftContent = snapshot.Content.GetById(true, 1); - Assert.AreEqual("val2", draftContent.Value(Mock.Of(), "prop")); - Assert.AreEqual("val-fr2", draftContent.Value(Mock.Of(), "prop", "fr-FR")); - Assert.AreEqual("val-uk2", draftContent.Value(Mock.Of(), "prop", "en-UK")); - - Assert.IsNull(draftContent.Name(_variationAccesor)); // no invariant name for varying content - Assert.AreEqual("name-fr2", draftContent.Name(_variationAccesor, "fr-FR")); - Assert.AreEqual("name-uk2", draftContent.Name(_variationAccesor, "en-UK")); - - // now french is default - _variationAccesor.VariationContext = new VariationContext("fr-FR"); - Assert.AreEqual("val-fr1", publishedContent.Value(Mock.Of(), "prop")); - Assert.AreEqual("name-fr1", publishedContent.Name(_variationAccesor)); - Assert.AreEqual(new DateTime(2018, 01, 01, 01, 00, 00), publishedContent.CultureDate(_variationAccesor)); - - // now uk is default - _variationAccesor.VariationContext = new VariationContext("en-UK"); - Assert.AreEqual("val-uk1", publishedContent.Value(Mock.Of(), "prop")); - Assert.AreEqual("name-uk1", publishedContent.Name(_variationAccesor)); - Assert.AreEqual(new DateTime(2018, 01, 02, 01, 00, 00), publishedContent.CultureDate(_variationAccesor)); - - // invariant needs to be retrieved explicitly, when it's not default - Assert.AreEqual("val1", publishedContent.Value(Mock.Of(), "prop", culture: "")); - - // but, - // if the content type / property type does not vary, then it's all invariant again - // modify the content type and property type, notify the snapshot service - _contentType.Variations = ContentVariation.Nothing; - _propertyType.Variations = ContentVariation.Nothing; - _snapshotService.Notify(new[] { new ContentTypeCacheRefresher.JsonPayload("IContentType", publishedContent.ContentType.Id, ContentTypeChangeTypes.RefreshMain) }); - - // get a new snapshot (nothing changed in the old one), get the published content again - var anotherSnapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); - var againContent = anotherSnapshot.Content.GetById(1); - - Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.Variations); - Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.GetPropertyType("prop").Variations); - - // now, "no culture" means "invariant" - Assert.AreEqual("It Works1!", againContent.Name(_variationAccesor)); - Assert.AreEqual("val1", againContent.Value(Mock.Of(), "prop")); - } - - [Test] - public void IsDraftIsPublished() - { - Init(); - - // get the published published content - var s = _snapshotService.CreatePublishedSnapshot(null); - var c1 = s.Content.GetById(1); - - // published content = nothing is draft here - Assert.IsFalse(c1.IsDraft("fr-FR")); - Assert.IsFalse(c1.IsDraft("en-UK")); - Assert.IsFalse(c1.IsDraft("dk-DA")); - Assert.IsFalse(c1.IsDraft("de-DE")); - - // and only those with published name, are published - Assert.IsTrue(c1.IsPublished("fr-FR")); - Assert.IsTrue(c1.IsPublished("en-UK")); - Assert.IsFalse(c1.IsDraft("dk-DA")); - Assert.IsTrue(c1.IsPublished("de-DE")); - - // get the draft published content - var c2 = s.Content.GetById(true, 1); - - // draft content = we have drafts - Assert.IsTrue(c2.IsDraft("fr-FR")); - Assert.IsTrue(c2.IsDraft("en-UK")); - Assert.IsTrue(c2.IsDraft("dk-DA")); - Assert.IsFalse(c2.IsDraft("de-DE")); // except for the one that does not - - // and only those with published name, are published - Assert.IsTrue(c2.IsPublished("fr-FR")); - Assert.IsTrue(c2.IsPublished("en-UK")); - Assert.IsFalse(c2.IsPublished("dk-DA")); - Assert.IsTrue(c2.IsPublished("de-DE")); - } - - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/tests/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs deleted file mode 100644 index 9140aec22a..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.PublishedContent -{ - /// - /// Unit tests for IPublishedContent and extensions - /// - [TestFixture] - public class PublishedContentDataTableTests : BaseWebTest - { - public override void SetUp() - { - base.SetUp(); - - // need to specify a different callback for testing - PublishedContentExtensions.GetPropertyAliasesAndNames = (contentTypeService, mediaTypeService, memberTypeService, alias) => - { - var userFields = new Dictionary() - { - {"property1", "Property 1"}, - {"property2", "Property 2"} - }; - if (alias == "Child") - { - userFields.Add("property4", "Property 4"); - } - else - { - userFields.Add("property3", "Property 3"); - } - - //ensure the standard fields are there - var allFields = new Dictionary() - { - {"Id", "Id"}, - {"NodeName", "NodeName"}, - {"NodeTypeAlias", "NodeTypeAlias"}, - {"CreateDate", "CreateDate"}, - {"UpdateDate", "UpdateDate"}, - {"CreatorId", "CreatorId"}, - {"WriterId", "WriterId"}, - {"Url", "Url"} - }; - foreach (var f in userFields.Where(f => !allFields.ContainsKey(f.Key))) - { - allFields.Add(f.Key, f.Value); - } - return allFields; - }; - var umbracoContext = GetUmbracoContext("/test"); - - //set the UmbracoContext.Current since the extension methods rely on it - Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; - } - - public override void TearDown() - { - base.TearDown(); - PublishedContentExtensions.GetPropertyAliasesAndNames = null; - } - - [Test] - public void To_DataTable() - { - var doc = GetContent(true, 1); - var dt = doc.ChildrenAsTable(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); - - Assert.AreEqual(11, dt.Columns.Count); - Assert.AreEqual(3, dt.Rows.Count); - Assert.AreEqual("value4", dt.Rows[0]["Property 1"]); - Assert.AreEqual("value5", dt.Rows[0]["Property 2"]); - Assert.AreEqual("value6", dt.Rows[0]["Property 4"]); - Assert.AreEqual("value7", dt.Rows[1]["Property 1"]); - Assert.AreEqual("value8", dt.Rows[1]["Property 2"]); - Assert.AreEqual("value9", dt.Rows[1]["Property 4"]); - Assert.AreEqual("value10", dt.Rows[2]["Property 1"]); - Assert.AreEqual("value11", dt.Rows[2]["Property 2"]); - Assert.AreEqual("value12", dt.Rows[2]["Property 4"]); - } - - [Test] - public void To_DataTable_With_Filter() - { - var doc = GetContent(true, 1); - //change a doc type alias - var c = (SolidPublishedContent)doc.Children.ElementAt(0); - c.ContentType = new PublishedContentType(Guid.NewGuid(), 22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); - - var dt = doc.ChildrenAsTable(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), "Child"); - - Assert.AreEqual(11, dt.Columns.Count); - Assert.AreEqual(2, dt.Rows.Count); - Assert.AreEqual("value7", dt.Rows[0]["Property 1"]); - Assert.AreEqual("value8", dt.Rows[0]["Property 2"]); - Assert.AreEqual("value9", dt.Rows[0]["Property 4"]); - Assert.AreEqual("value10", dt.Rows[1]["Property 1"]); - Assert.AreEqual("value11", dt.Rows[1]["Property 2"]); - Assert.AreEqual("value12", dt.Rows[1]["Property 4"]); - } - - [Test] - public void To_DataTable_No_Rows() - { - var doc = GetContent(false, 1); - var dt = doc.ChildrenAsTable(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); - //will return an empty data table - Assert.AreEqual(0, dt.Columns.Count); - Assert.AreEqual(0, dt.Rows.Count); - } - - private IPublishedContent GetContent(bool createChildren, int indexVals) - { - var serializer = new ConfigurationEditorJsonSerializer(); - var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new VoidEditor(DataValueEditorFactory), serializer) { Id = 1 }); - - var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); - var contentTypeAlias = createChildren ? "Parent" : "Child"; - var contentType = new PublishedContentType(Guid.NewGuid(), 22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); - var d = new SolidPublishedContent(contentType) - { - CreateDate = DateTime.Now, - CreatorId = 1, - Id = 3, - SortOrder = 4, - TemplateId = 5, - UpdateDate = DateTime.Now, - Path = "-1,3", - UrlSegment = "home-page", - Name = "Page" + Guid.NewGuid(), - Version = Guid.NewGuid(), - WriterId = 1, - Parent = null, - Level = 1, - Children = new List() - }; - d.Properties = new Collection(new List - { - new RawValueProperty(factory.CreatePropertyType("property1", 1), d, "value" + indexVals), - new RawValueProperty(factory.CreatePropertyType("property2", 1), d, "value" + (indexVals + 1)) - }); - if (createChildren) - { - d.Children = new List() - { - GetContent(false, indexVals + 3), - GetContent(false, indexVals + 6), - GetContent(false, indexVals + 9) - }; - } - - if (!createChildren) - { - //create additional columns, used to test the different columns for child nodes - ((Collection) d.Properties).Add( - new RawValueProperty(factory.CreatePropertyType("property4",1), d, "value" + (indexVals + 2))); - } - else - { - ((Collection) d.Properties).Add( - new RawValueProperty(factory.CreatePropertyType("property3", 1), d, "value" + (indexVals + 2))); - } - - return d; - } - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs b/tests/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs deleted file mode 100644 index 67d861a564..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Collections.Generic; -using NUnit.Framework; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class PublishedContentExtensionTests : PublishedContentTestBase - { - private IUmbracoContext _ctx; - private string _xmlContent = ""; - private bool _createContentTypes = true; - private Dictionary _contentTypes; - - protected override string GetXmlContent(int templateId) - { - return _xmlContent; - } - - [Test] - public void IsDocumentType_NonRecursive_ActualType_ReturnsTrue() - { - InitializeInheritedContentTypes(); - - var publishedContent = _ctx.Content.GetById(1100); - Assert.That(publishedContent.IsDocumentType("inherited", false)); - } - - [Test] - public void IsDocumentType_NonRecursive_BaseType_ReturnsFalse() - { - InitializeInheritedContentTypes(); - - var publishedContent = _ctx.Content.GetById(1100); - Assert.That(publishedContent.IsDocumentType("base", false), Is.False); - } - - [Test] - public void IsDocumentType_Recursive_ActualType_ReturnsTrue() - { - InitializeInheritedContentTypes(); - - var publishedContent = _ctx.Content.GetById(1100); - Assert.That(publishedContent.IsDocumentType("inherited", true)); - } - - [Test] - public void IsDocumentType_Recursive_BaseType_ReturnsTrue() - { - InitializeInheritedContentTypes(); - ContentTypesCache.GetPublishedContentTypeByAlias = null; - - var publishedContent = _ctx.Content.GetById(1100); - Assert.That(publishedContent.IsDocumentType("base", true)); - } - - [Test] - public void IsDocumentType_Recursive_InvalidBaseType_ReturnsFalse() - { - InitializeInheritedContentTypes(); - - var publishedContent = _ctx.Content.GetById(1100); - Assert.That(publishedContent.IsDocumentType("invalidbase", true), Is.False); - } - - private void InitializeInheritedContentTypes() - { - _ctx = GetUmbracoContext("/", 1, null, true); - if (_createContentTypes) - { - var contentTypeService = ServiceContext.ContentTypeService; - var baseType = new ContentType(ShortStringHelper, -1) { Alias = "base", Name = "Base" }; - const string contentTypeAlias = "inherited"; - var inheritedType = new ContentType(ShortStringHelper, baseType, contentTypeAlias) { Alias = contentTypeAlias, Name = "Inherited" }; - contentTypeService.Save(baseType); - contentTypeService.Save(inheritedType); - _contentTypes = new Dictionary - { - { baseType.Alias, new PublishedContentType(baseType, null) }, - { inheritedType.Alias, new PublishedContentType(inheritedType, null) } - }; - ContentTypesCache.GetPublishedContentTypeByAlias = alias => _contentTypes[alias]; - _createContentTypes = false; - } - - ContentTypesCache.GetPublishedContentTypeByAlias = alias => _contentTypes[alias]; - - _xmlContent = @" - - -]> - - -"; - } - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/tests/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs deleted file mode 100644 index 5793262bf8..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ /dev/null @@ -1,363 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.Testing; -using Current = Umbraco.Web.Composing.Current; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - [UmbracoTest(TypeLoader = UmbracoTestOptions.TypeLoader.PerFixture)] - public class PublishedContentLanguageVariantTests : PublishedContentSnapshotTestBase - { - protected override void Compose() - { - base.Compose(); - - Builder.Services.AddUnique(GetServiceContext()); - } - - protected ServiceContext GetServiceContext() - { - var serviceContext = TestObjects.GetServiceContextMock(Factory); - MockLocalizationService(serviceContext); - return serviceContext; - } - - private static void MockLocalizationService(ServiceContext serviceContext) - { - // Set up languages. - // Spanish falls back to English and Italian to Spanish (and then to English). - // French has no fall back. - // Danish, Swedish and Norweigan create an invalid loop. - var globalSettings = new GlobalSettings(); - var languages = new List - { - new Language(globalSettings, "en-US") { Id = 1, CultureName = "English", IsDefault = true }, - new Language(globalSettings, "fr") { Id = 2, CultureName = "French" }, - new Language(globalSettings, "es") { Id = 3, CultureName = "Spanish", FallbackLanguageId = 1 }, - new Language(globalSettings, "it") { Id = 4, CultureName = "Italian", FallbackLanguageId = 3 }, - new Language(globalSettings, "de") { Id = 5, CultureName = "German" }, - new Language(globalSettings, "da") { Id = 6, CultureName = "Danish", FallbackLanguageId = 8 }, - new Language(globalSettings, "sv") { Id = 7, CultureName = "Swedish", FallbackLanguageId = 6 }, - new Language(globalSettings, "no") { Id = 8, CultureName = "Norweigan", FallbackLanguageId = 7 }, - new Language(globalSettings, "nl") { Id = 9, CultureName = "Dutch", FallbackLanguageId = 1 } - }; - - var localizationService = Mock.Get(serviceContext.LocalizationService); - localizationService.Setup(x => x.GetAllLanguages()).Returns(languages); - localizationService.Setup(x => x.GetLanguageById(It.IsAny())) - .Returns((int id) => languages.SingleOrDefault(y => y.Id == id)); - localizationService.Setup(x => x.GetLanguageByIsoCode(It.IsAny())) - .Returns((string c) => languages.SingleOrDefault(y => y.IsoCode == c)); - } - - internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache) - { - var prop1Type = factory.CreatePropertyType("prop1", 1, variations: ContentVariation.Culture); - var welcomeType = factory.CreatePropertyType("welcomeText", 1, variations: ContentVariation.Culture); - var welcome2Type = factory.CreatePropertyType("welcomeText2", 1, variations: ContentVariation.Culture); - var nopropType = factory.CreatePropertyType("noprop", 1, variations: ContentVariation.Culture); - - IEnumerable CreatePropertyTypes1(IPublishedContentType contentType) - { - yield return factory.CreatePropertyType(contentType, "prop1", 1, variations: ContentVariation.Culture); - yield return factory.CreatePropertyType(contentType, "welcomeText", 1, variations: ContentVariation.Culture); - yield return factory.CreatePropertyType(contentType, "welcomeText2", 1, variations: ContentVariation.Culture); - yield return factory.CreatePropertyType(contentType, "noprop", 1, variations: ContentVariation.Culture); - } - - var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes1); - - IEnumerable CreatePropertyTypes2(IPublishedContentType contentType) - { - yield return factory.CreatePropertyType(contentType, "prop3", 1, variations: ContentVariation.Culture); - } - - var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "contentType2", Enumerable.Empty(), CreatePropertyTypes2); - - var prop1 = new SolidPublishedPropertyWithLanguageVariants - { - Alias = "welcomeText", - PropertyType = welcomeType - }; - prop1.SetSourceValue("en-US", "Welcome", true); - prop1.SetValue("en-US", "Welcome", true); - prop1.SetSourceValue("de", "Willkommen"); - prop1.SetValue("de", "Willkommen"); - prop1.SetSourceValue("nl", "Welkom"); - prop1.SetValue("nl", "Welkom"); - - var prop2 = new SolidPublishedPropertyWithLanguageVariants - { - Alias = "welcomeText2", - PropertyType = welcome2Type - }; - prop2.SetSourceValue("en-US", "Welcome", true); - prop2.SetValue("en-US", "Welcome", true); - - var prop3 = new SolidPublishedPropertyWithLanguageVariants - { - Alias = "welcomeText", - PropertyType = welcomeType - }; - prop3.SetSourceValue("en-US", "Welcome", true); - prop3.SetValue("en-US", "Welcome", true); - - var noprop = new SolidPublishedProperty - { - Alias = "noprop", - PropertyType = nopropType - }; - noprop.SolidHasValue = false; // has no value - noprop.SolidValue = "xxx"; // but returns something - - var item1 = new SolidPublishedContent(contentType1) - { - Id = 1, - SortOrder = 0, - Name = "Content 1", - UrlSegment = "content-1", - Path = "/1", - Level = 1, - ParentId = -1, - ChildIds = new[] { 2 }, - Properties = new Collection - { - prop1, prop2, noprop - } - }; - - var item2 = new SolidPublishedContent(contentType1) - { - Id = 2, - SortOrder = 0, - Name = "Content 2", - UrlSegment = "content-2", - Path = "/1/2", - Level = 2, - ParentId = 1, - ChildIds = new int[] { 3 }, - Properties = new Collection - { - prop3 - } - }; - - var prop4 = new SolidPublishedPropertyWithLanguageVariants - { - Alias = "prop3", - PropertyType = contentType2.GetPropertyType("prop3") - }; - prop4.SetSourceValue("en-US", "Oxxo", true); - prop4.SetValue("en-US", "Oxxo", true); - - var item3 = new SolidPublishedContent(contentType2) - { - Id = 3, - SortOrder = 0, - Name = "Content 3", - UrlSegment = "content-3", - Path = "/1/2/3", - Level = 3, - ParentId = 2, - ChildIds = new int[] { }, - Properties = new Collection - { - prop4 - } - }; - - item1.Children = new List { item2 }; - item2.Parent = item1; - - item2.Children = new List { item3 }; - item3.Parent = item2; - - cache.Add(item1); - cache.Add(item2); - cache.Add(item3); - } - - [Test] - public void Can_Get_Content_For_Populated_Requested_Language() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - var value = content.Value(Mock.Of(), "welcomeText", "en-US"); - Assert.AreEqual("Welcome", value); - } - - [Test] - public void Can_Get_Content_For_Populated_Requested_Non_Default_Language() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - var value = content.Value(Mock.Of(), "welcomeText", "de"); - Assert.AreEqual("Willkommen", value); - } - - [Test] - public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_Without_Fallback() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - var value = content.Value(Mock.Of(), "welcomeText", "fr"); - Assert.IsNull(value); - } - - [Test] - public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Unless_Requested() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - var value = content.Value(Mock.Of(), "welcomeText", "es"); - Assert.IsNull(value); - } - - [Test] - public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - var value = content.Value(Factory.GetRequiredService(), "welcomeText", "es", fallback: Fallback.ToLanguage); - Assert.AreEqual("Welcome", value); - } - - [Test] - public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - var value = content.Value(Factory.GetRequiredService(), "welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); - Assert.AreEqual("Welcome", value); - } - - [Test] - public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - var value = content.Value(Mock.Of(), "welcomeText", "no", fallback: Fallback.ToLanguage); - Assert.IsNull(value); - } - - [Test] - public void Do_Not_Get_Content_Recursively_Unless_Requested() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); - var value = content.Value(Mock.Of(), "welcomeText2"); - Assert.IsNull(value); - } - - [Test] - public void Can_Get_Content_Recursively() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); - var value = content.Value(Factory.GetRequiredService(), "welcomeText2", fallback: Fallback.ToAncestors); - Assert.AreEqual("Welcome", value); - } - - [Test] - public void Do_Not_Get_Content_Recursively_Unless_Requested2() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First().Children.First(); - Assert.IsNull(content.GetProperty("welcomeText2")); - var value = content.Value(Mock.Of(), "welcomeText2"); - Assert.IsNull(value); - } - - [Test] - public void Can_Get_Content_Recursively2() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First().Children.First(); - Assert.IsNull(content.GetProperty("welcomeText2")); - var value = content.Value(Factory.GetRequiredService(), "welcomeText2", fallback: Fallback.ToAncestors); - Assert.AreEqual("Welcome", value); - } - - [Test] - public void Can_Get_Content_Recursively3() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First().Children.First(); - Assert.IsNull(content.GetProperty("noprop")); - var value = content.Value(Factory.GetRequiredService(), "noprop", fallback: Fallback.ToAncestors); - // property has no value but we still get the value (ie, the converter would do something) - Assert.AreEqual("xxx", value); - } - - [Test] - public void Can_Get_Content_With_Recursive_Priority() - { - Current.VariationContextAccessor.VariationContext = new VariationContext("nl"); - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); - - var value = content.Value(Factory.GetRequiredService(), "welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language)); - - // No Dutch value is directly assigned. Check has fallen back to Dutch value from parent. - Assert.AreEqual("Welkom", value); - } - - [Test] - public void Can_Get_Content_With_Fallback_Language_Priority() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); - var value = content.Value(Factory.GetRequiredService(), "welcomeText", "nl", fallback: Fallback.ToLanguage); - - // No Dutch value is directly assigned. Check has fallen back to English value from language variant. - Assert.AreEqual("Welcome", value); - } - - [Test] - public void Throws_For_Non_Supported_Fallback() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); - Assert.Throws(() => content.Value(Factory.GetRequiredService(), "welcomeText", "nl", fallback: Fallback.To(999))); - } - - [Test] - public void Can_Fallback_To_Default_Value() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); - - // no Dutch value is assigned, so getting null - var value = content.Value(Factory.GetRequiredService(), "welcomeText", "nl"); - Assert.IsNull(value); - - // even if we 'just' provide a default value - value = content.Value(Factory.GetRequiredService(), "welcomeText", "nl", defaultValue: "woop"); - Assert.IsNull(value); - - // but it works with proper fallback settings - value = content.Value(Factory.GetRequiredService(), "welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop"); - Assert.AreEqual("woop", value); - } - - [Test] - public void Can_Have_Custom_Default_Value() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); - - // HACK: the value, pretend the converter would return something - var prop = content.GetProperty("welcomeText") as SolidPublishedPropertyWithLanguageVariants; - Assert.IsNotNull(prop); - prop.SetValue("nl", "nope"); // HasValue false but getting value returns this - - // there is an EN value - var value = content.Value(Factory.GetRequiredService(), "welcomeText", "en-US"); - Assert.AreEqual("Welcome", value); - - // there is no NL value and we get the 'converted' value - value = content.Value(Factory.GetRequiredService(), "welcomeText", "nl"); - Assert.AreEqual("nope", value); - - // but it works with proper fallback settings - value = content.Value(Factory.GetRequiredService(), "welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop"); - Assert.AreEqual("woop", value); - } - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/tests/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs deleted file mode 100644 index b592e9630d..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Examine; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Infrastructure; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.Testing; -using Umbraco.Web; -using Umbraco.Web.Composing; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - [UmbracoTest(TypeLoader = UmbracoTestOptions.TypeLoader.PerFixture)] - public class PublishedContentMoreTests : PublishedContentSnapshotTestBase - { - internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache) - { - IEnumerable CreatePropertyTypes(IPublishedContentType contentType) - { - yield return factory.CreatePropertyType(contentType, "prop1", 1); - } - - var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes); - var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "ContentType2", Enumerable.Empty(), CreatePropertyTypes); - var contentType2Sub = factory.CreateContentType(Guid.NewGuid(), 3, "ContentType2Sub", Enumerable.Empty(), CreatePropertyTypes); - - var content = new SolidPublishedContent(contentType1) - { - Id = 1, - SortOrder = 0, - Name = "Content 1", - UrlSegment = "content-1", - Path = "/1", - Level = 1, - ParentId = -1, - ChildIds = new int[] { }, - Properties = new Collection - { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } - } - }; - cache.Add(content); - - content = new SolidPublishedContent(contentType2) - { - Id = 2, - SortOrder = 1, - Name = "Content 2", - UrlSegment = "content-2", - Path = "/2", - Level = 1, - ParentId = -1, - ChildIds = new int[] { }, - Properties = new Collection - { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } - } - }; - cache.Add(content); - - content = new SolidPublishedContent(contentType2Sub) - { - Id = 3, - SortOrder = 2, - Name = "Content 2Sub", - UrlSegment = "content-2sub", - Path = "/3", - Level = 1, - ParentId = -1, - ChildIds = new int[] { }, - Properties = new Collection - { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } - } - }; - cache.Add(content); - } - - [Test] - public void First() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - Assert.AreEqual("Content 1", content.Name(VariationContextAccessor)); - } - - [Test] - public void Distinct() - { - var items = Current.UmbracoContext.Content.GetAtRoot() - .Distinct() - .Distinct() - .ToIndexedArray(); - - var item = items[0]; - Assert.AreEqual("Content 1", item.Content.Name); - Assert.IsTrue(item.IsFirst()); - Assert.IsFalse(item.IsLast()); - - item = items[1]; - Assert.AreEqual("Content 2", item.Content.Name); - Assert.IsFalse(item.IsFirst()); - Assert.IsFalse(item.IsLast()); - - item = items[2]; - Assert.AreEqual("Content 2Sub", item.Content.Name); - Assert.IsFalse(item.IsFirst()); - Assert.IsTrue(item.IsLast()); - } - - [Test] - public void OfType1() - { - var items = Current.UmbracoContext.Content.GetAtRoot() - .OfType() - .Distinct() - .ToIndexedArray(); - Assert.AreEqual(2, items.Length); - Assert.IsInstanceOf(items.First().Content); - } - - [Test] - public void OfType2() - { - var content = Current.UmbracoContext.Content.GetAtRoot() - .OfType() - .Distinct() - .ToIndexedArray(); - Assert.AreEqual(1, content.Length); - Assert.IsInstanceOf(content.First().Content); - } - - [Test] - public void OfType() - { - var content = Current.UmbracoContext.Content.GetAtRoot() - .OfType() - .First(x => x.Prop1 == 1234); - Assert.AreEqual("Content 2", content.Name); - Assert.AreEqual(1234, content.Prop1); - } - - [Test] - public void Position() - { - var items = Current.UmbracoContext.Content.GetAtRoot() - .Where(x => x.Value(Mock.Of(), "prop1") == 1234) - .ToIndexedArray(); - - Assert.IsTrue(items.First().IsFirst()); - Assert.IsFalse(items.First().IsLast()); - Assert.IsFalse(items.Skip(1).First().IsFirst()); - Assert.IsFalse(items.Skip(1).First().IsLast()); - Assert.IsFalse(items.Skip(2).First().IsFirst()); - Assert.IsTrue(items.Skip(2).First().IsLast()); - } - - [Test] - public void Issue() - { - var content = Current.UmbracoContext.Content.GetAtRoot() - .Distinct() - .OfType(); - - var where = content.Where(x => x.Prop1 == 1234); - var first = where.First(); - Assert.AreEqual(1234, first.Prop1); - - var content2 = Current.UmbracoContext.Content.GetAtRoot() - .OfType() - .First(x => x.Prop1 == 1234); - Assert.AreEqual(1234, content2.Prop1); - - var content3 = Current.UmbracoContext.Content.GetAtRoot() - .OfType() - .First(); - Assert.AreEqual(1234, content3.Prop1); - } - - [Test] - public void PublishedContentQueryTypedContentList() - { - var examineManager = new Mock(); - var query = new PublishedContentQuery(Current.UmbracoContext.PublishedSnapshot, Current.UmbracoContext.VariationContextAccessor, examineManager.Object); - var result = query.Content(new[] { 1, 2, 4 }).ToArray(); - Assert.AreEqual(2, result.Length); - Assert.AreEqual(1, result[0].Id); - Assert.AreEqual(2, result[1].Id); - } - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs b/tests/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs deleted file mode 100644 index e0b95f95e4..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Web.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Logging; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Cms.Tests.Common; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; - -namespace Umbraco.Tests.PublishedContent -{ - public abstract class PublishedContentSnapshotTestBase : PublishedContentTestBase - { - // read http://stackoverflow.com/questions/7713326/extension-method-that-works-on-ienumerablet-and-iqueryablet - // and http://msmvps.com/blogs/jon_skeet/archive/2010/10/28/overloading-and-generic-constraints.aspx - // and http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx - - public override void SetUp() - { - base.SetUp(); - - var umbracoContext = GetUmbracoContext(); - Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; - } - - protected override void Compose() - { - base.Compose(); - - Builder.Services.AddUnique(f => new PublishedModelFactory(f.GetRequiredService().GetTypes(), f.GetRequiredService())); - } - - protected override TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, ILogger logger, IProfilingLogger profilingLogger , IHostingEnvironment hostingEnvironment) - { - var baseLoader = base.CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, profilingLogger , hostingEnvironment); - - return new TypeLoader(typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, profilingLogger , false, - // this is so the model factory looks into the test assembly - baseLoader.AssembliesToScan - .Union(new[] {typeof(PublishedContentMoreTests).Assembly}) - .ToList()); - } - - private IUmbracoContext GetUmbracoContext() - { - RouteData routeData = null; - - var publishedSnapshot = CreatePublishedSnapshot(); - - var publishedSnapshotService = new Mock(); - publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedSnapshot); - - var globalSettings = TestObjects.GetGlobalSettings(); - - var httpContext = GetHttpContextFactory("http://umbraco.local/", routeData).HttpContext; - - var httpContextAccessor = TestHelper.GetHttpContextAccessor(httpContext); - var umbracoContext = new UmbracoContext( - httpContextAccessor, - publishedSnapshotService.Object, - Mock.Of(), - globalSettings, - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - - return umbracoContext; - } - - private SolidPublishedSnapshot CreatePublishedSnapshot() - { - var serializer = new ConfigurationEditorJsonSerializer(); - var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new VoidEditor(DataValueEditorFactory), serializer) { Id = 1 }); - - var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); - var caches = new SolidPublishedSnapshot(); - var cache = caches.InnerContentCache; - PopulateCache(factory, cache); - return caches; - } - - internal abstract void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache); - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/tests/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs deleted file mode 100644 index 7befa03f45..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Media; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PropertyEditors.ValueConverters; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Templates; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.PublishedContent -{ - /// - /// Abstract base class for tests for published content and published media - /// - public abstract class PublishedContentTestBase : BaseWebTest - { - protected override void Compose() - { - base.Compose(); - - // FIXME: what about the if (PropertyValueConvertersResolver.HasCurrent == false) ?? - // can we risk double - registering and then, what happens? - - Builder.WithCollectionBuilder() - .Clear() - .Append() - .Append() - .Append(); - } - - protected override void Initialize() - { - base.Initialize(); - - var converters = Factory.GetRequiredService(); - var umbracoContextAccessor = Mock.Of(); - var publishedUrlProvider = Mock.Of(); - var loggerFactory = NullLoggerFactory.Instance; - var serializer = new ConfigurationEditorJsonSerializer(); - - var imageSourceParser = new HtmlImageSourceParser(publishedUrlProvider); - var mediaFileManager = new MediaFileManager(Mock.Of(), Mock.Of(), - loggerFactory.CreateLogger(), Mock.Of()); - var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, loggerFactory.CreateLogger(), HostingEnvironment, Mock.Of(), Mock.Of(), mediaFileManager, ShortStringHelper, publishedUrlProvider, serializer); - var localLinkParser = new HtmlLocalLinkParser(umbracoContextAccessor, publishedUrlProvider); - var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new RichTextPropertyEditor( - DataValueEditorFactory, - Mock.Of(), - imageSourceParser, - localLinkParser, - pastedImages, - IOHelper, - Mock.Of()), - serializer) { Id = 1 }); - - - var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - - IEnumerable CreatePropertyTypes(IPublishedContentType contentType) - { - yield return publishedContentTypeFactory.CreatePropertyType(contentType, "content", 1); - } - - var type = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", CreatePropertyTypes); - ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; - - var umbracoContext = GetUmbracoContext("/test"); - Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; - } - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/tests/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs deleted file mode 100644 index 15a867a7d8..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ /dev/null @@ -1,511 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Xml; -using System.Xml.Linq; -using System.Xml.XPath; -using Microsoft.Extensions.DependencyInjection; -using Examine; -using NUnit.Framework; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.Examine; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.TestHelpers.Entities; - - -namespace Umbraco.Tests.PublishedContent -{ - /// - /// Tests the typed extension methods on IPublishedContent using the DefaultPublishedMediaStore - /// - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)] - public class PublishedMediaTests : PublishedContentTestBase - { - /// - /// sets up resolvers before resolution is frozen - /// - protected override void Compose() - { - base.Compose(); - - Builder.WithCollectionBuilder() - .Clear() - .Append(); - - Builder.Services.AddUnique(); - } - - private IMediaType MakeNewMediaType(IUser user, string text, int parentId = -1) - { - var mt = new MediaType(ShortStringHelper, parentId) { Name = text, Alias = text, Thumbnail = "icon-folder", Icon = "icon-folder" }; - ServiceContext.MediaTypeService.Save(mt); - return mt; - } - - private IMedia MakeNewMedia(string name, IMediaType mediaType, IUser user, int parentId) - { - var m = ServiceContext.MediaService.CreateMediaWithIdentity(name, parentId, mediaType.Alias); - return m; - } - - /// - /// Shared with PublishMediaStoreTests - /// - /// - /// - /// - internal IPublishedContent GetNode(int id, IUmbracoContext umbracoContext) - { - var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null, HostingEnvironment), - ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, - Factory.GetRequiredService(), Factory.GetRequiredService(), VariationContextAccessor); - var doc = cache.GetById(id); - Assert.IsNotNull(doc); - return doc; - } - - private IPublishedContent GetNode(int id) - { - return GetNode(id, GetUmbracoContext("/test")); - } - - [Test] - public void Get_Property_Value_Uses_Converter() - { - var mType = MockedContentTypes.CreateImageMediaType("image2"); - //lets add an RTE to this - mType.PropertyGroups.First().PropertyTypes.Add( - new PropertyType(ShortStringHelper, "test", ValueStorageType.Nvarchar, "content") - { - Name = "Rich Text", - DataTypeId = -87 //tiny mce - }); - var existing = ServiceContext.MediaTypeService.GetAll(); - ServiceContext.MediaTypeService.Save(mType); - var media = MockedMedia.CreateMediaImage(mType, -1); - media.Properties["content"].SetValue("
This is some content
"); - ServiceContext.MediaService.Save(media); - - var publishedMedia = GetNode(media.Id); - - var propVal = publishedMedia.Value(Factory.GetRequiredService(), "content"); - Assert.IsInstanceOf(propVal); - Assert.AreEqual("
This is some content
", propVal.ToString()); - - var propVal2 = publishedMedia.Value(Factory.GetRequiredService(), "content"); - Assert.IsInstanceOf(propVal2); - Assert.AreEqual("
This is some content
", propVal2.ToString()); - - var propVal3 = publishedMedia.Value(Factory.GetRequiredService(), "Content"); - Assert.IsInstanceOf(propVal3); - Assert.AreEqual("
This is some content
", propVal3.ToString()); - } - - [Test] - public void Ensure_Children_Sorted_With_Examine() - { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetRequiredService(), IndexInitializer.GetMockMediaService()); - - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, HostingEnvironment, RuntimeState, luceneDir, - validator: new ContentValueSetValidator(true))) - using (indexer.ProcessNonAsync()) - { - rebuilder.RegisterIndex(indexer.Name); - rebuilder.Populate(indexer); - - var searcher = indexer.GetSearcher(); - var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService()); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(1111); - var rootChildren = publishedMedia.Children(VariationContextAccessor).ToArray(); - var currSort = 0; - for (var i = 0; i < rootChildren.Count(); i++) - { - Assert.GreaterOrEqual(rootChildren[i].SortOrder, currSort); - currSort = rootChildren[i].SortOrder; - } - } - } - - [Test] - public void Do_Not_Find_In_Recycle_Bin() - { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetRequiredService(), IndexInitializer.GetMockMediaService()); - - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, HostingEnvironment, RuntimeState, luceneDir, - //include unpublished content since this uses the 'internal' indexer, it's up to the media cache to filter - validator: new ContentValueSetValidator(false))) - using (indexer.ProcessNonAsync()) - { - rebuilder.RegisterIndex(indexer.Name); - rebuilder.Populate(indexer); - - var searcher = indexer.GetSearcher(); - var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService()); - - //ensure it is found - var publishedMedia = cache.GetById(3113); - Assert.IsNotNull(publishedMedia); - - //move item to recycle bin - var newXml = XElement.Parse(@" - - 115 - 268 - 10726 - jpg - "); - indexer.IndexItems(new[]{ newXml.ConvertToValueSet("media") }); - - - //ensure it still exists in the index (raw examine search) - var criteria = searcher.CreateQuery(); - var filter = criteria.Id(3113); - var found = filter.Execute(); - Assert.IsNotNull(found); - Assert.AreEqual(1, found.TotalItemCount); - - //ensure it does not show up in the published media store - var recycledMedia = cache.GetById(3113); - Assert.IsNull(recycledMedia); - - } - - } - - [Test] - public void Children_With_Examine() - { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetRequiredService(), IndexInitializer.GetMockMediaService()); - - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, HostingEnvironment, RuntimeState, luceneDir, - validator: new ContentValueSetValidator(true))) - using (indexer.ProcessNonAsync()) - { - rebuilder.RegisterIndex(indexer.Name); - rebuilder.Populate(indexer); - - var searcher = indexer.GetSearcher(); - var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService()); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(1111); - var rootChildren = publishedMedia.Children(VariationContextAccessor); - Assert.IsTrue(rootChildren.Select(x => x.Id).ContainsAll(new[] { 2222, 1113, 1114, 1115, 1116 })); - - var publishedChild1 = cache.GetById(2222); - var subChildren = publishedChild1.Children(VariationContextAccessor); - Assert.IsTrue(subChildren.Select(x => x.Id).ContainsAll(new[] { 2112 })); - } - } - - [Test] - public void Descendants_With_Examine() - { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetRequiredService(), IndexInitializer.GetMockMediaService()); - - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, HostingEnvironment, RuntimeState, luceneDir, - validator: new ContentValueSetValidator(true))) - using (indexer.ProcessNonAsync()) - { - rebuilder.RegisterIndex(indexer.Name); - rebuilder.Populate(indexer); - - var searcher = indexer.GetSearcher(); - var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService()); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(1111); - var rootDescendants = publishedMedia.Descendants(Factory.GetRequiredService()); - Assert.IsTrue(rootDescendants.Select(x => x.Id).ContainsAll(new[] { 2112, 2222, 1113, 1114, 1115, 1116 })); - - var publishedChild1 = cache.GetById(2222); - var subDescendants = publishedChild1.Descendants(Factory.GetRequiredService()); - Assert.IsTrue(subDescendants.Select(x => x.Id).ContainsAll(new[] { 2112, 3113 })); - } - } - - [Test] - public void DescendantsOrSelf_With_Examine() - { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetRequiredService(), IndexInitializer.GetMockMediaService()); - - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, HostingEnvironment, RuntimeState, luceneDir, - validator: new ContentValueSetValidator(true))) - using (indexer.ProcessNonAsync()) - { - rebuilder.RegisterIndex(indexer.Name); - rebuilder.Populate(indexer); - - var searcher = indexer.GetSearcher(); - var ctx = GetUmbracoContext("/test"); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService()); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(1111); - var rootDescendants = publishedMedia.DescendantsOrSelf(Factory.GetRequiredService()); - Assert.IsTrue(rootDescendants.Select(x => x.Id).ContainsAll(new[] { 1111, 2112, 2222, 1113, 1114, 1115, 1116 })); - - var publishedChild1 = cache.GetById(2222); - var subDescendants = publishedChild1.DescendantsOrSelf(Factory.GetRequiredService()); - Assert.IsTrue(subDescendants.Select(x => x.Id).ContainsAll(new[] { 2222, 2112, 3113 })); - } - } - - [Test] - public void Ancestors_With_Examine() - { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetRequiredService(), IndexInitializer.GetMockMediaService()); - - - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, HostingEnvironment, RuntimeState, luceneDir, - validator: new ContentValueSetValidator(true))) - using (indexer.ProcessNonAsync()) - { - rebuilder.RegisterIndex(indexer.Name); - rebuilder.Populate(indexer); - - var ctx = GetUmbracoContext("/test"); - var searcher = indexer.GetSearcher(); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService()); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(3113); - var ancestors = publishedMedia.Ancestors(); - Assert.IsTrue(ancestors.Select(x => x.Id).ContainsAll(new[] { 2112, 2222, 1111 })); - } - - } - - [Test] - public void AncestorsOrSelf_With_Examine() - { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetRequiredService(), IndexInitializer.GetMockMediaService()); - - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, HostingEnvironment, RuntimeState, luceneDir, - validator: new ContentValueSetValidator(true))) - using (indexer.ProcessNonAsync()) - { - rebuilder.RegisterIndex(indexer.Name); - rebuilder.Populate(indexer); - - - var ctx = GetUmbracoContext("/test"); - var searcher = indexer.GetSearcher(); - var cache = new PublishedMediaCache(ServiceContext.MediaService, ServiceContext.UserService, searcher, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService()); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(3113); - var ancestors = publishedMedia.AncestorsOrSelf(); - Assert.IsTrue(ancestors.Select(x => x.Id).ContainsAll(new[] { 3113, 2112, 2222, 1111 })); - } - } - - [Test] - public void Children_Without_Examine() - { - var user = ServiceContext.UserService.GetUserById(0); - var mType = MakeNewMediaType(user, "TestMediaType"); - var mRoot = MakeNewMedia("MediaRoot", mType, user, -1); - - var mChild1 = MakeNewMedia("Child1", mType, user, mRoot.Id); - var mChild2 = MakeNewMedia("Child2", mType, user, mRoot.Id); - var mChild3 = MakeNewMedia("Child3", mType, user, mRoot.Id); - - var mSubChild1 = MakeNewMedia("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = MakeNewMedia("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = MakeNewMedia("SubChild3", mType, user, mChild1.Id); - - var publishedMedia = GetNode(mRoot.Id); - var rootChildren = publishedMedia.Children(VariationContextAccessor); - Assert.IsTrue(rootChildren.Select(x => x.Id).ContainsAll(new[] { mChild1.Id, mChild2.Id, mChild3.Id })); - - var publishedChild1 = GetNode(mChild1.Id); - var subChildren = publishedChild1.Children(VariationContextAccessor); - Assert.IsTrue(subChildren.Select(x => x.Id).ContainsAll(new[] { mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - } - - [Test] - public void Descendants_Without_Examine() - { - var user = ServiceContext.UserService.GetUserById(0); - var mType = MakeNewMediaType(user, "TestMediaType"); - var mRoot = MakeNewMedia("MediaRoot", mType, user, -1); - - var mChild1 = MakeNewMedia("Child1", mType, user, mRoot.Id); - var mChild2 = MakeNewMedia("Child2", mType, user, mRoot.Id); - var mChild3 = MakeNewMedia("Child3", mType, user, mRoot.Id); - - var mSubChild1 = MakeNewMedia("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = MakeNewMedia("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = MakeNewMedia("SubChild3", mType, user, mChild1.Id); - - var publishedMedia = GetNode(mRoot.Id); - var rootDescendants = publishedMedia.Descendants(Factory.GetRequiredService()); - Assert.IsTrue(rootDescendants.Select(x => x.Id).ContainsAll(new[] { mChild1.Id, mChild2.Id, mChild3.Id, mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - - var publishedChild1 = GetNode(mChild1.Id); - var subDescendants = publishedChild1.Descendants(Factory.GetRequiredService()); - Assert.IsTrue(subDescendants.Select(x => x.Id).ContainsAll(new[] { mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - } - - [Test] - public void DescendantsOrSelf_Without_Examine() - { - var user = ServiceContext.UserService.GetUserById(0); - var mType = MakeNewMediaType(user, "TestMediaType"); - var mRoot = MakeNewMedia("MediaRoot", mType, user, -1); - - var mChild1 = MakeNewMedia("Child1", mType, user, mRoot.Id); - var mChild2 = MakeNewMedia("Child2", mType, user, mRoot.Id); - var mChild3 = MakeNewMedia("Child3", mType, user, mRoot.Id); - - var mSubChild1 = MakeNewMedia("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = MakeNewMedia("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = MakeNewMedia("SubChild3", mType, user, mChild1.Id); - - var publishedMedia = GetNode(mRoot.Id); - var rootDescendantsOrSelf = publishedMedia.DescendantsOrSelf(Factory.GetRequiredService()); - Assert.IsTrue(rootDescendantsOrSelf.Select(x => x.Id).ContainsAll( - new[] { mRoot.Id, mChild1.Id, mChild2.Id, mChild3.Id, mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - - var publishedChild1 = GetNode(mChild1.Id); - var subDescendantsOrSelf = publishedChild1.DescendantsOrSelf(Factory.GetRequiredService()); - Assert.IsTrue(subDescendantsOrSelf.Select(x => x.Id).ContainsAll( - new[] { mChild1.Id, mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - } - - [Test] - public void Parent_Without_Examine() - { - var user = ServiceContext.UserService.GetUserById(0); - var mType = MakeNewMediaType(user, "TestMediaType"); - var mRoot = MakeNewMedia("MediaRoot", mType, user, -1); - - var mChild1 = MakeNewMedia("Child1", mType, user, mRoot.Id); - var mChild2 = MakeNewMedia("Child2", mType, user, mRoot.Id); - var mChild3 = MakeNewMedia("Child3", mType, user, mRoot.Id); - - var mSubChild1 = MakeNewMedia("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = MakeNewMedia("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = MakeNewMedia("SubChild3", mType, user, mChild1.Id); - - var publishedRoot = GetNode(mRoot.Id); - Assert.AreEqual(null, publishedRoot.Parent); - - var publishedChild1 = GetNode(mChild1.Id); - Assert.AreEqual(mRoot.Id, publishedChild1.Parent.Id); - - var publishedSubChild1 = GetNode(mSubChild1.Id); - Assert.AreEqual(mChild1.Id, publishedSubChild1.Parent.Id); - } - - [Test] - public void Ancestors_Without_Examine() - { - var user = ServiceContext.UserService.GetUserById(0); - var mType = MakeNewMediaType(user, "TestMediaType"); - var mRoot = MakeNewMedia("MediaRoot", mType, user, -1); - - var mChild1 = MakeNewMedia("Child1", mType, user, mRoot.Id); - var mChild2 = MakeNewMedia("Child2", mType, user, mRoot.Id); - var mChild3 = MakeNewMedia("Child3", mType, user, mRoot.Id); - - var mSubChild1 = MakeNewMedia("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = MakeNewMedia("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = MakeNewMedia("SubChild3", mType, user, mChild1.Id); - - var publishedSubChild1 = GetNode(mSubChild1.Id); - Assert.IsTrue(publishedSubChild1.Ancestors().Select(x => x.Id).ContainsAll(new[] { mChild1.Id, mRoot.Id })); - } - - [Test] - public void AncestorsOrSelf_Without_Examine() - { - var user = ServiceContext.UserService.GetUserById(0); - var mType = MakeNewMediaType(user, "TestMediaType"); - var mRoot = MakeNewMedia("MediaRoot", mType, user, -1); - - var mChild1 = MakeNewMedia("Child1", mType, user, mRoot.Id); - var mChild2 = MakeNewMedia("Child2", mType, user, mRoot.Id); - var mChild3 = MakeNewMedia("Child3", mType, user, mRoot.Id); - - var mSubChild1 = MakeNewMedia("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = MakeNewMedia("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = MakeNewMedia("SubChild3", mType, user, mChild1.Id); - - var publishedSubChild1 = GetNode(mSubChild1.Id); - Assert.IsTrue(publishedSubChild1.AncestorsOrSelf().Select(x => x.Id).ContainsAll( - new[] { mSubChild1.Id, mChild1.Id, mRoot.Id })); - } - - [Test] - public void Convert_From_Standard_Xml() - { - var nodeId = 2112; - - var xml = XElement.Parse(@" - - 115 - 268 - 10726 - jpg - - - 115 - 268 - 10726 - jpg - - "); - var node = xml.DescendantsAndSelf("Image").Single(x => (int)x.Attribute("id") == nodeId); - - var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService(), VariationContextAccessor); - - var nav = node.CreateNavigator(); - - var converted = publishedMedia.CreateFromCacheValues( - publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/Image"), nodeId)); - - Assert.AreEqual(nodeId, converted.Id); - Assert.AreEqual(3, converted.Level); - Assert.AreEqual(1, converted.SortOrder); - Assert.AreEqual("Sam's Umbraco Image", converted.Name); - Assert.AreEqual("-1,1111,2222,2112", converted.Path); - } - - [Test] - public void Detects_Error_In_Xml() - { - var errorXml = new XElement("error", string.Format("No media is maching '{0}'", 1234)); - var nav = errorXml.CreateNavigator(); - - var publishedMedia = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService(), Factory.GetRequiredService(), VariationContextAccessor); - var converted = publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/"), 1234); - - Assert.IsNull(converted); - } - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/tests/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs deleted file mode 100644 index e59043e087..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading.Tasks; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Routing; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public class PublishedRouterTests : BaseWebTest - { - [Test] - public async Task ConfigureRequest_Returns_False_Without_HasPublishedContent() - { - var umbracoContext = GetUmbracoContext("/test"); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var result = publishedRouter.BuildRequest(request); - - Assert.IsFalse(result.Success()); - } - - [Test] - public async Task ConfigureRequest_Returns_False_When_IsRedirect() - { - var umbracoContext = GetUmbracoContext("/test"); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var content = GetPublishedContentMock(); - request.SetPublishedContent(content.Object); - request.SetCulture("en-AU"); - request.SetRedirect("/hello"); - var result = publishedRouter.BuildRequest(request); - - Assert.IsFalse(result.Success()); - } - - private Mock GetPublishedContentMock() - { - var pc = new Mock(); - pc.Setup(content => content.Id).Returns(1); - pc.Setup(content => content.Name).Returns("test"); - pc.Setup(content => content.CreateDate).Returns(DateTime.Now); - pc.Setup(content => content.UpdateDate).Returns(DateTime.Now); - pc.Setup(content => content.Path).Returns("-1,1"); - pc.Setup(content => content.Parent).Returns(() => null); - pc.Setup(content => content.Properties).Returns(new Collection()); - pc.Setup(content => content.ContentType).Returns(new PublishedContentType(Guid.NewGuid(), 22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing)); - return pc; - } - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/RootNodeTests.cs b/tests/Umbraco.Tests/PublishedContent/RootNodeTests.cs deleted file mode 100644 index 4aad3d0acb..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/RootNodeTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Linq; -using NUnit.Framework; -using Umbraco.Web; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public class RootNodeTests : PublishedContentTestBase - { - [Test] - public void PublishedContentHasNoRootNode() - { - var ctx = GetUmbracoContext("/test"); - - // there is no content node with ID -1 - var content = ctx.Content.GetById(-1); - Assert.IsNull(content); - - // content at root has null parent - content = ctx.Content.GetById(1046); - Assert.IsNotNull(content); - Assert.AreEqual(1, content.Level); - Assert.IsNull(content.Parent); - - // non-existing content is null - content = ctx.Content.GetById(666); - Assert.IsNull(content); - } - - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/tests/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs deleted file mode 100644 index 4b2d999ce3..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ /dev/null @@ -1,449 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Xml; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Extensions; -using Umbraco.Web.Composing; - -namespace Umbraco.Tests.PublishedContent -{ - public class SolidPublishedSnapshot : IPublishedSnapshot - { - public readonly SolidPublishedContentCache InnerContentCache = new SolidPublishedContentCache(); - public readonly SolidPublishedContentCache InnerMediaCache = new SolidPublishedContentCache(); - - public IPublishedContentCache Content => InnerContentCache; - - public IPublishedMediaCache Media => InnerMediaCache; - - public IPublishedMemberCache Members => null; - - public IDomainCache Domains => null; - - public IDisposable ForcedPreview(bool forcedPreview, Action callback = null) - { - throw new NotImplementedException(); - } - - public void Resync() - { } - - public IAppCache SnapshotCache => null; - - public IAppCache ElementsCache => null; - - public void Dispose() - { } - } - - public class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache - { - private readonly Dictionary _content = new Dictionary(); - - public SolidPublishedContentCache() - : base(false) - { } - - public void Add(SolidPublishedContent content) - { - _content[content.Id] = content.CreateModel(Current.PublishedModelFactory); - } - - public void Clear() - { - _content.Clear(); - } - - public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string culture = null) - { - throw new NotImplementedException(); - } - - public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null) - { - throw new NotImplementedException(); - } - - public string GetRouteById(bool preview, int contentId, string culture = null) - { - throw new NotImplementedException(); - } - - public string GetRouteById(int contentId, string culture = null) - { - throw new NotImplementedException(); - } - - public override IPublishedContent GetById(bool preview, int contentId) - { - return _content.ContainsKey(contentId) ? _content[contentId] : null; - } - - public override IPublishedContent GetById(bool preview, Guid contentId) - { - throw new NotImplementedException(); - } - - public override IPublishedContent GetById(bool preview, Udi nodeId) - => throw new NotSupportedException(); - - public override bool HasById(bool preview, int contentId) - { - return _content.ContainsKey(contentId); - } - - public override IEnumerable GetAtRoot(bool preview, string culture = null) - { - return _content.Values.Where(x => x.Parent == null); - } - - public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) - { - throw new NotImplementedException(); - } - - public override IPublishedContent GetSingleByXPath(bool preview, System.Xml.XPath.XPathExpression xpath, XPathVariable[] vars) - { - throw new NotImplementedException(); - } - - public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) - { - throw new NotImplementedException(); - } - - public override IEnumerable GetByXPath(bool preview, System.Xml.XPath.XPathExpression xpath, XPathVariable[] vars) - { - throw new NotImplementedException(); - } - - public override System.Xml.XPath.XPathNavigator CreateNavigator(bool preview) - { - throw new NotImplementedException(); - } - - public override System.Xml.XPath.XPathNavigator CreateNodeNavigator(int id, bool preview) - { - throw new NotImplementedException(); - } - - public override bool HasContent(bool preview) - { - return _content.Count > 0; - } - - public override IPublishedContentType GetContentType(int id) - { - throw new NotImplementedException(); - } - - public override IPublishedContentType GetContentType(string alias) - { - throw new NotImplementedException(); - } - - public override IPublishedContentType GetContentType(Guid key) - { - throw new NotImplementedException(); - } - - public override IEnumerable GetByContentType(IPublishedContentType contentType) - { - throw new NotImplementedException(); - } - } - - public class SolidPublishedContent : IPublishedContent - { - #region Constructor - - public SolidPublishedContent(IPublishedContentType contentType) - { - // initialize boring stuff - TemplateId = 0; - WriterId = CreatorId = 0; - CreateDate = UpdateDate = DateTime.Now; - Version = Guid.Empty; - - ContentType = contentType; - } - - #endregion - - #region Content - - private Dictionary _cultures; - - private Dictionary GetCultures() - { - return new Dictionary { { "", new PublishedCultureInfo("", Name, UrlSegment, UpdateDate) } }; - } - - public int Id { get; set; } - public Guid Key { get; set; } - public int? TemplateId { get; set; } - public int SortOrder { get; set; } - public string Name { get; set; } - public IReadOnlyDictionary Cultures => _cultures ?? (_cultures = GetCultures()); - public string UrlSegment { get; set; } - public int WriterId { get; set; } - public int CreatorId { get; set; } - public string Path { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public Guid Version { get; set; } - public int Level { get; set; } - - public PublishedItemType ItemType => PublishedItemType.Content; - public bool IsDraft(string culture = null) => false; - public bool IsPublished(string culture = null) => true; - - #endregion - - #region Tree - - public int ParentId { get; set; } - public IEnumerable ChildIds { get; set; } - - public IPublishedContent Parent { get; set; } - public IEnumerable Children { get; set; } - public IEnumerable ChildrenForAllCultures => Children; - - #endregion - - #region ContentType - - public IPublishedContentType ContentType { get; set; } - - #endregion - - #region Properties - - public IEnumerable Properties { get; set; } - - public IPublishedProperty GetProperty(string alias) - { - return Properties.FirstOrDefault(p => p.Alias.InvariantEquals(alias)); - } - - public IPublishedProperty GetProperty(string alias, bool recurse) - { - var property = GetProperty(alias); - if (recurse == false) return property; - - IPublishedContent content = this; - while (content != null && (property == null || property.HasValue() == false)) - { - content = content.Parent; - property = content?.GetProperty(alias); - } - - return property; - } - - public object this[string alias] - { - get - { - var property = GetProperty(alias); - return property == null || property.HasValue() == false ? null : property.GetValue(); - } - } - - #endregion - } - - public class SolidPublishedProperty : IPublishedProperty - { - public IPublishedPropertyType PropertyType { get; set; } - public string Alias { get; set; } - public object SolidSourceValue { get; set; } - public object SolidValue { get; set; } - public bool SolidHasValue { get; set; } - public object SolidXPathValue { get; set; } - - public virtual object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue; - public virtual object GetValue(string culture = null, string segment = null) => SolidValue; - public virtual object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue; - public virtual bool HasValue(string culture = null, string segment = null) => SolidHasValue; - } - - public class SolidPublishedPropertyWithLanguageVariants : SolidPublishedProperty - { - private readonly IDictionary _solidSourceValues = new Dictionary(); - private readonly IDictionary _solidValues = new Dictionary(); - private readonly IDictionary _solidXPathValues = new Dictionary(); - - public override object GetSourceValue(string culture = null, string segment = null) - { - if (string.IsNullOrEmpty(culture)) - { - return base.GetSourceValue(culture, segment); - } - - return _solidSourceValues.ContainsKey(culture) ? _solidSourceValues[culture] : null; - } - - public override object GetValue(string culture = null, string segment = null) - { - if (string.IsNullOrEmpty(culture)) - { - return base.GetValue(culture, segment); - } - - return _solidValues.ContainsKey(culture) ? _solidValues[culture] : null; - } - - public override object GetXPathValue(string culture = null, string segment = null) - { - if (string.IsNullOrEmpty(culture)) - { - return base.GetXPathValue(culture, segment); - } - - return _solidXPathValues.ContainsKey(culture) ? _solidXPathValues[culture] : null; - } - - public override bool HasValue(string culture = null, string segment = null) - { - if (string.IsNullOrEmpty(culture)) - { - return base.HasValue(culture, segment); - } - - return _solidSourceValues.ContainsKey(culture); - } - - public void SetSourceValue(string culture, object value, bool defaultValue = false) - { - _solidSourceValues.Add(culture, value); - if (defaultValue) - { - SolidSourceValue = value; - SolidHasValue = true; - } - } - - public void SetValue(string culture, object value, bool defaultValue = false) - { - _solidValues.Add(culture, value); - if (defaultValue) - { - SolidValue = value; - SolidHasValue = true; - } - } - - public void SetXPathValue(string culture, object value, bool defaultValue = false) - { - _solidXPathValues.Add(culture, value); - if (defaultValue) - { - SolidXPathValue = value; - } - } - } - - [PublishedModel("ContentType2")] - public class ContentType2 : PublishedContentModel - { - #region Plumbing - - public ContentType2(IPublishedContent content, IPublishedValueFallback fallback) - : base(content, fallback) - { } - - #endregion - - public int Prop1 => this.Value(Mock.Of(), "prop1"); - } - - [PublishedModel("ContentType2Sub")] - public class ContentType2Sub : ContentType2 - { - #region Plumbing - - public ContentType2Sub(IPublishedContent content, IPublishedValueFallback fallback) - : base(content, fallback) - { } - - #endregion - } - - public class PublishedContentStrong1 : PublishedContentModel - { - public PublishedContentStrong1(IPublishedContent content, IPublishedValueFallback fallback) - : base(content, fallback) - { } - - public int StrongValue => this.Value(Mock.Of(), "strongValue"); - } - - public class PublishedContentStrong1Sub : PublishedContentStrong1 - { - public PublishedContentStrong1Sub(IPublishedContent content, IPublishedValueFallback fallback) - : base(content, fallback) - { } - - public int AnotherValue => this.Value(Mock.Of(), "anotherValue"); - } - - public class PublishedContentStrong2 : PublishedContentModel - { - public PublishedContentStrong2(IPublishedContent content, IPublishedValueFallback fallback) - : base(content, fallback) - { } - - public int StrongValue => this.Value(Mock.Of(), "strongValue"); - } - - public class AutoPublishedContentType : PublishedContentType - { - private static readonly IPublishedPropertyType Default; - - static AutoPublishedContentType() - { - var serializer = new ConfigurationEditorJsonSerializer(); - var dataTypeServiceMock = new Mock(); - var dataType = new DataType(new VoidEditor(Mock.Of()), serializer) - { Id = 666 }; - dataTypeServiceMock.Setup(x => x.GetAll()).Returns(dataType.Yield); - - var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeServiceMock.Object); - Default = factory.CreatePropertyType("*", 666); - } - - public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable propertyTypes) - : base(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) - { } - - public AutoPublishedContentType(Guid key, int id, string alias, Func> propertyTypes) - : base(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) - { } - - public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) - : base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) - { } - - public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable compositionAliases, Func> propertyTypes) - : base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) - { } - - public override IPublishedPropertyType GetPropertyType(string alias) - { - var propertyType = base.GetPropertyType(alias); - return propertyType ?? Default; - } - } -} diff --git a/tests/Umbraco.Tests/PublishedContent/StronglyTypedModels/Home.cs b/tests/Umbraco.Tests/PublishedContent/StronglyTypedModels/Home.cs deleted file mode 100644 index d9a3807c25..0000000000 --- a/tests/Umbraco.Tests/PublishedContent/StronglyTypedModels/Home.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Web; -using Umbraco.Core.Models; - -namespace Umbraco.Tests.PublishedContent.StronglyTypedModels -{ - /// - /// Used for testing strongly-typed published content extensions that work against the - /// - public class Home : TypedModelBase - { - public Home(IPublishedContent publishedContent) : base(publishedContent) - { - } - } -} diff --git a/tests/Umbraco.Tests/Routing/ContentFinderByIdTests.cs b/tests/Umbraco.Tests/Routing/ContentFinderByIdTests.cs deleted file mode 100644 index 652706377c..0000000000 --- a/tests/Umbraco.Tests/Routing/ContentFinderByIdTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Web; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - public class ContentFinderByIdTests : BaseWebTest - { - [TestCase("/1046", 1046)] - [TestCase("/1046.aspx", 1046)] - public async Task Lookup_By_Id(string urlAsString, int nodeMatch) - { - var umbracoContext = GetUmbracoContext(urlAsString); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var webRoutingSettings = new WebRoutingSettings(); - var lookup = new ContentFinderByIdPath(Microsoft.Extensions.Options.Options.Create(webRoutingSettings), LoggerFactory.CreateLogger(), Factory.GetRequiredService(), GetUmbracoContextAccessor(umbracoContext)); - - - var result = lookup.TryFindContent(frequest); - - Assert.IsTrue(result); - Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch); - } - } -} diff --git a/tests/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs b/tests/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs deleted file mode 100644 index 6372dc7424..0000000000 --- a/tests/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Threading.Tasks; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Web; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - public class ContentFinderByPageIdQueryTests : BaseWebTest - { - [TestCase("/?umbPageId=1046", 1046)] - [TestCase("/?UMBPAGEID=1046", 1046)] - [TestCase("/default.aspx?umbPageId=1046", 1046)] // TODO: Should this match?? - [TestCase("/some/other/page?umbPageId=1046", 1046)] // TODO: Should this match?? - [TestCase("/some/other/page.aspx?umbPageId=1046", 1046)] // TODO: Should this match?? - public async Task Lookup_By_Page_Id(string urlAsString, int nodeMatch) - { - var umbracoContext = GetUmbracoContext(urlAsString); - var httpContext = GetHttpContextFactory(urlAsString).HttpContext; - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var mockRequestAccessor = new Mock(); - mockRequestAccessor.Setup(x => x.GetRequestValue("umbPageID")).Returns(httpContext.Request.QueryString["umbPageID"]); - - var lookup = new ContentFinderByPageIdQuery(mockRequestAccessor.Object, GetUmbracoContextAccessor(umbracoContext)); - - var result = lookup.TryFindContent(frequest); - - Assert.IsTrue(result); - Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch); - } - } -} diff --git a/tests/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs b/tests/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs deleted file mode 100644 index a7d256c9ed..0000000000 --- a/tests/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class ContentFinderByUrlAndTemplateTests : BaseWebTest - { - Template CreateTemplate(string alias) - { - var template = new Template(ShortStringHelper, alias, alias); - template.Content = ""; // else saving throws with a dirty internal error - ServiceContext.FileService.SaveTemplate(template); - return template; - } - - [TestCase("/blah")] - [TestCase("/default.aspx/blah")] //this one is actually rather important since this is the path that comes through when we are running in pre-IIS 7 for the root document '/' ! - [TestCase("/home/Sub1/blah")] - [TestCase("/Home/Sub1/Blah")] //different cases - [TestCase("/home/Sub1.aspx/blah")] - public async Task Match_Document_By_Url_With_Template(string urlAsString) - { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var template1 = CreateTemplate("test"); - var template2 = CreateTemplate("blah"); - var umbracoContext = GetUmbracoContext(urlAsString, template1.Id, globalSettings: globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var reqBuilder = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var webRoutingSettings = new WebRoutingSettings(); - var lookup = new ContentFinderByUrlAndTemplate( - LoggerFactory.CreateLogger(), - ServiceContext.FileService, - ServiceContext.ContentTypeService, - GetUmbracoContextAccessor(umbracoContext), - Microsoft.Extensions.Options.Options.Create(webRoutingSettings)); - - var result = lookup.TryFindContent(reqBuilder); - - IPublishedRequest frequest = reqBuilder.Build(); - - Assert.IsTrue(result); - Assert.IsNotNull(frequest.PublishedContent); - var templateAlias = frequest.GetTemplateAlias(); - Assert.IsNotNull(templateAlias ); - Assert.AreEqual("blah".ToUpperInvariant(), templateAlias.ToUpperInvariant()); - } - } -} diff --git a/tests/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs b/tests/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs deleted file mode 100644 index 35f06627fe..0000000000 --- a/tests/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class ContentFinderByUrlTests : BaseWebTest - { - [TestCase("/", 1046)] - [TestCase("/default.aspx", 1046)] //this one is actually rather important since this is the path that comes through when we are running in pre-IIS 7 for the root document '/' ! - [TestCase("/Sub1", 1173)] - [TestCase("/sub1", 1173)] - [TestCase("/sub1.aspx", 1173)] - [TestCase("/home/sub1", -1)] // should fail - - // these two are special. getNiceUrl(1046) returns "/" but getNiceUrl(1172) cannot also return "/" so - // we've made it return "/test-page" => we have to support that URL back in the lookup... - [TestCase("/home", 1046)] - [TestCase("/test-page", 1172)] - public async Task Match_Document_By_Url_Hide_Top_Level(string urlString, int expectedId) - { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true }; - - var snapshotService = CreatePublishedSnapshotService(globalSettings); - var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings, snapshotService: snapshotService); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); - - Assert.IsTrue(globalSettings.HideTopLevelNodeFromPath); - - // FIXME: debugging - going further down, the routes cache is NOT empty?! - if (urlString == "/home/sub1") - System.Diagnostics.Debugger.Break(); - - var result = lookup.TryFindContent(frequest); - - if (expectedId > 0) - { - Assert.IsTrue(result); - Assert.AreEqual(expectedId, frequest.PublishedContent.Id); - } - else - { - Assert.IsFalse(result); - } - } - - [TestCase("/", 1046)] - [TestCase("/default.aspx", 1046)] //this one is actually rather important since this is the path that comes through when we are running in pre-IIS 7 for the root document '/' ! - [TestCase("/home", 1046)] - [TestCase("/home/Sub1", 1173)] - [TestCase("/Home/Sub1", 1173)] //different cases - [TestCase("/home/Sub1.aspx", 1173)] - public async Task Match_Document_By_Url(string urlString, int expectedId) - { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); - - Assert.IsFalse(globalSettings.HideTopLevelNodeFromPath); - - var result = lookup.TryFindContent(frequest); - - Assert.IsTrue(result); - Assert.AreEqual(expectedId, frequest.PublishedContent.Id); - } - /// - /// This test handles requests with special characters in the URL. - /// - /// - /// - [TestCase("/", 1046)] - [TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)] - [TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)] - public async Task Match_Document_By_Url_With_Special_Characters(string urlString, int expectedId) - { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); - - var result = lookup.TryFindContent(frequest); - - Assert.IsTrue(result); - Assert.AreEqual(expectedId, frequest.PublishedContent.Id); - } - - /// - /// This test handles requests with a hostname associated. - /// The logic for handling this goes through the DomainHelper and is a bit different - /// from what happens in a normal request - so it has a separate test with a mocked - /// hostname added. - /// - /// - /// - [TestCase("/", 1046)] - [TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)] - [TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)] - public async Task Match_Document_By_Url_With_Special_Characters_Using_Hostname(string urlString, int expectedId) - { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite", -1, "en-US", false), new Uri("http://mysite/"))); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); - - var result = lookup.TryFindContent(frequest); - - Assert.IsTrue(result); - Assert.AreEqual(expectedId, frequest.PublishedContent.Id); - } - - /// - /// This test handles requests with a hostname with special characters associated. - /// The logic for handling this goes through the DomainHelper and is a bit different - /// from what happens in a normal request - so it has a separate test with a mocked - /// hostname added. - /// - /// - /// - [TestCase("/æøå/", 1046)] - [TestCase("/æøå/home/sub1", 1173)] - [TestCase("/æøå/home/sub1/custom-sub-3-with-accént-character", 1179)] - [TestCase("/æøå/home/sub1/custom-sub-4-with-æøå", 1180)] - public async Task Match_Document_By_Url_With_Special_Characters_In_Hostname(string urlString, int expectedId) - { - var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; - - var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite/æøå", -1, "en-US", false), new Uri("http://mysite/æøå"))); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); - - var result = lookup.TryFindContent(frequest); - - Assert.IsTrue(result); - Assert.AreEqual(expectedId, frequest.PublishedContent.Id); - } - } -} diff --git a/tests/Umbraco.Tests/Routing/GetContentUrlsTests.cs b/tests/Umbraco.Tests/Routing/GetContentUrlsTests.cs deleted file mode 100644 index fe371e7302..0000000000 --- a/tests/Umbraco.Tests/Routing/GetContentUrlsTests.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers.Entities; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - public class GetContentUrlsTests : UrlRoutingTestBase - { - private GlobalSettings _globalSettings; - private WebRoutingSettings _webRoutingSettings; - private RequestHandlerSettings _requestHandlerSettings; - - public override void SetUp() - { - base.SetUp(); - - _globalSettings = new GlobalSettings(); - _webRoutingSettings = new WebRoutingSettings(); - _requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - } - - private ILocalizedTextService GetTextService() - { - var textService = Mock.Of( - x => x.Localize("content/itemNotPublished", - It.IsAny(), - It.IsAny>()) == "content/itemNotPublished"); - return textService; - } - - private ILocalizationService GetLangService(params string[] isoCodes) - { - var allLangs = isoCodes - .Select(CultureInfo.GetCultureInfo) - .Select(culture => new Language(_globalSettings, culture.Name) - { - CultureName = culture.DisplayName, - IsDefault = true, - IsMandatory = true - }).ToArray(); - - var langService = Mock.Of(x => x.GetAllLanguages() == allLangs); - return langService; - } - - [Test] - public async Task Content_Not_Published() - { - var contentType = MockedContentTypes.CreateBasicContentType(); - var content = MockedContent.CreateBasicContent(contentType); - content.Id = 1046; // FIXME: we are using this ID only because it's built into the test XML published cache - content.Path = "-1,1046"; - - var umbContext = GetUmbracoContext("http://localhost:8000"); - var publishedRouter = CreatePublishedRouter( - GetUmbracoContextAccessor(umbContext), - Factory, - contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbContext)) })); - var urls = (await content.GetContentUrlsAsync(publishedRouter, - umbContext, - GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, - VariationContextAccessor, - LoggerFactory.CreateLogger(), - UriUtility, - PublishedUrlProvider)).ToList(); - - Assert.AreEqual(1, urls.Count); - Assert.AreEqual("content/itemNotPublished", urls[0].Text); - Assert.IsFalse(urls[0].IsUrl); - } - - [Test] - public async Task Invariant_Root_Content_Published_No_Domains() - { - var contentType = MockedContentTypes.CreateBasicContentType(); - var content = MockedContent.CreateBasicContent(contentType); - content.Id = 1046; // FIXME: we are using this ID only because it's built into the test XML published cache - content.Path = "-1,1046"; - content.Published = true; - - var umbContext = GetUmbracoContext("http://localhost:8000"); - var umbracoContextAccessor = GetUmbracoContextAccessor(umbContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(_requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), - umbracoContextAccessor, UriUtility); - var publishedUrlProvider = new UrlProvider( - umbracoContextAccessor, - Microsoft.Extensions.Options.Options.Create(_webRoutingSettings), - new UrlProviderCollection(new []{urlProvider}), - new MediaUrlProviderCollection(Enumerable.Empty()), - Mock.Of() - ); - - var publishedRouter = CreatePublishedRouter( - umbracoContextAccessor, - Factory, - contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(LoggerFactory.CreateLogger(), umbracoContextAccessor) })); - var urls = (await content.GetContentUrlsAsync(publishedRouter, - umbContext, - GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, - VariationContextAccessor, - LoggerFactory.CreateLogger(), - UriUtility, - publishedUrlProvider)).ToList(); - - Assert.AreEqual(1, urls.Count); - Assert.AreEqual("/home/", urls[0].Text); - Assert.AreEqual("en-US", urls[0].Culture); - Assert.IsTrue(urls[0].IsUrl); - } - - [Test] - public async Task Invariant_Child_Content_Published_No_Domains() - { - var contentType = MockedContentTypes.CreateBasicContentType(); - var parent = MockedContent.CreateBasicContent(contentType); - parent.Id = 1046; // FIXME: we are using this ID only because it's built into the test XML published cache - parent.Name = "home"; - parent.Path = "-1,1046"; - parent.Published = true; - var child = MockedContent.CreateBasicContent(contentType); - child.Name = "sub1"; - child.Id = 1173; // FIXME: we are using this ID only because it's built into the test XML published cache - child.Path = "-1,1046,1173"; - child.Published = true; - - var umbContext = GetUmbracoContext("http://localhost:8000"); - var umbracoContextAccessor = GetUmbracoContextAccessor(umbContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(_requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = new UrlProvider( - umbracoContextAccessor, - Microsoft.Extensions.Options.Options.Create(_webRoutingSettings), - new UrlProviderCollection(new []{urlProvider}), - new MediaUrlProviderCollection(Enumerable.Empty()), - Mock.Of() - ); - - var publishedRouter = CreatePublishedRouter( - umbracoContextAccessor, - Factory, - contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger(), umbracoContextAccessor) })); - var urls = (await child.GetContentUrlsAsync(publishedRouter, - umbContext, - GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, - VariationContextAccessor, - LoggerFactory.CreateLogger(), - UriUtility, - publishedUrlProvider - )).ToList(); - - Assert.AreEqual(1, urls.Count); - Assert.AreEqual("/home/sub1/", urls[0].Text); - Assert.AreEqual("en-US", urls[0].Culture); - Assert.IsTrue(urls[0].IsUrl); - } - - // TODO: We need a lot of tests here, the above was just to get started with being able to unit test this method - // * variant URLs without domains assigned, what happens? - // * variant URLs with domains assigned, but also having more languages installed than there are domains/cultures assigned - // * variant URLs with an ancestor culture unpublished - // * invariant URLs with ancestors as variants - // * ... probably a lot more - - } -} diff --git a/tests/Umbraco.Tests/Routing/RouteTestExtensions.cs b/tests/Umbraco.Tests/Routing/RouteTestExtensions.cs deleted file mode 100644 index 642488c256..0000000000 --- a/tests/Umbraco.Tests/Routing/RouteTestExtensions.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Web; -using System.Web.Routing; -using Moq; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Routing -{ - public static class RouteTestExtensions - { - - /// - /// Return the route data for the URL based on a mocked context - /// - /// - /// - /// - public static RouteData GetDataForRoute(this RouteCollection routes, string requestUrl) - { - var context = new FakeHttpContextFactory(requestUrl); - return routes.GetDataForRoute(context.HttpContext); - } - - /// - /// Get the route data based on the URL and HTTP context - /// - /// - /// - /// - public static RouteData GetDataForRoute(this RouteCollection routes, HttpContextBase httpContext) - { - var data = routes.GetRouteData(httpContext); - - //set the route data on the request context - var requestMock = Mock.Get(httpContext.Request.RequestContext); - requestMock.Setup(x => x.RouteData).Returns(data); - - return data; - } - - /// - /// Checks if the URL will be ignored in the RouteTable - /// - /// - /// - /// - /// MVCContrib has a similar one but is faulty: - /// http://mvccontrib.codeplex.com/workitem/7173 - /// - public static bool ShouldIgnoreRoute(this string url) - { - var http = new FakeHttpContextFactory(url); - var r = RouteTable.Routes.GetRouteData(http.HttpContext); - if (r == null) return false; - return (r.RouteHandler is StopRoutingHandler); - } - - } -} diff --git a/tests/Umbraco.Tests/Routing/RoutesCacheTests.cs b/tests/Umbraco.Tests/Routing/RoutesCacheTests.cs deleted file mode 100644 index ba851bf761..0000000000 --- a/tests/Umbraco.Tests/Routing/RoutesCacheTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NUnit.Framework; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class RoutesCacheTests : BaseWebTest - { - [Test] - public void U4_7939() - { - //var routingContext = GetRoutingContext("/test", 1111); - var umbracoContext = GetUmbracoContext("/test", 0); - var cache = umbracoContext.PublishedSnapshot.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); - - // FIXME: not sure? - //PublishedContentCache.UnitTesting = false; // else does not write to routes cache - //Assert.IsFalse(PublishedContentCache.UnitTesting); - - var z = cache.GetByRoute(false, "/home/sub1"); - Assert.IsNotNull(z); - Assert.AreEqual(1173, z.Id); - - var routes = cache.RoutesCache.GetCachedRoutes(); - Assert.AreEqual(1, routes.Count); - - // before the fix, the following assert would fail because the route would - // have been stored as { 0, "/home/sub1" } - essentially meaning we were NOT - // storing anything in the route cache! - - Assert.AreEqual(1173, routes.Keys.First()); - Assert.AreEqual("/home/sub1", routes.Values.First()); - } - } -} diff --git a/tests/Umbraco.Tests/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs b/tests/Umbraco.Tests/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs deleted file mode 100644 index 3a2ccc23df..0000000000 --- a/tests/Umbraco.Tests/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.Extensions.Logging; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class UrlProviderWithHideTopLevelNodeFromPathTests : BaseUrlProviderTest - { - private GlobalSettings _globalSettings; - - public override void SetUp() - { - _globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = HideTopLevelNodeFromPath }; - base.SetUp(); - PublishedSnapshotService = CreatePublishedSnapshotService(_globalSettings); - - - } - - protected override bool HideTopLevelNodeFromPath => true; - - protected override void ComposeSettings() - { - base.ComposeSettings(); - Builder.Services.AddUnique(x => Microsoft.Extensions.Options.Options.Create(_globalSettings)); - } - - [TestCase(1046, "/")] - [TestCase(1173, "/sub1/")] - [TestCase(1174, "/sub1/sub2/")] - [TestCase(1176, "/sub1/sub-3/")] - [TestCase(1177, "/sub1/custom-sub-1/")] - [TestCase(1178, "/sub1/custom-sub-2/")] - [TestCase(1175, "/sub-2/")] - [TestCase(1172, "/test-page/")] // not hidden because not first root - public void Get_Url_Hiding_Top_Level(int nodeId, string niceUrlMatch) - { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - - var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: _globalSettings, snapshotService:PublishedSnapshotService); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - - var result = publishedUrlProvider.GetUrl(nodeId); - Assert.AreEqual(niceUrlMatch, result); - } - } -} diff --git a/tests/Umbraco.Tests/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs b/tests/Umbraco.Tests/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs deleted file mode 100644 index bbadf897d0..0000000000 --- a/tests/Umbraco.Tests/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.PublishedContent; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class UrlProviderWithoutHideTopLevelNodeFromPathTests : BaseUrlProviderTest - { - private readonly GlobalSettings _globalSettings; - - public UrlProviderWithoutHideTopLevelNodeFromPathTests() - { - _globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = HideTopLevelNodeFromPath }; - } - - protected override bool HideTopLevelNodeFromPath => false; - - protected override void ComposeSettings() - { - base.ComposeSettings(); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(_globalSettings)); - } - - /// - /// This checks that when we retrieve a NiceUrl for multiple items that there are no issues with cache overlap - /// and that they are all cached correctly. - /// - [Test] - public void Ensure_Cache_Is_Correct() - { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = false }; - - var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: _globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - - var samples = new Dictionary { - { 1046, "/home" }, - { 1173, "/home/sub1" }, - { 1174, "/home/sub1/sub2" }, - { 1176, "/home/sub1/sub-3" }, - { 1177, "/home/sub1/custom-sub-1" }, - { 1178, "/home/sub1/custom-sub-2" }, - { 1175, "/home/sub-2" }, - { 1172, "/test-page" } - }; - - foreach (var sample in samples) - { - var result = publishedUrlProvider.GetUrl(sample.Key); - Assert.AreEqual(sample.Value, result); - } - - var randomSample = new KeyValuePair(1177, "/home/sub1/custom-sub-1"); - for (int i = 0; i < 5; i++) - { - var result = publishedUrlProvider.GetUrl(randomSample.Key); - Assert.AreEqual(randomSample.Value, result); - } - - var cache = umbracoContext.Content as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); - var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); - Assert.AreEqual(8, cachedRoutes.Count); - - foreach (var sample in samples) - { - Assert.IsTrue(cachedRoutes.ContainsKey(sample.Key)); - Assert.AreEqual(sample.Value, cachedRoutes[sample.Key]); - } - - var cachedIds = cache.RoutesCache.GetCachedIds(); - Assert.AreEqual(0, cachedIds.Count); - } - - [TestCase(1046, "/home/")] - [TestCase(1173, "/home/sub1/")] - [TestCase(1174, "/home/sub1/sub2/")] - [TestCase(1176, "/home/sub1/sub-3/")] - [TestCase(1177, "/home/sub1/custom-sub-1/")] - [TestCase(1178, "/home/sub1/custom-sub-2/")] - [TestCase(1175, "/home/sub-2/")] - [TestCase(1172, "/test-page/")] - public void Get_Url_Not_Hiding_Top_Level(int nodeId, string niceUrlMatch) - { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - - var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: _globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - - var result = publishedUrlProvider.GetUrl(nodeId); - Assert.AreEqual(niceUrlMatch, result); - } - - [Test] - public void Get_Url_For_Culture_Variant_Without_Domains_Non_Current_Url() - { - const string currentUri = "http://example.us/test"; - - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - - var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); - var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; - - var publishedContentCache = new Mock(); - publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) - .Returns("9876/home/test-fr"); //prefix with the root id node with the domain assigned as per the umbraco standard - publishedContentCache.Setup(x => x.GetById(It.IsAny())) - .Returns(id => id == 1234 ? publishedContent : null); - - var domainCache = new Mock(); - domainCache.Setup(x => x.GetAssigned(It.IsAny(), false)) - .Returns((int contentId, bool includeWildcards) => Enumerable.Empty()); - - var snapshot = Mock.Of(x => x.Content == publishedContentCache.Object && x.Domains == domainCache.Object); - - var snapshotService = new Mock(); - snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())) - .Returns(snapshot); - - var umbracoContext = GetUmbracoContext(currentUri, - globalSettings: _globalSettings, - snapshotService: snapshotService.Object); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - - //even though we are asking for a specific culture URL, there are no domains assigned so all that can be returned is a normal relative URL. - var url = publishedUrlProvider.GetUrl(1234, culture: "fr-FR"); - - Assert.AreEqual("/home/test-fr/", url); - } - - /// - /// This tests DefaultUrlProvider.GetUrl with a specific culture when the current URL is the culture specific domain - /// - [Test] - public void Get_Url_For_Culture_Variant_With_Current_Url() - { - const string currentUri = "http://example.fr/test"; - - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - - var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); - var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; - - var publishedContentCache = new Mock(); - publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) - .Returns("9876/home/test-fr"); //prefix with the root id node with the domain assigned as per the umbraco standard - publishedContentCache.Setup(x => x.GetById(It.IsAny())) - .Returns(id => id == 1234 ? publishedContent : null); - - var domainCache = new Mock(); - domainCache.Setup(x => x.GetAssigned(It.IsAny(), false)) - .Returns((int contentId, bool includeWildcards) => - { - if (contentId != 9876) return Enumerable.Empty(); - return new[] - { - new Domain(2, "example.us", 9876, "en-US", false), //default - new Domain(3, "example.fr", 9876, "fr-FR", false) - }; - }); - domainCache.Setup(x => x.DefaultCulture).Returns(CultureInfo.GetCultureInfo("en-US").Name); - - var snapshot = Mock.Of(x => x.Content == publishedContentCache.Object && x.Domains == domainCache.Object); - - var snapshotService = new Mock(); - snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())) - .Returns(snapshot); - - var umbracoContext = GetUmbracoContext(currentUri, - globalSettings: _globalSettings, - snapshotService: snapshotService.Object); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - - var url = publishedUrlProvider.GetUrl(1234, culture: "fr-FR"); - - Assert.AreEqual("/home/test-fr/", url); - } - - /// - /// This tests DefaultUrlProvider.GetUrl with a specific culture when the current URL is not the culture specific domain - /// - [Test] - public void Get_Url_For_Culture_Variant_Non_Current_Url() - { - const string currentUri = "http://example.us/test"; - - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - - var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); - var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; - - var publishedContentCache = new Mock(); - publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) - .Returns("9876/home/test-fr"); //prefix with the root id node with the domain assigned as per the umbraco standard - publishedContentCache.Setup(x => x.GetById(It.IsAny())) - .Returns(id => id == 1234 ? publishedContent : null); - - var domainCache = new Mock(); - domainCache.Setup(x => x.GetAssigned(It.IsAny(), false)) - .Returns((int contentId, bool includeWildcards) => - { - if (contentId != 9876) return Enumerable.Empty(); - return new[] - { - new Domain(2, "example.us", 9876, "en-US", false), //default - new Domain(3, "example.fr", 9876, "fr-FR", false) - }; - }); - domainCache.Setup(x => x.DefaultCulture).Returns(CultureInfo.GetCultureInfo("en-US").Name); - - var snapshot = Mock.Of(x => x.Content == publishedContentCache.Object && x.Domains == domainCache.Object); - - var snapshotService = new Mock(); - snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())) - .Returns(snapshot); - - var umbracoContext = GetUmbracoContext(currentUri, - globalSettings: _globalSettings, - snapshotService: snapshotService.Object); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - - - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - var url = publishedUrlProvider.GetUrl(1234, culture: "fr-FR"); - - //the current uri is not the culture specific domain we want, so the result is an absolute path to the culture specific domain - Assert.AreEqual("http://example.fr/home/test-fr/", url); - } - - [Test] - public void Get_Url_Relative_Or_Absolute() - { - var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; - - var umbracoContext = GetUmbracoContext("http://example.com/test", 1111, globalSettings: _globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var urlProvider = new DefaultUrlProvider( - Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), umbracoContextAccessor, UriUtility); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - - Assert.AreEqual("/home/sub1/custom-sub-1/", publishedUrlProvider.GetUrl(1177)); - - publishedUrlProvider.Mode = UrlMode.Absolute; - Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", publishedUrlProvider.GetUrl(1177)); - } - - [Test] - public void Get_Url_Unpublished() - { - var requestHandlerSettings = new RequestHandlerSettings(); - - var urlProvider = new DefaultUrlProvider(Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), - LoggerFactory.CreateLogger(), - new SiteDomainMapper(), UmbracoContextAccessor, UriUtility); - var umbracoContext = GetUmbracoContext("http://example.com/test", 1111, globalSettings: _globalSettings); - var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider); - - //mock the Umbraco settings that we need - - Assert.AreEqual("#", publishedUrlProvider.GetUrl(999999)); - - publishedUrlProvider.Mode = UrlMode.Absolute; - - Assert.AreEqual("#", publishedUrlProvider.GetUrl(999999)); - } - } -} diff --git a/tests/Umbraco.Tests/Routing/UrlRoutingTestBase.cs b/tests/Umbraco.Tests/Routing/UrlRoutingTestBase.cs deleted file mode 100644 index e2f195b09d..0000000000 --- a/tests/Umbraco.Tests/Routing/UrlRoutingTestBase.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public abstract class UrlRoutingTestBase : BaseWebTest - { - /// - /// Sets up the mock domain service - /// - /// - protected IDomainService SetupDomainServiceMock(IEnumerable allDomains) - { - var domainService = Mock.Get(ServiceContext.DomainService); - //setup mock domain service - domainService.Setup(service => service.GetAll(It.IsAny())) - .Returns((bool incWildcards) => incWildcards ? allDomains : allDomains.Where(d => d.IsWildcard == false)); - domainService.Setup(service => service.GetAssignedDomains(It.IsAny(), It.IsAny())) - .Returns((int id, bool incWildcards) => allDomains.Where(d => d.RootContentId == id && (incWildcards || d.IsWildcard == false))); - return domainService.Object; - } - - protected override void Compose() - { - base.Compose(); - - Builder.Services.AddUnique(GetServiceContext()); - } - - protected ServiceContext GetServiceContext() - { - // get the mocked service context to get the mocked domain service - var serviceContext = TestObjects.GetServiceContextMock(Factory); - - //setup mock domain service - var domainService = Mock.Get(serviceContext.DomainService); - domainService.Setup(service => service.GetAll(It.IsAny())) - .Returns((bool incWildcards) => new[] - { - new UmbracoDomain("domain1.com/"){Id = 1, LanguageId = LangDeId, RootContentId = 1001, LanguageIsoCode = "de-DE"}, - new UmbracoDomain("domain1.com/en"){Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}, - new UmbracoDomain("domain1.com/fr"){Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"} - }); - - return serviceContext; - } - - public const int LangDeId = 333; - public const int LangEngId = 334; - public const int LangFrId = 335; - public const int LangCzId = 336; - public const int LangNlId = 337; - public const int LangDkId = 338; - } -} diff --git a/tests/Umbraco.Tests/Scoping/PassThroughEventDispatcherTests.cs b/tests/Umbraco.Tests/Scoping/PassThroughEventDispatcherTests.cs deleted file mode 100644 index ca4767c0f1..0000000000 --- a/tests/Umbraco.Tests/Scoping/PassThroughEventDispatcherTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -using NUnit.Framework; - -namespace Umbraco.Tests.Scoping -{ - [TestFixture] - [NUnit.Framework.Ignore("Cannot dispatch events on NoScope!")] - public class PassThroughEventDispatcherTests - { - // FIXME: so... should we remove, or enable, these tests? - - // [Test] - // public void TriggersCancelableEvents() - // { - // var counter = 0; - - // DoThing1 += (sender, args) => { counter++; }; - - // var scopeProvider = new ScopeProvider(Mock.Of()); - // var events = scopeProvider.GetAmbientOrNoScope().Events; - // events.DispatchCancelable(DoThing1, this, new CancellableEventArgs()); - - // Assert.AreEqual(1, counter); - // } - - // [Test] - // public void TriggersEvents() - // { - // var counter = 0; - - // DoThing1 += (sender, args) => { counter++; }; - // DoThing2 += (sender, args) => { counter++; }; - // DoThing3 += (sender, args) => { counter++; }; - - // var scopeProvider = new ScopeProvider(Mock.Of()); - // var events = scopeProvider.GetAmbientOrNoScope().Events; - // events.Dispatch(DoThing1, this, new EventArgs()); - // events.Dispatch(DoThing2, this, new EventArgs()); - // events.Dispatch(DoThing3, this, new EventArgs()); - - // Assert.AreEqual(3, counter); - // } - - // [Test] - // public void DoesNotQueueEvents() - // { - // var scopeProvider = new ScopeProvider(Mock.Of()); - // var events = scopeProvider.GetAmbientOrNoScope().Events; - // events.Dispatch(DoThing1, this, new EventArgs()); - // events.Dispatch(DoThing2, this, new EventArgs()); - // events.Dispatch(DoThing3, this, new EventArgs()); - - // Assert.IsEmpty(events.GetEvents(EventDefinitionFilter.All)); - // } - - // public event EventHandler DoThing1; - // public event EventHandler DoThing2; - // public event TypedEventHandler DoThing3; - } -} diff --git a/tests/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/tests/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs deleted file mode 100644 index b1131d7787..0000000000 --- a/tests/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Web.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Sync; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.PublishedCache; -using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; -using Umbraco.Web.Composing; - -namespace Umbraco.Tests.Scoping -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] - public class ScopedNuCacheTests : TestWithDatabaseBase - { - private DistributedCacheBinder _distributedCacheBinder; - - protected override void Compose() - { - base.Compose(); - - // the cache refresher component needs to trigger to refresh caches - // but then, it requires a lot of plumbing ;( - // FIXME: and we cannot inject a DistributedCache yet - // so doing all this mess - Builder.Services.AddUnique(); - Builder.Services.AddUnique(f => Mock.Of()); - Builder.WithCollectionBuilder() - .Add(() => Builder.TypeLoader.GetCacheRefreshers()); - Builder.AddNotificationHandler(); - } - - public class NotificationHandler : INotificationHandler - { - public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification); - - public static Action PublishedContent { get; set; } - } - - public override void TearDown() - { - base.TearDown(); - - NotificationHandler.PublishedContent = null; - } - - protected override IPublishedSnapshotService CreatePublishedSnapshotService(GlobalSettings globalSettings = null) - { - var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; - var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Current.UmbracoContextAccessor); - var runtimeStateMock = new Mock(); - runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run); - - var contentTypeFactory = Factory.GetRequiredService(); - var documentRepository = Mock.Of(); - var mediaRepository = Mock.Of(); - var memberRepository = Mock.Of(); - var hostingEnvironment = TestHelper.GetHostingEnvironment(); - - var typeFinder = TestHelper.GetTypeFinder(); - - var nuCacheSettings = new NuCacheSettings(); - var lifetime = new Mock(); - var repository = new NuCacheContentRepository(ScopeProvider, AppCaches.Disabled, Mock.Of>(), memberRepository, documentRepository, mediaRepository, Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider(ShortStringHelper) })); - var snapshotService = new PublishedSnapshotService( - options, - null, - ServiceContext, - contentTypeFactory, - publishedSnapshotAccessor, - Mock.Of(), - base.ProfilingLogger, - NullLoggerFactory.Instance, - ScopeProvider, - new NuCacheContentService(repository, ScopeProvider, NullLoggerFactory.Instance, Mock.Of()), - DefaultCultureAccessor, - Microsoft.Extensions.Options.Options.Create(globalSettings ?? new GlobalSettings()), - Factory.GetRequiredService(), - new NoopPublishedModelFactory(), - hostingEnvironment, - Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); - - return snapshotService; - } - - protected IUmbracoContext GetUmbracoContextNu(string url, RouteData routeData = null, bool setSingleton = false) - { - // ensure we have a PublishedSnapshotService - var service = PublishedSnapshotService as PublishedSnapshotService; - - var httpContext = GetHttpContextFactory(url, routeData).HttpContext; - var httpContextAccessor = TestHelper.GetHttpContextAccessor(httpContext); - var globalSettings = TestObjects.GetGlobalSettings(); - var umbracoContext = new UmbracoContext( - httpContextAccessor, - service, - Mock.Of(), - globalSettings, - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - - if (setSingleton) - Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; - - return umbracoContext; - } - - [TestCase(true)] - [TestCase(false)] - public void TestScope(bool complete) - { - var umbracoContext = GetUmbracoContextNu("http://example.com/", setSingleton: true); - - // wire cache refresher - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(Current.ServerMessenger, Current.CacheRefreshers)); - - // create document type, document - var contentType = new ContentType(ShortStringHelper, -1) { Alias = "CustomDocument", Name = "Custom Document" }; - ServiceContext.ContentTypeService.Save(contentType); - var item = new Content("name", -1, contentType); - - // event handler - var evented = 0; - NotificationHandler.PublishedContent = notification => - { - evented++; - - var e = umbracoContext.Content.GetById(item.Id); - - // during events, due to LiveSnapshot, we see the changes - Assert.IsNotNull(e); - Assert.AreEqual("changed", e.Name(VariationContextAccessor)); - }; - - using (var scope = ScopeProvider.CreateScope()) - { - ServiceContext.ContentService.SaveAndPublish(item); - scope.Complete(); - } - - // been created - var x = umbracoContext.Content.GetById(item.Id); - Assert.IsNotNull(x); - Assert.AreEqual("name", x.Name(VariationContextAccessor)); - - using (var scope = ScopeProvider.CreateScope()) - { - item.Name = "changed"; - ServiceContext.ContentService.SaveAndPublish(item); - - if (complete) - scope.Complete(); - } - - // only 1 event occuring because we are publishing twice for the same event for - // the same object and the scope deduplicates the events (uses the latest) - Assert.AreEqual(complete ? 1 : 0, evented); - - // after the scope, - // if completed, we see the changes - // else changes have been rolled back - x = umbracoContext.Content.GetById(item.Id); - Assert.IsNotNull(x); - Assert.AreEqual(complete ? "changed" : "name", x.Name(VariationContextAccessor)); - } - } -} diff --git a/tests/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/tests/Umbraco.Tests/Scoping/ScopedXmlTests.cs deleted file mode 100644 index 53ef2e0246..0000000000 --- a/tests/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ /dev/null @@ -1,303 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Sync; -using Umbraco.Cms.Infrastructure.Sync; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Composing; - -namespace Umbraco.Tests.Scoping -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] - public class ScopedXmlTests : TestWithDatabaseBase - { - private DistributedCacheBinder _distributedCacheBinder; - - protected override void Compose() - { - base.Compose(); - - // the cache refresher component needs to trigger to refresh caches - // but then, it requires a lot of plumbing ;( - // FIXME: and we cannot inject a DistributedCache yet - // so doing all this mess - Builder.Services.AddUnique(); - Builder.Services.AddUnique(f => Mock.Of()); - Builder.WithCollectionBuilder() - .Add(() => Builder.TypeLoader.GetCacheRefreshers()); - Builder.AddNotificationHandler(); - } - - protected override void ComposeSettings() - { - var contentSettings = new ContentSettings(); - var globalSettings = new GlobalSettings(); - var userPasswordConfigurationSettings = new UserPasswordConfigurationSettings(); - - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(contentSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(globalSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(userPasswordConfigurationSettings)); - } - - - public class NotificationHandler : INotificationHandler - { - public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification); - - public static Action PublishedContent { get; set; } - } - - [TearDown] - public void Teardown() - { - NotificationHandler.PublishedContent = null; - SafeXmlReaderWriter.Cloning = null; - } - - // in 7.6, content.Instance - // .XmlContent - comes from .XmlContentInternal and is cached in context items for current request - // .XmlContentInternal - the actual main xml document - // become in 8 - // xmlStore.Xml - the actual main xml document - // publishedContentCache.GetXml() - the captured xml - - private static XmlStore XmlStore => (Current.Factory.GetRequiredService() as XmlPublishedSnapshotService).XmlStore; - private static XmlDocument XmlMaster => XmlStore.Xml; - private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.Content).GetXml(false); - - [TestCase(true)] - [TestCase(false)] - public void TestScope(bool complete) - { - var umbracoContext = GetUmbracoContext("http://example.com/", setSingleton: true); - - // sanity checks - Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext); - Assert.AreSame(XmlStore, ((PublishedContentCache) umbracoContext.Content).XmlStore); - - // create document type, document - var contentType = new ContentType(ShortStringHelper, -1) { Alias = "CustomDocument", Name = "Custom Document" }; - ServiceContext.ContentTypeService.Save(contentType); - var item = new Content("name", -1, contentType); - - // wire cache refresher - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(Current.ServerMessenger, Current.CacheRefreshers)); - - // check xml in context = "before" - var xml = XmlInContext; - var beforeXml = xml; - var beforeOuterXml = beforeXml.OuterXml; - Console.WriteLine("Xml Before:"); - Console.WriteLine(xml.OuterXml); - - // event handler - var evented = 0; - NotificationHandler.PublishedContent = notification => - { - evented++; - - // should see the changes in context, not in master - xml = XmlInContext; - Assert.AreNotSame(beforeXml, xml); - Console.WriteLine("Xml Event:"); - Console.WriteLine(xml.OuterXml); - var node = xml.GetElementById(item.Id.ToString()); - Assert.IsNotNull(node); - - xml = XmlMaster; - Assert.AreSame(beforeXml, xml); - Assert.AreEqual(beforeOuterXml, xml.OuterXml); - }; - - using (var scope = ScopeProvider.CreateScope()) - { - ServiceContext.ContentService.SaveAndPublish(item); // should create an xml clone - item.Name = "changed"; - ServiceContext.ContentService.SaveAndPublish(item); // should re-use the xml clone - - // this should never change - Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml); - - // this does not change, other thread don't see the changes - xml = XmlMaster; - Assert.AreSame(beforeXml, xml); - Console.WriteLine("XmlInternal During:"); - Console.WriteLine(xml.OuterXml); - Assert.AreEqual(beforeOuterXml, xml.OuterXml); - - // this does not change during the scope (only in events) - // because it is the events that trigger the changes - xml = XmlInContext; - Assert.IsNotNull(xml); - Assert.AreSame(beforeXml, xml); - Assert.AreEqual(beforeOuterXml, xml.OuterXml); - - // note - // this means that, as long as ppl don't create scopes, they'll see their - // changes right after SaveAndPublish, but if they create scopes, - // they will have to wail until the scope is completed, ie wait until the - // events trigger, to use eg GetUrl etc - - if (complete) - scope.Complete(); - } - - //The reason why there is only 1 event occuring is because we are publishing twice for the same event for the same - //object and the scope deduplicates the events(uses the latest) - Assert.AreEqual(complete? 1 : 0, evented); - - // this should never change - Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml); - - xml = XmlInContext; - Console.WriteLine("Xml After:"); - Console.WriteLine(xml.OuterXml); - if (complete) - { - var node = xml.GetElementById(item.Id.ToString()); - Assert.IsNotNull(node); - } - else - { - Assert.AreSame(beforeXml, xml); - Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed! - } - - xml = XmlMaster; - if (complete) - { - var node = xml.GetElementById(item.Id.ToString()); - Assert.IsNotNull(node); - } - else - { - Assert.AreSame(beforeXml, xml); - Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed! - } - } - - [TestCase(true)] - [TestCase(false)] - public void TestScopeMany(bool complete) - { - var umbracoContext = GetUmbracoContext("http://example.com/", setSingleton: true); - - // sanity checks - Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext); - Assert.AreSame(XmlStore, ((PublishedContentCache)umbracoContext.Content).XmlStore); - - // create document type - var contentType = new ContentType(ShortStringHelper,-1) { Alias = "CustomDocument", Name = "Custom Document" }; - ServiceContext.ContentTypeService.Save(contentType); - - // wire cache refresher - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(Current.ServerMessenger, Current.CacheRefreshers)); - - // check xml in context = "before" - var xml = XmlInContext; - var beforeXml = xml; - var beforeOuterXml = beforeXml.OuterXml; - var item = new Content("name", -1, contentType); - const int count = 10; - var ids = new int[count]; - var clones = 0; - - SafeXmlReaderWriter.Cloning = () => { clones++; }; - - Console.WriteLine("Xml Before:"); - Console.WriteLine(xml.OuterXml); - - using (var scope = ScopeProvider.CreateScope()) - { - ServiceContext.ContentService.SaveAndPublish(item); - - for (var i = 0; i < count; i++) - { - var temp = new Content("content_" + i, -1, contentType); - ServiceContext.ContentService.SaveAndPublish(temp); - ids[i] = temp.Id; - } - - // this should never change - Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml); - - xml = XmlMaster; - Assert.IsNotNull(xml); - Console.WriteLine("Xml InScope (before complete):"); - Console.WriteLine(xml.OuterXml); - Assert.AreEqual(beforeOuterXml, xml.OuterXml); - - if (complete) - scope.Complete(); - - xml = XmlMaster; - Assert.IsNotNull(xml); - Console.WriteLine("Xml InScope (after complete):"); - Console.WriteLine(xml.OuterXml); - Assert.AreEqual(beforeOuterXml, xml.OuterXml); - } - - var scopeProvider = ScopeProvider; - Assert.IsNotNull(scopeProvider); - // ambient scope may be null, or maybe not, depending on whether the code that - // was called did proper scoped work, or some direct (NoScope) use of the database - Assert.IsNull(scopeProvider.AmbientContext); - - // limited number of clones! - Assert.AreEqual(complete ? 1 : 0, clones); - - // this should never change - Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml); - - xml = XmlMaster; - Assert.IsNotNull(xml); - Console.WriteLine("Xml After:"); - Console.WriteLine(xml.OuterXml); - if (complete) - { - var node = xml.GetElementById(item.Id.ToString()); - Assert.IsNotNull(node); - - for (var i = 0; i < 10; i++) - { - node = xml.GetElementById(ids[i].ToString()); - Assert.IsNotNull(node); - } - } - else - { - Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed! - } - } - - public class LocalServerMessenger : ServerMessengerBase - { - public LocalServerMessenger() - : base(false) - { } - - public override void SendMessages() { } - - public override void Sync() { } - - protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) - { - throw new NotImplementedException(); - } - } - } -} diff --git a/tests/Umbraco.Tests/Services/Importing/Dictionary-Package.xml b/tests/Umbraco.Tests/Services/Importing/Dictionary-Package.xml deleted file mode 100644 index 18ff588d64..0000000000 --- a/tests/Umbraco.Tests/Services/Importing/Dictionary-Package.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - Dictionary-Package - 1.0 - MIT license - http://not.available - - 3 - 0 - 0 - - - - Test - http://not.available - - - - - - - - - - - - - - - - - - diff --git a/tests/Umbraco.Tests/Services/TestWithSomeContentBase.cs b/tests/Umbraco.Tests/Services/TestWithSomeContentBase.cs deleted file mode 100644 index 4b99f3d127..0000000000 --- a/tests/Umbraco.Tests/Services/TestWithSomeContentBase.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Threading; -using NUnit.Framework; -using Umbraco.Cms.Core.Models; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; - -namespace Umbraco.Tests.Services -{ - [TestFixture] - public abstract class TestWithSomeContentBase : TestWithDatabaseBase - { - public override void SetUp() - { - base.SetUp(); - CreateTestData(); - } - - public virtual void CreateTestData() - { - //NOTE Maybe not the best way to create/save test data as we are using the services, which are being tested. - - //Create and Save ContentType "umbTextpage" -> 1060 - ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); - contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); - ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! - ServiceContext.ContentTypeService.Save(contentType); - - //Create and Save Content "Homepage" based on "umbTextpage" -> 1061 - Content textpage = MockedContent.CreateSimpleContent(contentType); - textpage.Key = new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0"); - ServiceContext.ContentService.Save(textpage, 0); - - //Create and Save Content "Text Page 1" based on "umbTextpage" -> 1062 - Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); - subpage.ContentSchedule.Add(DateTime.Now.AddMinutes(-5), null); - ServiceContext.ContentService.Save(subpage, 0); - - //Create and Save Content "Text Page 1" based on "umbTextpage" -> 1063 - Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); - ServiceContext.ContentService.Save(subpage2, 0); - - Content subpage3 = MockedContent.CreateSimpleContent(contentType, "Text Page 3", textpage.Id); - ServiceContext.ContentService.Save(subpage3, 0); - - //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> 1064 - Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); - trashed.Trashed = true; - ServiceContext.ContentService.Save(trashed, 0); - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/tests/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs deleted file mode 100644 index d0717c1f78..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.IO; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; -using NPoco; -using NUnit.Framework; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Logging; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.Mappers; -using Umbraco.Cms.Persistence.SqlCe; -using Umbraco.Extensions; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.TestHelpers -{ - [TestFixture] - public abstract class BaseUsingSqlCeSyntax - { - protected IMapperCollection Mappers { get; private set; } - - protected ISqlContext SqlContext { get; private set; } - - internal TestObjects TestObjects = new TestObjects(); - - protected Sql Sql() - { - return NPoco.Sql.BuilderFor(SqlContext); - } - - [SetUp] - public virtual void Initialize() - { - var services = TestHelper.GetRegister(); - - var ioHelper = TestHelper.IOHelper; - var logger = new ProfilingLogger(Mock.Of>(), Mock.Of()); - var typeFinder = TestHelper.GetTypeFinder(); - var typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, - new DirectoryInfo(ioHelper.MapPath(Constants.SystemDirectories.TempData)), - Mock.Of>(), - logger, - false); - - var composition = new UmbracoBuilder(services, Mock.Of(), TestHelper.GetMockedTypeLoader()); - - - services.AddUnique(_ => Mock.Of()); - services.AddUnique(_ => NullLoggerFactory.Instance); - services.AddUnique(_ => Mock.Of()); - services.AddUnique(typeLoader); - - composition.WithCollectionBuilder() - .AddCoreMappers(); - - services.AddUnique(_ => SqlContext); - - var factory = Current.Factory = TestHelper.CreateServiceProvider(composition); - - var pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; - var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init()); - var sqlSyntax = new SqlCeSyntaxProvider(Options.Create(new GlobalSettings())); - SqlContext = new SqlContext(sqlSyntax, DatabaseType.SQLCe, pocoDataFactory, new Lazy(() => factory.GetRequiredService())); - Mappers = factory.GetRequiredService(); - - SetUp(); - } - - public virtual void SetUp() - {} - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/tests/Umbraco.Tests/TestHelpers/BaseWebTest.cs deleted file mode 100644 index d686856bb2..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Logging; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Cms.Tests.Common; -using Umbraco.Extensions; -using Umbraco.Tests.PublishedContent; -using Umbraco.Tests.TestHelpers.Stubs; -using Umbraco.Web.Composing; - -namespace Umbraco.Tests.TestHelpers -{ - [TestFixture] - public abstract class BaseWebTest : TestWithDatabaseBase - { - protected override void Compose() - { - base.Compose(); - - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - } - - protected override void Initialize() - { - base.Initialize(); - - // need to specify a custom callback for unit tests - // AutoPublishedContentTypes generates properties automatically - - var serializer = new ConfigurationEditorJsonSerializer(); - var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new VoidEditor(DataValueEditorFactory), serializer) { Id = 1 }); - - var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); - var type = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", new PublishedPropertyType[] { }); - ContentTypesCache.GetPublishedContentTypeByAlias = alias => GetPublishedContentTypeByAlias(alias) ?? type; - } - - protected virtual PublishedContentType GetPublishedContentTypeByAlias(string alias) => null; - - protected override string GetXmlContent(int templateId) - { - return @" - - - - -]> - - - - - 1 - - This is some content]]> - - - - - - - - - - - - - - - - - - -"; - } - - internal PublishedRouter CreatePublishedRouter(IUmbracoContextAccessor umbracoContextAccessor, IServiceProvider container = null, ContentFinderCollection contentFinders = null) - { - var webRoutingSettings = new WebRoutingSettings(); - return CreatePublishedRouter(umbracoContextAccessor, webRoutingSettings, container ?? Factory, contentFinders); - } - - internal static PublishedRouter CreatePublishedRouter(IUmbracoContextAccessor umbracoContextAccessor, WebRoutingSettings webRoutingSettings, IServiceProvider container = null, ContentFinderCollection contentFinders = null) - => new PublishedRouter( - Microsoft.Extensions.Options.Options.Create(webRoutingSettings), - contentFinders ?? new ContentFinderCollection(Enumerable.Empty()), - new TestLastChanceFinder(), - new TestVariationContextAccessor(), - new ProfilingLogger(Mock.Of>(), Mock.Of()), - Mock.Of>(), - Mock.Of(), - Mock.Of(), - container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), - container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), - container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), - umbracoContextAccessor, - Mock.Of() - ); - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingExtensions.cs b/tests/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingExtensions.cs deleted file mode 100644 index c812eeb41d..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Microsoft.Owin.Extensions; -using Owin; - -namespace Umbraco.Tests.TestHelpers.ControllerTesting -{ - public static class AuthenticateEverythingExtensions - { - public static IAppBuilder AuthenticateEverything(this IAppBuilder app) - { - if (app == null) - throw new ArgumentNullException("app"); - app.Use(typeof(AuthenticateEverythingMiddleware), (object)app, (object)new AuthenticateEverythingMiddleware.AuthenticateEverythingAuthenticationOptions()); - app.UseStageMarker(PipelineStage.Authenticate); - return app; - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs b/tests/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs deleted file mode 100644 index 96239d5921..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Infrastructure; -using Owin; -using Umbraco.Extensions; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Security; - -namespace Umbraco.Tests.TestHelpers.ControllerTesting -{ - /// - /// Ensures there's an admin user assigned to the request - /// - public class AuthenticateEverythingMiddleware : AuthenticationMiddleware - { - public AuthenticateEverythingMiddleware(OwinMiddleware next, IAppBuilder app, AuthenticationOptions options) - : base(next, options) - { - } - - protected override AuthenticationHandler CreateHandler() - { - return new AuthenticateEverythingHandler(); - } - - public class AuthenticateEverythingHandler : AuthenticationHandler - { - protected override Task AuthenticateCoreAsync() - { - var securityStamp = Guid.NewGuid().ToString(); - - var identity = new ClaimsIdentity(); - identity.AddRequiredClaims( - Cms.Core.Constants.Security.SuperUserIdAsString, - "admin", - "Admin", - new[] { -1 }, - new[] { -1 }, - "en-US", - securityStamp, - new[] { "content", "media", "members" }, - new[] { "admin" }); - - return Task.FromResult(new AuthenticationTicket( - identity, - new AuthenticationProperties() - { - ExpiresUtc = DateTime.Now.AddDays(1) - })); - } - } - - public class AuthenticateEverythingAuthenticationOptions : AuthenticationOptions - { - public AuthenticateEverythingAuthenticationOptions() - : base("AuthenticateEverything") - { - AuthenticationMode = AuthenticationMode.Active; - } - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/SpecificAssemblyResolver.cs b/tests/Umbraco.Tests/TestHelpers/ControllerTesting/SpecificAssemblyResolver.cs deleted file mode 100644 index a31637981b..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/SpecificAssemblyResolver.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using System.Web.Http.Dispatcher; - -namespace Umbraco.Tests.TestHelpers.ControllerTesting -{ - public class SpecificAssemblyResolver : IAssembliesResolver - { - private readonly Assembly[] _assemblies; - - public SpecificAssemblyResolver(Assembly[] assemblies) - { - _assemblies = assemblies; - } - - public ICollection GetAssemblies() - { - return _assemblies; - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivator.cs b/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivator.cs deleted file mode 100644 index 1c1687dec8..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivator.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Net.Http; -using System.Web.Http; -using Umbraco.Cms.Core.Web; -using Umbraco.Web; - -namespace Umbraco.Tests.TestHelpers.ControllerTesting -{ - public class TestControllerActivator : TestControllerActivatorBase - { - private readonly Func _factory; - - public TestControllerActivator(Func factory) - { - _factory = factory; - } - - protected override ApiController CreateController(Type controllerType, HttpRequestMessage msg, IUmbracoContextAccessor umbracoContextAccessor) - { - return _factory(msg, umbracoContextAccessor); - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs deleted file mode 100644 index 6e7da0a558..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Claims; -using System.Web; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Dispatcher; -using Moq; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Tests.Common; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Web; -using Umbraco.Web.WebApi; - -namespace Umbraco.Tests.TestHelpers.ControllerTesting -{ - /// - /// Used to mock all of the services required for re-mocking and testing controllers - /// - /// - /// A more complete version of this is found in the Umbraco REST API project but this has the basics covered - /// - public abstract class TestControllerActivatorBase : DefaultHttpControllerActivator, IHttpControllerActivator - { - IHttpController IHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) - { - // default - if (!typeof (UmbracoApiControllerBase).IsAssignableFrom(controllerType)) - return base.Create(request, controllerDescriptor, controllerType); - - var owinContext = request.TryGetOwinContext().Result; - - var mockedUserService = Mock.Of(); - var mockedContentService = Mock.Of(); - var mockedMediaService = Mock.Of(); - var mockedEntityService = Mock.Of(); - var mockedMemberService = Mock.Of(); - var mockedMemberTypeService = Mock.Of(); - var mockedDataTypeService = Mock.Of(); - var mockedContentTypeService = Mock.Of(); - - var serviceContext = ServiceContext.CreatePartial( - userService: mockedUserService, - contentService: mockedContentService, - mediaService: mockedMediaService, - entityService: mockedEntityService, - memberService: mockedMemberService, - memberTypeService: mockedMemberTypeService, - dataTypeService: mockedDataTypeService, - contentTypeService: mockedContentTypeService, - localizedTextService:Mock.Of()); - - var globalSettings = new GlobalSettings(); - - // FIXME: v8? - ////new app context - //var dbCtx = new Mock(Mock.Of(), Mock.Of(), Mock.Of(), "test"); - ////ensure these are set so that the appctx is 'Configured' - //dbCtx.Setup(x => x.CanConnect).Returns(true); - //dbCtx.Setup(x => x.IsDatabaseConfigured).Returns(true); - //var appCtx = ApplicationContext.EnsureContext( - // dbCtx.Object, - // //pass in mocked services - // serviceContext, - // CacheHelper.CreateDisabledCacheHelper(), - // new ProfilingLogger(Mock.Of(), Mock.Of()), - // true); - - var httpContextItems = new Dictionary - { - //add the special owin environment to the httpcontext items, this is how the GetOwinContext works - ["owin.Environment"] = new Dictionary() - }; - - //httpcontext with an auth'd user - var httpContext = Mock.Of( - http => http.User == owinContext.Authentication.User - //ensure the request exists with a cookies collection - && http.Request == Mock.Of(r => r.Cookies == new HttpCookieCollection() - && r.RequestContext == new System.Web.Routing.RequestContext - { - RouteData = new System.Web.Routing.RouteData() - }) - //ensure the request exists with an items collection - && http.Items == httpContextItems); - //chuck it into the props since this is what MS does when hosted and it's needed there - request.Properties["MS_HttpContext"] = httpContext; - - var backofficeIdentity = (ClaimsIdentity) owinContext.Authentication.User.Identity; - - var backofficeSecurity = new Mock(); - - //mock CurrentUser - var groups = new List(); - for (var index = 0; index < backofficeIdentity.GetRoles().Length; index++) - { - var role = backofficeIdentity.GetRoles()[index]; - groups.Add(new ReadOnlyUserGroup(index + 1, role, "icon-user", null, null, role, new string[0], new string[0])); - } - var mockUser = MockedUser.GetUserMock(); - mockUser.Setup(x => x.IsApproved).Returns(true); - mockUser.Setup(x => x.IsLockedOut).Returns(false); - mockUser.Setup(x => x.AllowedSections).Returns(backofficeIdentity.GetAllowedApplications()); - mockUser.Setup(x => x.Groups).Returns(groups); - mockUser.Setup(x => x.Email).Returns("admin@admin.com"); - mockUser.Setup(x => x.Id).Returns((int)backofficeIdentity.GetId()); - mockUser.Setup(x => x.Language).Returns("en"); - mockUser.Setup(x => x.Name).Returns(backofficeIdentity.GetRealName()); - mockUser.Setup(x => x.StartContentIds).Returns(backofficeIdentity.GetStartContentNodes()); - mockUser.Setup(x => x.StartMediaIds).Returns(backofficeIdentity.GetStartMediaNodes()); - mockUser.Setup(x => x.Username).Returns(backofficeIdentity.GetUsername()); - backofficeSecurity.Setup(x => x.CurrentUser) - .Returns(mockUser.Object); - - //mock Validate - backofficeSecurity.Setup(x => x.UserHasSectionAccess(It.IsAny(), It.IsAny())) - .Returns(() => true); - - var publishedSnapshot = new Mock(); - publishedSnapshot.Setup(x => x.Members).Returns(Mock.Of()); - var publishedSnapshotService = new Mock(); - publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedSnapshot.Object); - - var umbracoContextAccessor = Umbraco.Web.Composing.Current.UmbracoContextAccessor; - - var httpContextAccessor = TestHelper.GetHttpContextAccessor(httpContext); - var umbCtx = new UmbracoContext(httpContextAccessor, - publishedSnapshotService.Object, - backofficeSecurity.Object, - globalSettings, - TestHelper.GetHostingEnvironment(), - new TestVariationContextAccessor(), - TestHelper.UriUtility, - new AspNetCookieManager(httpContextAccessor)); - - //replace it - umbracoContextAccessor.UmbracoContext = umbCtx; - - var urlHelper = new Mock(); - urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(UrlInfo.Url("/hello/world/1234")); - - return CreateController(controllerType, request, umbracoContextAccessor); - } - - protected abstract ApiController CreateController(Type controllerType, HttpRequestMessage msg, IUmbracoContextAccessor umbracoContextAccessor); - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TestStartup.cs b/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TestStartup.cs deleted file mode 100644 index 408046ad9a..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TestStartup.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Net.Http; -using System.Web.Http; -using System.Web.Http.Dispatcher; -using System.Web.Http.ExceptionHandling; -using System.Web.Http.Tracing; -using Owin; -using Umbraco.Cms.Core.Web; -using Umbraco.Web; -using Umbraco.Web.Hosting; -using Umbraco.Web.WebApi; - -namespace Umbraco.Tests.TestHelpers.ControllerTesting -{ - /// - /// Startup class for the self-hosted web server works for OWIN and WebAPI - /// - public class TestStartup - { - private readonly Func _controllerFactory; - private readonly Action _initialize; - - public TestStartup(Action initialize, Func controllerFactory) - { - _controllerFactory = controllerFactory; - _initialize = initialize; - } - - public void Configuration(IAppBuilder app) - { - var httpConfig = new HttpConfiguration(); - - // TODO: Enable this if you can't see the errors produced - // var traceWriter = httpConfig.EnableSystemDiagnosticsTracing(); - // traceWriter.IsVerbose = true; - // traceWriter.MinimumLevel = TraceLevel.Debug; - - httpConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; - - // Add in a simple exception tracer so we can see what is causing the 500 Internal Server Error - httpConfig.Services.Add(typeof (IExceptionLogger), new TraceExceptionLogger()); - - httpConfig.Services.Replace(typeof (IAssembliesResolver), new SpecificAssemblyResolver(new[] { typeof (AspNetApplicationShutdownRegistry).Assembly })); - httpConfig.Services.Replace(typeof (IHttpControllerActivator), new TestControllerActivator(_controllerFactory)); - httpConfig.Services.Replace(typeof (IHttpControllerSelector), new NamespaceHttpControllerSelector(httpConfig)); - - //auth everything - app.AuthenticateEverything(); - - _initialize(httpConfig); - - app.UseWebApi(httpConfig); - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TraceExceptionLogger.cs b/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TraceExceptionLogger.cs deleted file mode 100644 index de7bd8fc10..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/ControllerTesting/TraceExceptionLogger.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Diagnostics; -using System.Web.Http.ExceptionHandling; - -namespace Umbraco.Tests.TestHelpers.ControllerTesting -{ - /// - /// Traces any errors for WebApi to the output window - /// - public class TraceExceptionLogger : ExceptionLogger - { - public override void Log(ExceptionLoggerContext context) - { - Trace.TraceError(context.ExceptionContext.Exception.ToString()); - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs b/tests/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs deleted file mode 100644 index 2aba18cfe6..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Tests.Common.Extensions; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedContent - { - public static Content CreateBasicContent(IContentType contentType) - { - var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateSimpleContent(IContentType contentType) - { - var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; - object obj = - new - { - title = "Welcome to our Home page", - bodyText = "This is the welcome message on the first page", - author = "John Doe" - }; - - content.PropertyValues(obj); - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateSimpleContent(IContentType contentType, string name, int parentId = -1, string culture = null, string segment = null) - { - var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; - object obj = - new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" - }; - - content.PropertyValues(obj, culture, segment); - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateSimpleContent(IContentType contentType, string name, IContent parent, string culture = null, string segment = null, bool setPropertyValues = true) - { - var content = new Content(name, parent, contentType, culture) { CreatorId = 0, WriterId = 0 }; - - if (setPropertyValues) - { - object obj = - new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" - }; - - content.PropertyValues(obj, culture, segment); - } - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateTextpageContent(IContentType contentType, string name, int parentId) - { - var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0}; - object obj = - new - { - title = name + " textpage", - bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), - keywords = "text,page,meta", - description = "This is the meta description for a textpage" - }; - - content.PropertyValues(obj); - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateSimpleContentWithSpecialDatabaseTypes(IContentType contentType, string name, int parentId, string decimalValue, string intValue, DateTime datetimeValue) - { - var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; - object obj = new - { - decimalProperty = decimalValue, - intProperty = intValue, - datetimeProperty = datetimeValue - }; - - content.PropertyValues(obj); - content.ResetDirtyProperties(false); - return content; - } - - public static Content CreateAllTypesContent(IContentType contentType, string name, int parentId) - { - var content = new Content("Random Content Name", parentId, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; - - content.SetValue("isTrue", true); - content.SetValue("number", 42); - content.SetValue("bodyText", "Lorem Ipsum Body Text Test"); - content.SetValue("singleLineText", "Single Line Text Test"); - content.SetValue("multilineText", "Multiple lines \n in one box"); - content.SetValue("upload", "/media/1234/koala.jpg"); - content.SetValue("label", "Non-editable label"); - content.SetValue("dateTime", DateTime.Now.AddDays(-20)); - content.SetValue("colorPicker", "black"); - content.SetValue("ddlMultiple", "1234,1235"); - content.SetValue("rbList", "random"); - content.SetValue("date", DateTime.Now.AddDays(-10)); - content.SetValue("ddl", "1234"); - content.SetValue("chklist", "randomc"); - content.SetValue("contentPicker", Udi.Create(Constants.UdiEntityType.Document, new Guid("74ECA1D4-934E-436A-A7C7-36CC16D4095C")).ToString()); - content.SetValue("mediaPicker3", "[{\"key\": \"8f78ce9e-8fe0-4500-a52d-4c4f35566ba9\",\"mediaKey\": \"44CB39C8-01E5-45EB-9CF8-E70AAF2D1691\",\"crops\": [],\"focalPoint\": {\"left\": 0.5,\"top\": 0.5}}]"); - content.SetValue("memberPicker", Udi.Create(Constants.UdiEntityType.Member, new Guid("9A50A448-59C0-4D42-8F93-4F1D55B0F47D")).ToString()); - content.SetValue("multiUrlPicker", "[{\"name\":\"https://test.com\",\"url\":\"https://test.com\"}]"); - content.SetValue("tags", "this,is,tags"); - - return content; - } - - public static IEnumerable CreateTextpageContent(IContentType contentType, int parentId, int amount) - { - var list = new List(); - - for (int i = 0; i < amount; i++) - { - var name = "Textpage No-" + i; - var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; - object obj = - new - { - title = name + " title", - bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), - keywords = "text,page,meta", - description = "This is the meta description for a textpage" - }; - - content.PropertyValues(obj); - - content.ResetDirtyProperties(false); - - list.Add(content); - } - - return list; - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/tests/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs deleted file mode 100644 index 0f0a57c9eb..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ /dev/null @@ -1,514 +0,0 @@ -using System; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Strings; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedContentTypes - { - public static IShortStringHelper ShortStringHelper { get; } = - new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); - - public static ContentType CreateBasicContentType(string alias = "basePage", string name = "Base Page", IContentType parent = null) - { - var contentType = parent == null ? new ContentType(ShortStringHelper, -1) : new ContentType(ShortStringHelper, parent, alias); - - contentType.Alias = alias; - contentType.Name = name; - contentType.Description = "ContentType used for basic pages"; - contentType.Icon = ".sprTreeDoc3"; - contentType.Thumbnail = "doc2.png"; - contentType.SortOrder = 1; - contentType.CreatorId = 0; - contentType.Trashed = false; - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateTextPageContentType(string alias = "textPage", string name = "Text Page") - { - var contentType = new ContentType(ShortStringHelper, -1) - { - Alias = alias, - Name = name, - Description = "ContentType used for Text pages", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox, LabelOnTop = true }); - contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.RichtextEditor, LabelOnTop = false }); - - var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox }); - metaCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.Textarea }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - contentType.SetDefaultTemplate(new Template(ShortStringHelper, "Textpage", "textpage")); - - return contentType; - } - - public static ContentType CreateMetaContentType() - { - var contentType = new ContentType(ShortStringHelper, -1) - { - Alias = "meta", - Name = "Meta", - Description = "ContentType used for Meta tags", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "metakeywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - metaCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "metadescription", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); - - contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateContentMetaContentType() - { - var contentType = new ContentType(ShortStringHelper, -1) - { - Alias = "contentMeta", - Name = "Content Meta", - Description = "ContentType used for Content Meta", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - - contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Content", SortOrder = 2 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSeoContentType() - { - var contentType = new ContentType(ShortStringHelper, -1) - { - Alias = "seo", - Name = "Seo", - Description = "ContentType used for Seo", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType(ShortStringHelper, "seotest", ValueStorageType.Ntext) { Alias = "seokeywords", Name = "Seo Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - metaCollection.Add(new PropertyType(ShortStringHelper, "seotest", ValueStorageType.Ntext) { Alias = "seodescription", Name = "Seo Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); - - contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Seo", SortOrder = 5 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType() - { - var contentType = new ContentType(ShortStringHelper, -1) - { - Alias = "simple", - Name = "Simple Page", - Description = "ContentType used for simple text pages", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) - { - Name = "Content", - Alias = "content", - SortOrder = 1 - }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType3(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") - { - var contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases, propertyGroupName); - - var propertyType = new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Nvarchar) - { - Alias = RandomAlias("tags", randomizeAliases), - Name = "Tags", - Description = "Tags", - Mandatory = false, - SortOrder = 99, - DataTypeId = Constants.DataTypes.Tags - }; - contentType.AddPropertyType(propertyType); - - return contentType; - } - - - - public static ContentType CreateSimpleContentType(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") - { - var contentType = parent == null ? new ContentType(ShortStringHelper, -1) : new ContentType(ShortStringHelper, parent, alias); - - contentType.Alias = alias; - contentType.Name = name; - contentType.Description = "ContentType used for simple text pages"; - contentType.Icon = ".sprTreeDoc3"; - contentType.Thumbnail = "doc2.png"; - contentType.SortOrder = 1; - contentType.CreatorId = 0; - contentType.Trashed = false; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88, LabelOnTop = true }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("author", randomizeAliases) , Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); - - var pg = new PropertyGroup(contentCollection) - { - Name = propertyGroupName, - SortOrder = 1 - }; - contentType.PropertyGroups.Add(pg); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - contentType.SetDefaultTemplate(new Template(ShortStringHelper, "Textpage", "textpage")); - - return contentType; - } - - public static MediaType CreateSimpleMediaType(string alias, string name, IMediaType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") - { - var contentType = parent == null ? new MediaType(ShortStringHelper, -1) : new MediaType(ShortStringHelper, parent, alias); - - contentType.Alias = alias; - contentType.Name = name; - contentType.Description = "ContentType used for simple text pages"; - contentType.Icon = ".sprTreeDoc3"; - contentType.Thumbnail = "doc2.png"; - contentType.SortOrder = 1; - contentType.CreatorId = 0; - contentType.Trashed = false; - - var contentCollection = new PropertyTypeCollection(false); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("author", randomizeAliases), Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); - - var pg = new PropertyGroup(contentCollection) { Name = propertyGroupName, SortOrder = 1 }; - contentType.PropertyGroups.Add(pg); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType(string alias, string name, bool mandatory) - { - var contentType = new ContentType(ShortStringHelper, -1) - { - Alias = alias, - Name = name, - Description = "ContentType used for simple text pages", - Icon = ".sprTreeDoc3", - Thumbnail = "doc2.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = mandatory, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = mandatory, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = mandatory, SortOrder = 3, DataTypeId = -88 }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection collection) - { - var contentType = new ContentType(ShortStringHelper, -1) - { - Alias = alias, - Name = name, - Description = "ContentType used for simple text pages", - Icon = ".sprTreeDoc3", - Thumbnail = "doc3.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - contentType.PropertyGroups.Add(new PropertyGroup(collection) { Name = "Content", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection collection, string propertyGroupName, IContentType parent = null) - { - var contentType = parent == null ? new ContentType(ShortStringHelper, -1) : new ContentType(ShortStringHelper, parent, alias); - - contentType.Alias = alias; - contentType.Name = name; - contentType.Description = "ContentType used for simple text pages"; - contentType.Icon = ".sprTreeDoc3"; - contentType.Thumbnail = "doc2.png"; - contentType.SortOrder = 1; - contentType.CreatorId = 0; - contentType.Trashed = false; - - contentType.PropertyGroups.Add(new PropertyGroup(collection) { Name = propertyGroupName, SortOrder = 1 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection groupedCollection, PropertyTypeCollection nonGroupedCollection) - { - var contentType = CreateSimpleContentType(alias, name, groupedCollection); - //now add the non-grouped properties - foreach (var x in nonGroupedCollection) - contentType.AddPropertyType(x); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateAllTypesContentType(string alias, string name) - { - var contentType = new ContentType(ShortStringHelper, -1) - { - Alias = alias, - Name = name, - Description = "ContentType containing all standard DataTypes", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Boolean, ValueStorageType.Integer) { Alias = "isTrue", Name = "Is True or False", Mandatory = false, SortOrder = 1, DataTypeId = -49 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Integer, ValueStorageType.Integer) { Alias = "number", Name = "Number", Mandatory = false, SortOrder = 2, DataTypeId = -51 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Mandatory = false, SortOrder = 3, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar) { Alias = "singleLineText", Name = "Text String", Mandatory = false, SortOrder = 4, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext) { Alias = "multilineText", Name = "Multiple Text Strings", Mandatory = false, SortOrder = 5, DataTypeId = -89 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = "upload", Name = "Upload Field", Mandatory = false, SortOrder = 6, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = "label", Name = "Label", Mandatory = false, SortOrder = 7, DataTypeId = -92 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.DateTime, ValueStorageType.Date) { Alias = "dateTime", Name = "Date Time", Mandatory = false, SortOrder = 8, DataTypeId = -36 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.ColorPicker, ValueStorageType.Nvarchar) { Alias = "colorPicker", Name = "Color Picker", Mandatory = false, SortOrder = 9, DataTypeId = -37 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.DropDownListFlexible, ValueStorageType.Nvarchar) { Alias = "ddlMultiple", Name = "Dropdown List Multiple", Mandatory = false, SortOrder = 11, DataTypeId = -39 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.RadioButtonList, ValueStorageType.Nvarchar) { Alias = "rbList", Name = "Radio Button List", Mandatory = false, SortOrder = 12, DataTypeId = -40 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.DateTime, ValueStorageType.Date) { Alias = "date", Name = "Date", Mandatory = false, SortOrder = 13, DataTypeId = -36 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.DropDownListFlexible, ValueStorageType.Integer) { Alias = "ddl", Name = "Dropdown List", Mandatory = false, SortOrder = 14, DataTypeId = -42 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.CheckBoxList, ValueStorageType.Nvarchar) { Alias = "chklist", Name = "Checkbox List", Mandatory = false, SortOrder = 15, DataTypeId = -43 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.ContentPicker, ValueStorageType.Integer) { Alias = "contentPicker", Name = "Content Picker", Mandatory = false, SortOrder = 16, DataTypeId = 1046 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.MediaPicker3, ValueStorageType.Integer) { Alias = "mediapicker3", Name = "Media Picker", Mandatory = false, SortOrder = 17, DataTypeId = 1051 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.MemberPicker, ValueStorageType.Integer) { Alias = "memberPicker", Name = "Member Picker", Mandatory = false, SortOrder = 18, DataTypeId = 1047 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.MultiUrlPicker, ValueStorageType.Nvarchar) { Alias = "multiUrlPicker", Name = "Multi URL Picker", Mandatory = false, SortOrder = 21, DataTypeId = 1050 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Ntext) { Alias = "tags", Name = "Tags", Mandatory = false, SortOrder = 22, DataTypeId = 1041 }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - - return contentType; - } - - public static MediaType CreateNewMediaType() - { - var mediaType = new MediaType(ShortStringHelper, -1) - { - Alias = "newMediaType", - Name = "New Media Type", - Description = "ContentType used for a new format", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(false); - contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Nvarchar) { Alias = "videoFile", Name = "Video File", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - - mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - mediaType.ResetDirtyProperties(false); - - return mediaType; - } - - public static MediaType CreateImageMediaType(string alias = Constants.Conventions.MediaTypes.Image) - { - var mediaType = new MediaType(ShortStringHelper, -1) - { - Alias = alias, - Name = "Image", - Description = "ContentType used for images", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(false); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); - - mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - mediaType.ResetDirtyProperties(false); - - return mediaType; - } - - public static MediaType CreateImageMediaTypeWithCrop(string alias = Constants.Conventions.MediaTypes.Image) - { - var mediaType = new MediaType(ShortStringHelper, -1) - { - Alias = alias, - Name = "Image", - Description = "ContentType used for images", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(false); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.ImageCropper, ValueStorageType.Ntext) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = 1043 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); - - mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - mediaType.ResetDirtyProperties(false); - - return mediaType; - } - - public static MemberType CreateSimpleMemberType(string alias = null, string name = null) - { - var contentType = new MemberType(ShortStringHelper, -1) - { - Alias = alias ?? "simple", - Name = name ?? "Simple Page", - Description = "Some member type", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(false); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static void EnsureAllIds(ContentTypeCompositionBase contentType, int seedId) - { - //ensure everything has ids - contentType.Id = seedId; - var itemid = seedId + 1; - foreach (var propertyGroup in contentType.PropertyGroups) - { - propertyGroup.Id = itemid++; - } - foreach (var propertyType in contentType.PropertyTypes) - { - propertyType.Id = itemid++; - } - } - - - private static string RandomAlias(string alias, bool randomizeAliases) - { - if (randomizeAliases) - { - return string.Concat(alias, Guid.NewGuid().ToString("N")); - } - - return alias; - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Entities/MockedEntity.cs b/tests/Umbraco.Tests/TestHelpers/Entities/MockedEntity.cs deleted file mode 100644 index c40d5b33bf..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Entities/MockedEntity.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Cms.Core.Models.Entities; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - [Serializable] - [DataContract(IsReference = true)] - public class MockedEntity : EntityBase - { - [DataMember] - public string Alias { get; set; } - - [DataMember] - public string Name { get; set; } - - [DataMember] - public string Value { get; set; } - } - - public class CustomMockedEntity : MockedEntity - { - [DataMember] - public string Title { get; set; } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs b/tests/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs deleted file mode 100644 index 2d8bcc6395..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Tests.Common.Extensions; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public static class MockedMedia - { - public static Media CreateSimpleMedia(IMediaType contentType, string name, int parentId) - { - var content = new Media(name, parentId, contentType) { CreatorId = 0 }; - object obj = - new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" - }; - - content.PropertyValues(obj); - - content.ResetDirtyProperties(false); - - return content; - } - - public static Media CreateMediaImage(IMediaType mediaType, int parentId) - { - var media = new Media("Test Image", parentId, mediaType) - { - CreatorId = 0 - }; - - media.SetValue(Constants.Conventions.Media.File, "/media/test-image.png"); - media.SetValue(Constants.Conventions.Media.Width, "200"); - media.SetValue(Constants.Conventions.Media.Height, "200"); - media.SetValue(Constants.Conventions.Media.Bytes, "100"); - media.SetValue(Constants.Conventions.Media.Extension, "png"); - - return media; - } - - public static Media CreateMediaFile(IMediaType mediaType, int parentId) - { - var media = new Media("Test File", parentId, mediaType) - { - CreatorId = 0 - }; - - media.SetValue(Constants.Conventions.Media.File, "/media/test-file.txt"); - media.SetValue(Constants.Conventions.Media.Bytes, "4"); - media.SetValue(Constants.Conventions.Media.Extension, "txt"); - - return media; - } - - public static Media CreateMediaImageWithCrop(IMediaType mediaType, int parentId) - { - var media = new Media("Test Image", parentId, mediaType) - { - CreatorId = 0 - }; - - media.SetValue(Constants.Conventions.Media.File, "{src: '/media/test-image.png', crops: []}"); - media.SetValue(Constants.Conventions.Media.Width, "200"); - media.SetValue(Constants.Conventions.Media.Height, "200"); - media.SetValue(Constants.Conventions.Media.Bytes, "100"); - media.SetValue(Constants.Conventions.Media.Extension, "png"); - - - - return media; - } - - public static Media CreateMediaFolder(IMediaType mediaType, int parentId) - { - var media = new Media("Test Folder", parentId, mediaType) - { - CreatorId = 0 - }; - - return media; - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs b/tests/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs deleted file mode 100644 index bc9dcf1750..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedMember - { - public static Member CreateSimpleMember(IMemberType contentType, string name, string email, string password, string username, Guid? key = null) - { - var member = new Member(name, email, username, password, contentType) - { - CreatorId = 0, - Email = email, - RawPasswordValue = password, - Username = username - }; - - if (key.HasValue) - { - member.Key = key.Value; - } - - member.SetValue("title", name + " member"); - member.SetValue("bodyText", "This is a subpage"); - member.SetValue("author", "John Doe"); - - member.ResetDirtyProperties(false); - - return member; - } - - public static Member CreateSimpleMember(IMemberType contentType, string name, string email, string username, Guid? key = null) - { - var member = new Member(name, email, username, contentType) - { - CreatorId = 0, - Email = email, - Username = username - }; - - if (key.HasValue) - { - member.Key = key.Value; - } - - member.SetValue("title", name + " member"); - member.SetValue("bodyText", "This is a subpage"); - member.SetValue("author", "John Doe"); - - member.ResetDirtyProperties(false); - - return member; - } - - public static IEnumerable CreateSimpleMember(IMemberType memberType, int amount, Action onCreating = null) - { - var list = new List(); - - for (int i = 0; i < amount; i++) - { - var name = "Member No-" + i; - var member = new Member(name, "test" + i + "@test.com", "test" + i, "test" + i, memberType); - member.SetValue("title", name + " member" + i); - member.SetValue("bodyText", "This is a subpage" + i); - member.SetValue("author", "John Doe" + i); - - if (onCreating != null) - { - onCreating(i, member); - } - - member.ResetDirtyProperties(false); - - list.Add(member); - } - - return list; - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Entities/MockedPropertyTypes.cs b/tests/Umbraco.Tests/TestHelpers/Entities/MockedPropertyTypes.cs deleted file mode 100644 index a2d137800e..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Entities/MockedPropertyTypes.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedPropertyTypes - { - /// - /// Returns a decimal property. - /// Requires a datatype definition Id, since this is not one of the pre-populated datatypes - /// - /// Alias of the created property type - /// Name of the created property type - /// Integer Id of a decimal datatype to use - /// Property type storing decimal value - public static PropertyType CreateDecimalProperty(string alias, string name, int dtdId) - { - return - new PropertyType(TestHelper.ShortStringHelper, "test", ValueStorageType.Decimal, alias) - { - Name = name, - Description = "Decimal property type", - Mandatory = false, - SortOrder = 4, - DataTypeId = dtdId - }; - } - - /// - /// Returns a integer property. - /// - /// Alias of the created property type - /// Name of the created property type - /// Property type storing integer value - public static PropertyType CreateIntegerProperty(string alias, string name) - { - return - new PropertyType(TestHelper.ShortStringHelper, "test", ValueStorageType.Integer, alias) - { - Name = name, - Description = "Integer property type", - Mandatory = false, - SortOrder = 4, - DataTypeId = -51 - }; - } - - /// - /// Returns a DateTime property. - /// - /// Alias of the created property type - /// Name of the created property type - /// Property type storing DateTime value - public static PropertyType CreateDateTimeProperty(string alias, string name) - { - return - new PropertyType(TestHelper.ShortStringHelper, "test", ValueStorageType.Date, alias) - { - Name = name, - Description = "DateTime property type", - Mandatory = false, - SortOrder = 4, - DataTypeId = -36 - }; - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs b/tests/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs deleted file mode 100644 index f2fbaca571..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using Moq; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedUser - { - /// - /// Returns a and ensures that the ToUserCache and FromUserCache methods are mapped correctly for - /// dealing with start node caches - /// - /// - internal static Mock GetUserMock() - { - var userCache = new Dictionary(); - var userMock = new Mock(); - userMock.Setup(x => x.FromUserCache(It.IsAny())).Returns((string key) => userCache.TryGetValue(key, out var val) ? val is int[] iVal ? iVal : null : null); - userMock.Setup(x => x.ToUserCache(It.IsAny(), It.IsAny())).Callback((string key, int[] val) => userCache[key] = val); - return userMock; - } - - internal static User CreateUser(string suffix = "") - { - var globalSettings = new GlobalSettings(); - var user = new User(globalSettings) - { - Language = "en", - IsApproved = true, - Name = "TestUser" + suffix, - RawPasswordValue = "testing", - IsLockedOut = false, - Email = "test" + suffix + "@test.com", - Username = "TestUser" + suffix - }; - - return user; - } - - internal static IEnumerable CreateMulipleUsers(int amount, Action onCreating = null) - { - var list = new List(); - - var globalSettings = new GlobalSettings(); - for (int i = 0; i < amount; i++) - { - var name = "Member No-" + i; - var user = new User(globalSettings, name, "test" + i + "@test.com", "test" + i, "test" + i); - - onCreating?.Invoke(i, user); - - user.ResetDirtyProperties(false); - - list.Add(user); - } - - return list; - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs b/tests/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs deleted file mode 100644 index 37a510a0e8..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Entities/MockedUserGroup.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Strings; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedUserGroup - { - public static IShortStringHelper ShortStringHelper { get; } = - new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); - - public static UserGroup CreateUserGroup(string suffix = "", string[] permissions = null, string[] allowedSections = null) - { - var group = new UserGroup(ShortStringHelper) - { - Alias = "testUserGroup" + suffix, - Name = "TestUserGroup" + suffix, - Permissions = permissions ?? new[] { "A", "B", "C" } - }; - - if (allowedSections == null) - { - group.AddAllowedSection("content"); - group.AddAllowedSection("media"); - } - else - { - foreach (var allowedSection in allowedSections) - { - group.AddAllowedSection(allowedSection); - } - } - - return group; - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs b/tests/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs deleted file mode 100644 index bcbbdab598..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Specialized; -using System.Security; -using System.Security.Principal; -using System.Web; -using System.Web.Routing; -using Moq; - -namespace Umbraco.Tests.TestHelpers -{ - /// - /// Creates a mock http context with supporting other contexts to test against - /// - public class FakeHttpContextFactory - { - - [SecuritySafeCritical] - public FakeHttpContextFactory(Uri fullUrl) - { - CreateContext(fullUrl); - } - - [SecuritySafeCritical] - public FakeHttpContextFactory(string path) - { - if (path.StartsWith("http://") || path.StartsWith("https://")) - CreateContext(new Uri(path)); - else - CreateContext(new Uri("http://mysite" + VirtualPathUtility.ToAbsolute(path, "/"))); - } - - [SecuritySafeCritical] - public FakeHttpContextFactory(string path, RouteData routeData) - { - if (path.StartsWith("http://") || path.StartsWith("https://")) - CreateContext(new Uri(path), routeData); - else - CreateContext(new Uri("http://mysite" + VirtualPathUtility.ToAbsolute(path, "/")), routeData); - } - - public HttpContextBase HttpContext { get; private set; } - public RequestContext RequestContext { get; private set; } - - /// - /// Mocks the http context to test against - /// - /// - /// - /// - private void CreateContext(Uri fullUrl, RouteData routeData = null) - { - //Request context - - var requestContextMock = new Mock(); - - RequestContext = requestContextMock.Object; - - //Cookie collection - var cookieCollection = new HttpCookieCollection(); - cookieCollection.Add(new HttpCookie("UMB_UCONTEXT", "FBA996E7-D6BE-489B-B199-2B0F3D2DD826")); - - //Request - var requestMock = new Mock(); - requestMock.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns("~" + fullUrl.AbsolutePath); - requestMock.Setup(x => x.PathInfo).Returns(string.Empty); - requestMock.Setup(x => x.RawUrl).Returns(VirtualPathUtility.ToAbsolute("~" + fullUrl.AbsolutePath, "/")); - requestMock.Setup(x => x.RequestContext).Returns(RequestContext); - requestMock.Setup(x => x.Url).Returns(fullUrl); - requestMock.Setup(x => x.ApplicationPath).Returns("/"); - requestMock.Setup(x => x.Cookies).Returns(cookieCollection); - requestMock.Setup(x => x.ServerVariables).Returns(new NameValueCollection()); - var queryStrings = HttpUtility.ParseQueryString(fullUrl.Query); - requestMock.Setup(x => x.QueryString).Returns(queryStrings); - requestMock.Setup(x => x.Form).Returns(new NameValueCollection()); - - //Cache - var cacheMock = new Mock(); - - //Response - //var response = new FakeHttpResponse(); - var responseMock = new Mock(); - responseMock.Setup(x => x.ApplyAppPathModifier(It.IsAny())).Returns((string s) => s); - responseMock.Setup(x => x.Cache).Returns(cacheMock.Object); - - //Server - - var serverMock = new Mock(); - serverMock.Setup(x => x.MapPath(It.IsAny())).Returns(Environment.CurrentDirectory); - - //User - var user = new Mock().Object; - - //HTTP Context - - var httpContextMock = new Mock(); - httpContextMock.Setup(x => x.Cache).Returns(HttpRuntime.Cache); - //note: foreach on Items should return DictionaryEntries! - //httpContextMock.Setup(x => x.Items).Returns(new Dictionary()); - httpContextMock.Setup(x => x.Items).Returns(new Hashtable()); - httpContextMock.Setup(x => x.Request).Returns(requestMock.Object); - httpContextMock.Setup(x => x.Server).Returns(serverMock.Object); - httpContextMock.Setup(x => x.Response).Returns(responseMock.Object); - httpContextMock.Setup(x => x.User).Returns(user); - - HttpContext = httpContextMock.Object; - - requestContextMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object); - - - if (routeData is null) - { - routeData = new RouteData(); - } - - requestContextMock.Setup(x => x.RouteData).Returns(routeData); - } - - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/tests/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs deleted file mode 100644 index cf88208286..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Web.Mvc; -using System.Web.Routing; -using System.Web.SessionState; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Web; -using Umbraco.Extensions; -using Current = Umbraco.Web.Composing.Current; - -namespace Umbraco.Tests.TestHelpers.Stubs -{ - /// - /// Used in place of the UmbracoControllerFactory which relies on BuildManager which throws exceptions in a unit test context - /// - internal class TestControllerFactory : IControllerFactory - { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ILogger _logger; - private readonly Func _factory; - - public TestControllerFactory(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) - { - _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; - } - - public TestControllerFactory(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, Func factory) - { - _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; - _factory = factory; - } - - public IController CreateController(RequestContext requestContext, string controllerName) - { - if (_factory != null) return _factory(requestContext); - - var typeFinder = TestHelper.GetTypeFinder(); - var types = typeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); - - var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); - var t = controllerTypes.SingleOrDefault(); - - if (t == null) - return null; - - var possibleParams = new object[] - { - _umbracoContextAccessor, _logger - }; - var ctors = t.GetConstructors(); - foreach (var ctor in ctors.OrderByDescending(x => x.GetParameters().Length)) - { - var args = new List(); - var allParams = ctor.GetParameters().ToArray(); - foreach (var parameter in allParams) - { - var found = possibleParams.SingleOrDefault(x => x.GetType() == parameter.ParameterType) - ?? Current.Factory.GetRequiredService(parameter.ParameterType); - if (found != null) args.Add(found); - } - if (args.Count == allParams.Length) - { - return Activator.CreateInstance(t, args.ToArray()) as IController; - } - } - return null; - } - - public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) - { - return SessionStateBehavior.Disabled; - } - - public void ReleaseController(IController controller) - { - controller.DisposeIfDisposable(); - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Stubs/TestExamineManager.cs b/tests/Umbraco.Tests/TestHelpers/Stubs/TestExamineManager.cs deleted file mode 100644 index 7bb3b90d81..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Stubs/TestExamineManager.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using Examine; - -namespace Umbraco.Tests.TestHelpers.Stubs -{ - internal class TestExamineManager : IExamineManager - { - private readonly ConcurrentDictionary _indexers = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _searchers = new ConcurrentDictionary(); - - public IIndex AddIndex(IIndex indexer) - { - _indexers.TryAdd(indexer.Name, indexer); - return indexer; - } - - public ISearcher AddSearcher(ISearcher searcher) - { - _searchers.TryAdd(searcher.Name, searcher); - return searcher; - } - - public void Dispose() - { - //noop - } - - public bool TryGetIndex(string indexName, out IIndex index) - { - return _indexers.TryGetValue(indexName, out index); - } - - public bool TryGetSearcher(string searcherName, out ISearcher searcher) - { - return _searchers.TryGetValue(searcherName, out searcher); - } - - public IEnumerable Indexes => _indexers.Values; - - public IEnumerable RegisteredSearchers => _searchers.Values; - - public IReadOnlyDictionary IndexProviders => _indexers; - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs b/tests/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs deleted file mode 100644 index 89798b8771..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Umbraco.Cms.Core.Routing; - -namespace Umbraco.Tests.TestHelpers.Stubs -{ - internal class TestLastChanceFinder : IContentLastChanceFinder - { - public bool TryFindContent(IPublishedRequestBuilder frequest) => false; - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/Stubs/TestUserPasswordConfig.cs b/tests/Umbraco.Tests/TestHelpers/Stubs/TestUserPasswordConfig.cs deleted file mode 100644 index 065305c63a..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/Stubs/TestUserPasswordConfig.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Umbraco.Cms.Core.Configuration; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.TestHelpers.Stubs -{ - internal class TestUserPasswordConfig : IUserPasswordConfiguration - { - public int RequiredLength => 12; - - public bool RequireNonLetterOrDigit => false; - - public bool RequireDigit => false; - - public bool RequireLowercase => false; - - public bool RequireUppercase => false; - - public bool UseLegacyEncoding => false; - - public string HashAlgorithmType => Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName; - - public int MaxFailedAccessAttemptsBeforeLockout => 5; - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/TestHelper.cs b/tests/Umbraco.Tests/TestHelpers/TestHelper.cs deleted file mode 100644 index b8960c6b64..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/TestHelper.cs +++ /dev/null @@ -1,346 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Web; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Diagnostics; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Logging; -using Umbraco.Cms.Core.Mail; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Entities; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Net; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Runtime; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure; -using Umbraco.Cms.Infrastructure.Migrations.Install; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Persistence.SqlCe; -using Umbraco.Cms.Tests.Common; -using Umbraco.Extensions; -using Umbraco.Web; -using Umbraco.Web.Hosting; -using Constants = Umbraco.Cms.Core.Constants; -using File = System.IO.File; - -namespace Umbraco.Tests.TestHelpers -{ - /// - /// Common helper properties and methods useful to testing - /// - public static class TestHelper - { - private static readonly TestHelperInternal _testHelperInternal = new TestHelperInternal(); - private static IEmailSender _emailSender; - - private class TestHelperInternal : TestHelperBase - { - public TestHelperInternal() : base(typeof(TestHelperInternal).Assembly) - { - - } - - public override IDbProviderFactoryCreator DbProviderFactoryCreator { get; } = new UmbracoDbProviderFactoryCreator(); - public DatabaseSchemaCreatorFactory DatabaseSchemaCreatorFactory { get; } = new DatabaseSchemaCreatorFactory(Mock.Of>(), NullLoggerFactory.Instance, new UmbracoVersion(), Mock.Of()); - - public override IBulkSqlInsertProvider BulkSqlInsertProvider { get; } = new SqlCeBulkSqlInsertProvider(); - - public override IMarchal Marchal { get; } = new FrameworkMarchal(); - - public override IHostingEnvironment GetHostingEnvironment() - => new AspNetHostingEnvironment(Options.Create(new HostingSettings())); - - public override IApplicationShutdownRegistry GetHostingEnvironmentLifetime() - => new AspNetApplicationShutdownRegistry(); - - public override IIpResolver GetIpResolver() - => new AspNetIpResolver(); - } - - public static ITypeFinder GetTypeFinder() => _testHelperInternal.GetTypeFinder(); - - public static TypeLoader GetMockedTypeLoader() => _testHelperInternal.GetMockedTypeLoader(); - - /// - /// Gets the working directory of the test project. - /// - /// The assembly directory. - public static string WorkingDirectory => _testHelperInternal.WorkingDirectory; - - public static IShortStringHelper ShortStringHelper => _testHelperInternal.ShortStringHelper; - public static IJsonSerializer JsonSerializer => _testHelperInternal.JsonSerializer; - public static IVariationContextAccessor VariationContextAccessor => _testHelperInternal.VariationContextAccessor; - public static IDbProviderFactoryCreator DbProviderFactoryCreator => _testHelperInternal.DbProviderFactoryCreator; - public static DatabaseSchemaCreatorFactory DatabaseSchemaCreatorFactory => _testHelperInternal.DatabaseSchemaCreatorFactory; - public static IBulkSqlInsertProvider BulkSqlInsertProvider => _testHelperInternal.BulkSqlInsertProvider; - public static IMarchal Marchal => _testHelperInternal.Marchal; - public static CoreDebugSettings CoreDebugSettings => _testHelperInternal.CoreDebugSettings; - - - public static IIOHelper IOHelper => _testHelperInternal.IOHelper; - public static IMainDom MainDom => _testHelperInternal.MainDom; - public static UriUtility UriUtility => _testHelperInternal.UriUtility; - - public static IEmailSender EmailSender { get; } = new EmailSender(Options.Create(new GlobalSettings())); - - - /// - /// Some test files are copied to the /bin (/bin/debug) on build, this is a utility to return their physical path based on a virtual path name - /// - /// - /// - public static string MapPathForTestFiles(string relativePath) => _testHelperInternal.MapPathForTestFiles(relativePath); - - public static void InitializeContentDirectories() - { - CreateDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath, Constants.SystemDirectories.AppPlugins }); - } - - public static void CleanContentDirectories() - { - CleanDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath }); - } - - public static void CreateDirectories(string[] directories) - { - foreach (var directory in directories) - { - var directoryInfo = new DirectoryInfo(IOHelper.MapPath(directory)); - if (directoryInfo.Exists == false) - Directory.CreateDirectory(IOHelper.MapPath(directory)); - } - } - - public static void CleanDirectories(string[] directories) - { - var preserves = new Dictionary - { - { Constants.SystemDirectories.MvcViews, new[] {"dummy.txt"} } - }; - foreach (var directory in directories) - { - var directoryInfo = new DirectoryInfo(IOHelper.MapPath(directory)); - var preserve = preserves.ContainsKey(directory) ? preserves[directory] : null; - if (directoryInfo.Exists) - foreach (var x in directoryInfo.GetFiles().Where(x => preserve == null || preserve.Contains(x.Name) == false)) - x.Delete(); - } - } - - public static void CleanUmbracoSettingsConfig() - { - var currDir = new DirectoryInfo(WorkingDirectory); - - var umbracoSettingsFile = Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"); - if (File.Exists(umbracoSettingsFile)) - File.Delete(umbracoSettingsFile); - } - - // TODO: Move to Assertions or AssertHelper - // FIXME: obsolete the dateTimeFormat thing and replace with dateDelta - public static void AssertPropertyValuesAreEqual(object actual, object expected, string dateTimeFormat = null, Func sorter = null, string[] ignoreProperties = null) - { - const int dateDeltaMilliseconds = 500; // .5s - - var properties = expected.GetType().GetProperties(); - foreach (var property in properties) - { - // ignore properties that are attributed with EditorBrowsableState.Never - var att = property.GetCustomAttribute(false); - if (att != null && att.State == EditorBrowsableState.Never) - continue; - - // ignore explicitely ignored properties - if (ignoreProperties != null && ignoreProperties.Contains(property.Name)) - continue; - - var actualValue = property.GetValue(actual, null); - var expectedValue = property.GetValue(expected, null); - - AssertAreEqual(property, expectedValue, actualValue, sorter, dateDeltaMilliseconds); - } - } - - private static void AssertAreEqual(PropertyInfo property, object expected, object actual, Func sorter = null, int dateDeltaMilliseconds = 0) - { - if (!(expected is string) && expected is IEnumerable) - { - // sort property collection by alias, not by property ids - // on members, built-in properties don't have ids (always zero) - if (expected is PropertyCollection) - sorter = e => ((PropertyCollection) e).OrderBy(x => x.Alias); - - // compare lists - AssertListsAreEqual(property, (IEnumerable) actual, (IEnumerable) expected, sorter, dateDeltaMilliseconds); - } - else if (expected is DateTime expectedDateTime) - { - // compare date & time with delta - var actualDateTime = (DateTime) actual; - var delta = (actualDateTime - expectedDateTime).TotalMilliseconds; - Assert.IsTrue(Math.Abs(delta) <= dateDeltaMilliseconds, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expected, actual); - } - else if (expected is Property expectedProperty) - { - // compare values - var actualProperty = (Property) actual; - var expectedPropertyValues = expectedProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); - var actualPropertyValues = actualProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); - if (expectedPropertyValues.Length != actualPropertyValues.Length) - Assert.Fail($"{property.DeclaringType.Name}.{property.Name}: Expected {expectedPropertyValues.Length} but got {actualPropertyValues.Length}."); - for (var i = 0; i < expectedPropertyValues.Length; i++) - { - Assert.AreEqual(expectedPropertyValues[i].EditedValue, actualPropertyValues[i].EditedValue, $"{property.DeclaringType.Name}.{property.Name}: Expected draft value \"{expectedPropertyValues[i].EditedValue}\" but got \"{actualPropertyValues[i].EditedValue}\"."); - Assert.AreEqual(expectedPropertyValues[i].PublishedValue, actualPropertyValues[i].PublishedValue, $"{property.DeclaringType.Name}.{property.Name}: Expected published value \"{expectedPropertyValues[i].EditedValue}\" but got \"{actualPropertyValues[i].EditedValue}\"."); - } - } - else if (expected is IDataEditor expectedEditor) - { - Assert.IsInstanceOf(actual); - var actualEditor = (IDataEditor) actual; - Assert.AreEqual(expectedEditor.Alias, actualEditor.Alias); - // what else shall we test? - } - else - { - // directly compare values - Assert.AreEqual(expected, actual, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, - expected?.ToString() ?? "", actual?.ToString() ?? ""); - } - } - - private static void AssertListsAreEqual(PropertyInfo property, IEnumerable expected, IEnumerable actual, Func sorter = null, int dateDeltaMilliseconds = 0) - { - - - if (sorter == null) - { - // this is pretty hackerific but saves us some code to write - sorter = enumerable => - { - // semi-generic way of ensuring any collection of IEntity are sorted by Ids for comparison - var entities = enumerable.OfType().ToList(); - return entities.Count > 0 ? (IEnumerable) entities.OrderBy(x => x.Id) : entities; - }; - } - - var expectedListEx = sorter(expected).Cast().ToList(); - var actualListEx = sorter(actual).Cast().ToList(); - - if (actualListEx.Count != expectedListEx.Count) - Assert.Fail("Collection {0}.{1} does not match. Expected IEnumerable containing {2} elements but was IEnumerable containing {3} elements", property.PropertyType.Name, property.Name, expectedListEx.Count, actualListEx.Count); - - for (var i = 0; i < actualListEx.Count; i++) - AssertAreEqual(property, expectedListEx[i], actualListEx[i], sorter, dateDeltaMilliseconds); - } - - public static void DeleteDirectory(string path) - { - Try(() => - { - if (Directory.Exists(path) == false) return; - foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) - File.Delete(file); - }); - - Try(() => - { - if (Directory.Exists(path) == false) return; - Directory.Delete(path, true); - }); - } - - public static void TryAssert(Action action, int maxTries = 5, int waitMilliseconds = 200) - { - Try(action, maxTries, waitMilliseconds); - } - - public static void Try(Action action, int maxTries = 5, int waitMilliseconds = 200) - { - Try(action, maxTries, waitMilliseconds); - } - - public static void Try(Action action, int maxTries = 5, int waitMilliseconds = 200) - where T : Exception - { - var tries = 0; - while (true) - { - try - { - action(); - break; - } - catch (T) - { - if (tries++ > maxTries) - throw; - Thread.Sleep(waitMilliseconds); - } - } - } - - public static IUmbracoVersion GetUmbracoVersion() => _testHelperInternal.GetUmbracoVersion(); - - public static IServiceCollection GetRegister() => _testHelperInternal.GetRegister().AddLazySupport(); - - public static IHostingEnvironment GetHostingEnvironment() => _testHelperInternal.GetHostingEnvironment(); - - public static ILoggingConfiguration GetLoggingConfiguration(IHostingEnvironment hostingEnv) => _testHelperInternal.GetLoggingConfiguration(hostingEnv); - - public static IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => _testHelperInternal.GetHostingEnvironmentLifetime(); - - public static IIpResolver GetIpResolver() => _testHelperInternal.GetIpResolver(); - - public static IRequestCache GetRequestCache() => _testHelperInternal.GetRequestCache(); - - public static IHttpContextAccessor GetHttpContextAccessor(HttpContextBase httpContextBase = null) - { - if (httpContextBase is null) - { - var httpContextMock = new Mock(); - - httpContextMock.Setup(x => x.DisposeOnPipelineCompleted(It.IsAny())) - .Returns(Mock.Of()); - - httpContextBase = httpContextMock.Object; - } - - var mock = new Mock(); - - mock.Setup(x => x.HttpContext).Returns(httpContextBase); - - return mock.Object; - } - - public static IPublishedUrlProvider GetPublishedUrlProvider() => _testHelperInternal.GetPublishedUrlProvider(); - - public static IServiceProvider CreateServiceProvider(IUmbracoBuilder builder) - { - builder.Build(); - return builder.Services.BuildServiceProvider(); - } - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/tests/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs deleted file mode 100644 index 1f32e73916..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ /dev/null @@ -1,327 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Linq.Expressions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Persistence.SqlCe; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.TestHelpers; -using Umbraco.Extensions; -using Umbraco.Web; - -namespace Umbraco.Tests.TestHelpers -{ - /// - /// Provides objects for tests. - /// - internal partial class TestObjects - { - /// - /// Gets a mocked IUmbracoDatabaseFactory. - /// - /// An IUmbracoDatabaseFactory. - /// A value indicating whether the factory is configured. - /// A value indicating whether the factory can connect to the database. - /// This is just a void factory that has no actual database. - public IUmbracoDatabaseFactory GetDatabaseFactoryMock(bool configured = true, bool canConnect = true) - { - var sqlSyntax = new SqlCeSyntaxProvider(Options.Create(new GlobalSettings())); - var sqlContext = Mock.Of(); - Mock.Get(sqlContext).Setup(x => x.SqlSyntax).Returns(sqlSyntax); - - var databaseFactoryMock = new Mock(); - databaseFactoryMock.Setup(x => x.Configured).Returns(configured); - databaseFactoryMock.Setup(x => x.CanConnect).Returns(canConnect); - databaseFactoryMock.Setup(x => x.SqlContext).Returns(sqlContext); - - // can create a database - but don't try to use it! - if (configured && canConnect) - databaseFactoryMock.Setup(x => x.CreateDatabase()).Returns(GetUmbracoSqlCeDatabase(Mock.Of>())); - - return databaseFactoryMock.Object; - } - - /// - /// Gets a mocked service context built with mocked services. - /// - /// A ServiceContext. - public ServiceContext GetServiceContextMock(IServiceProvider container = null) - { - // FIXME: else some tests break - figure it out - container = null; - - return ServiceContext.CreatePartial( - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container), - MockService(container)); - } - - private T MockService(IServiceProvider container = null) - where T : class - { - return container?.GetService() ?? new Mock().Object; - } - - /// - /// Gets an opened database connection that can begin a transaction. - /// - /// A DbConnection. - /// This is because NPoco wants a DbConnection, NOT an IDbConnection, - /// and DbConnection is hard to mock so we create our own class here. - public DbConnection GetDbConnection() - { - return new MockDbConnection(); - } - - /// - /// Gets an Umbraco context. - /// - /// An Umbraco context. - /// This should be the minimum Umbraco context. - public IUmbracoContext GetUmbracoContextMock(IUmbracoContextAccessor accessor = null) - { - - var publishedSnapshotMock = new Mock(); - publishedSnapshotMock.Setup(x => x.Members).Returns(Mock.Of()); - var publishedSnapshot = publishedSnapshotMock.Object; - var publishedSnapshotServiceMock = new Mock(); - publishedSnapshotServiceMock.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedSnapshot); - var publishedSnapshotService = publishedSnapshotServiceMock.Object; - - var globalSettings = GetGlobalSettings(); - - if (accessor == null) accessor = new TestUmbracoContextAccessor(); - - var httpContextAccessor = TestHelper.GetHttpContextAccessor(); - - var umbracoContextFactory = new UmbracoContextFactory( - accessor, - publishedSnapshotService, - new TestVariationContextAccessor(), - new TestDefaultCultureAccessor(), - globalSettings, - Mock.Of(), - TestHelper.GetHostingEnvironment(), - TestHelper.UriUtility, - httpContextAccessor, - new AspNetCookieManager(httpContextAccessor)); - - return umbracoContextFactory.EnsureUmbracoContext().UmbracoContext; - } - - public GlobalSettings GetGlobalSettings() - { - return new GlobalSettings(); - } - public FileSystems GetFileSystemsMock() - { - var fileSystems = FileSystemsCreator.CreateTestFileSystems( - NullLoggerFactory.Instance, - Mock.Of(), - Mock.Of>(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of() - ); - - return fileSystems; - } - - #region Inner classes - - private class MockDbConnection : DbConnection - { - protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) - { - return Mock.Of(); // enough here - } - - public override void Close() - { - throw new NotImplementedException(); - } - - public override void ChangeDatabase(string databaseName) - { - throw new NotImplementedException(); - } - - public override void Open() - { - throw new NotImplementedException(); - } - - public override string ConnectionString { get; set; } - - protected override DbCommand CreateDbCommand() - { - throw new NotImplementedException(); - } - - public override string Database { get; } - public override string DataSource { get; } - public override string ServerVersion { get; } - public override ConnectionState State => ConnectionState.Open; // else NPoco reopens - } - - public class TestDataTypeService : IDataTypeService - { - public TestDataTypeService() - { - DataTypes = new Dictionary(); - } - - public TestDataTypeService(params IDataType[] dataTypes) - { - DataTypes = dataTypes.ToDictionary(x => x.Id, x => x); - } - - public TestDataTypeService(IEnumerable dataTypes) - { - DataTypes = dataTypes.ToDictionary(x => x.Id, x => x); - } - - public Dictionary DataTypes { get; } - - public Attempt> CreateContainer(int parentId, string name, int userId = -1) - { - throw new NotImplementedException(); - } - - public Attempt SaveContainer(EntityContainer container, int userId = -1) - { - throw new NotImplementedException(); - } - - public EntityContainer GetContainer(int containerId) - { - throw new NotImplementedException(); - } - - public EntityContainer GetContainer(Guid containerId) - { - throw new NotImplementedException(); - } - - public IEnumerable GetContainers(string folderName, int level) - { - throw new NotImplementedException(); - } - - public IEnumerable GetContainers(IDataType dataType) - { - throw new NotImplementedException(); - } - - public IEnumerable GetContainers(int[] containerIds) - { - throw new NotImplementedException(); - } - - public Attempt DeleteContainer(int containerId, int userId = -1) - { - throw new NotImplementedException(); - } - - public Attempt> RenameContainer(int id, string name, int userId = -1) - { - throw new NotImplementedException(); - } - - public IDataType GetDataType(string name) - { - throw new NotImplementedException(); - } - - public IDataType GetDataType(int id) - { - DataTypes.TryGetValue(id, out var dataType); - return dataType; - } - - public IDataType GetDataType(Guid id) - { - throw new NotImplementedException(); - } - - public IEnumerable GetAll(params int[] ids) - { - if (ids.Length == 0) return DataTypes.Values; - return ids.Select(x => DataTypes.TryGetValue(x, out var dataType) ? dataType : null).WhereNotNull(); - } - - public void Save(IDataType dataType, int userId = -1) - { - throw new NotImplementedException(); - } - - public void Save(IEnumerable dataTypeDefinitions, int userId = -1) - { - throw new NotImplementedException(); - } - - public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) - { - throw new NotImplementedException(); - } - - public void Delete(IDataType dataType, int userId = -1) - { - throw new NotImplementedException(); - } - - public IEnumerable GetByEditorAlias(string propertyEditorAlias) - { - throw new NotImplementedException(); - } - - public Attempt> Move(IDataType toMove, int parentId) - { - throw new NotImplementedException(); - } - - public IReadOnlyDictionary> GetReferences(int id) - { - throw new NotImplementedException(); - } - } - - #endregion - - - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/TestObjects.cs b/tests/Umbraco.Tests/TestHelpers/TestObjects.cs deleted file mode 100644 index 6f7fcfe2dd..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/TestObjects.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Moq; -using NPoco; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Infrastructure.Migrations.Install; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.Mappers; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; -using Umbraco.Cms.Persistence.SqlCe; -using Umbraco.Web.Composing; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.TestHelpers -{ - /// - /// Provides objects for tests. - /// - internal partial class TestObjects - { - - public TestObjects() - { - } - - /// - /// Gets an UmbracoDatabase. - /// - /// A logger. - /// An UmbracoDatabase. - /// This is just a void database that has no actual database but pretends to have an open connection - /// that can begin a transaction. - public UmbracoDatabase GetUmbracoSqlCeDatabase(ILogger logger) - { - var syntax = new SqlCeSyntaxProvider(Options.Create(new GlobalSettings())); - var connection = GetDbConnection(); - var sqlContext = new SqlContext(syntax, DatabaseType.SQLCe, Mock.Of()); - return new UmbracoDatabase(connection, sqlContext, logger, TestHelper.BulkSqlInsertProvider); - } - - /// - /// Gets an UmbracoDatabase. - /// - /// A logger. - /// An UmbracoDatabase. - /// This is just a void database that has no actual database but pretends to have an open connection - /// that can begin a transaction. - public UmbracoDatabase GetUmbracoSqlServerDatabase(ILogger logger) - { - var syntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings())); // do NOT try to get the server's version! - var connection = GetDbConnection(); - var sqlContext = new SqlContext(syntax, DatabaseType.SqlServer2008, Mock.Of()); - return new UmbracoDatabase(connection, sqlContext, logger, TestHelper.BulkSqlInsertProvider); - } - - public IScopeProvider GetScopeProvider(ILoggerFactory loggerFactory, FileSystems fileSystems = null, IUmbracoDatabaseFactory databaseFactory = null) - { - var globalSettings = Options.Create(new GlobalSettings()); - var connectionString = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ConnectionString; - var connectionStrings = Options.Create(new ConnectionStrings { UmbracoConnectionString = new ConfigConnectionString(Constants.System.UmbracoConnectionName, connectionString) }); - var coreDebugSettings = new CoreDebugSettings(); - - if (databaseFactory == null) - { - // var mappersBuilder = new MapperCollectionBuilder(Current.Container); // FIXME: - // mappersBuilder.AddCore(); - // var mappers = mappersBuilder.CreateCollection(); - var mappers = Current.Factory.GetRequiredService(); - databaseFactory = new UmbracoDatabaseFactory( - loggerFactory.CreateLogger(), - loggerFactory, - globalSettings, - connectionStrings, - new Lazy(() => mappers), - TestHelper.DbProviderFactoryCreator, - new DatabaseSchemaCreatorFactory(Mock.Of>(),loggerFactory, new UmbracoVersion(), Mock.Of())); - } - - fileSystems ??= new FileSystems(loggerFactory, TestHelper.IOHelper, globalSettings, TestHelper.GetHostingEnvironment()); - var coreDebug = TestHelper.CoreDebugSettings; - var mediaFileManager = Mock.Of(); - var eventAggregator = Mock.Of(); - return new ScopeProvider(databaseFactory, fileSystems, Options.Create(coreDebugSettings), mediaFileManager, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance, eventAggregator); - } - - } -} diff --git a/tests/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/tests/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs deleted file mode 100644 index 9c06510370..0000000000 --- a/tests/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ /dev/null @@ -1,429 +0,0 @@ -using System; -using System.Configuration; -using System.Data.SqlServerCe; -using System.IO; -using System.Threading; -using System.Web.Routing; -using System.Xml; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Logging; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.Migrations.Install; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.Mappers; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; -using Umbraco.Cms.Persistence.SqlCe; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.Testing; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.WebApi; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.TestHelpers -{ - /// - /// Provides a base class for all Umbraco tests that require a database. - /// - /// - /// Can provide a SqlCE database populated with the Umbraco schema. The database should - /// be accessed through the UmbracoDatabaseFactory. - /// Provides an Umbraco context and Xml content. - /// fixme what else? - /// - [UmbracoTest(WithApplication = true)] - public abstract class TestWithDatabaseBase : UmbracoTestBase - { - private string _databasePath; - private static byte[] _databaseBytes; - - protected PublishedContentTypeCache ContentTypesCache { get; private set; } - - protected override ISqlSyntaxProvider SqlSyntax => GetSyntaxProvider(); - protected IVariationContextAccessor VariationContextAccessor => new TestVariationContextAccessor(); - - internal ScopeProvider ScopeProvider => Current.ScopeProvider as ScopeProvider; - internal IUmbracoDatabaseFactory UmbracoDatabaseFactory => Factory.GetRequiredService(); - internal IDataValueEditorFactory DataValueEditorFactory => Factory.GetRequiredService(); - - protected ISqlContext SqlContext => Factory.GetRequiredService(); - - public override void SetUp() - { - // Ensure the data directory is set before continuing - var path = TestHelper.WorkingDirectory; - AppDomain.CurrentDomain.SetData("DataDirectory", path); - - base.SetUp(); - } - - protected override void Compose() - { - base.Compose(); - - Builder.Services.AddTransient(); - Builder.Services.AddTransient(factory => PublishedSnapshotService); - Builder.Services.AddTransient(factory => DefaultCultureAccessor); - - Builder.WithCollectionBuilder() - .Clear() - .Add(() => Builder.TypeLoader.GetDataEditors()); - - Builder.WithCollectionBuilder() - .Add(Builder.TypeLoader.GetUmbracoApiControllers()); - - Builder.Services.AddUnique(f => - { - if (Options.Database == UmbracoTestOptions.Database.None) - return TestObjects.GetDatabaseFactoryMock(); - - var lazyMappers = new Lazy(f.GetRequiredService); - var factory = new UmbracoDatabaseFactory(f.GetRequiredService>(), f.GetRequiredService(), GetDbConnectionString(), GetDbProviderName(), lazyMappers, TestHelper.DbProviderFactoryCreator, f.GetRequiredService()); - return factory; - }); - } - - [OneTimeTearDown] - public void FixtureTearDown() - { - RemoveDatabaseFile(); - } - - public override void TearDown() - { - var profilingLogger = Factory.GetService(); - var timer = profilingLogger?.TraceDuration("teardown"); // FIXME: move that one up - try - { - // FIXME: should we first kill all scopes? - if (Options.Database == UmbracoTestOptions.Database.NewSchemaPerTest) - RemoveDatabaseFile(); - - AppDomain.CurrentDomain.SetData("DataDirectory", null); - - // make sure we dispose of the service to unbind events - PublishedSnapshotService?.Dispose(); - PublishedSnapshotService = null; - } - finally - { - timer?.Dispose(); - } - - base.TearDown(); - } - - private void CreateAndInitializeDatabase() - { - using (ProfilingLogger.TraceDuration("Create database.")) - { - CreateSqlCeDatabase(); // TODO: faster! - } - - using (ProfilingLogger.TraceDuration("Initialize database.")) - { - InitializeDatabase(); // TODO: faster! - } - } - - protected virtual ISqlSyntaxProvider GetSyntaxProvider() - { - return new SqlCeSyntaxProvider(Microsoft.Extensions.Options.Options.Create(new GlobalSettings())); - } - - protected virtual string GetDbProviderName() - { - return Constants.DbProviderNames.SqlCe; - } - - protected virtual string GetDbConnectionString() - { - return @"DataSource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1;"; - } - - - - /// - /// Creates the SqlCe database if required - /// - protected virtual void CreateSqlCeDatabase() - { - if (Options.Database == UmbracoTestOptions.Database.None) - return; - - var path = TestHelper.WorkingDirectory; - - //Get the connectionstring settings from config - var settings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; - ConfigurationManager.AppSettings.Set( - Constants.System.UmbracoConnectionName, - GetDbConnectionString()); - - _databasePath = string.Concat(path, "\\UmbracoNPocoTests.sdf"); - - //create a new database file if - // - is the first test in the session - // - the database file doesn't exist - // - NewDbFileAndSchemaPerTest - // - _isFirstTestInFixture + DbInitBehavior.NewDbFileAndSchemaPerFixture - - //if this is the first test in the session, always ensure a new db file is created - if (FirstTestInSession - || File.Exists(_databasePath) == false - || Options.Database == UmbracoTestOptions.Database.NewSchemaPerTest - || Options.Database == UmbracoTestOptions.Database.NewEmptyPerTest - || (FirstTestInFixture && Options.Database == UmbracoTestOptions.Database.NewSchemaPerFixture)) - { - using (ProfilingLogger.TraceDuration("Remove database file")) - { - RemoveDatabaseFile(null, ex => - { - //if this doesn't work we have to make sure everything is reset! otherwise - // well run into issues because we've already set some things up - TearDown(); - throw ex; - }); - } - - //Create the Sql CE database - using (ProfilingLogger.TraceDuration("Create database file")) - { - if (Options.Database != UmbracoTestOptions.Database.NewEmptyPerTest && _databaseBytes != null) - { - File.WriteAllBytes(_databasePath, _databaseBytes); - } - else - { - using (var engine = new SqlCeEngine(settings.ConnectionString)) - { - engine.CreateDatabase(); - } - } - } - - } - } - - protected IDefaultCultureAccessor DefaultCultureAccessor { get; set; } - - protected IPublishedSnapshotService PublishedSnapshotService { get; set; } - - protected override void Initialize() // FIXME: should NOT be here! - { - base.Initialize(); - - DefaultCultureAccessor = new TestDefaultCultureAccessor(); - - CreateAndInitializeDatabase(); - - // ensure we have a PublishedSnapshotService - if (PublishedSnapshotService == null) - { - PublishedSnapshotService = CreatePublishedSnapshotService(); - } - } - - protected virtual IPublishedSnapshotService CreatePublishedSnapshotService(GlobalSettings globalSettings = null) - { - var cache = NoAppCache.Instance; - - ContentTypesCache ??= new PublishedContentTypeCache( - Factory.GetRequiredService(), - Factory.GetRequiredService(), - Factory.GetRequiredService(), - Factory.GetRequiredService(), - Factory.GetRequiredService>()); - - // testing=true so XmlStore will not use the file nor the database - - var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor); - var variationContextAccessor = new TestVariationContextAccessor(); - var service = new XmlPublishedSnapshotService( - ServiceContext, - Factory.GetRequiredService(), - ScopeProvider, - cache, publishedSnapshotAccessor, variationContextAccessor, - Factory.GetRequiredService(), - Factory.GetRequiredService(), Factory.GetRequiredService(), Factory.GetRequiredService(), - DefaultCultureAccessor, - Factory.GetRequiredService(), - globalSettings ?? TestObjects.GetGlobalSettings(), - HostingEnvironment, - HostingLifetime, - ShortStringHelper, - Factory.GetRequiredService(), - ContentTypesCache, - null, true, Options.PublishedRepositoryEvents); - - // initialize PublishedCacheService content with an Xml source - service.XmlStore.GetXmlDocument = () => - { - var doc = new XmlDocument(); - doc.LoadXml(GetXmlContent(0)); - return doc; - }; - - return service; - } - - /// - /// Creates the tables and data for the database - /// - protected virtual void InitializeDatabase() - { - if (Options.Database == UmbracoTestOptions.Database.None || Options.Database == UmbracoTestOptions.Database.NewEmptyPerTest) - return; - - //create the schema and load default data if: - // - is the first test in the session - // - NewDbFileAndSchemaPerTest - // - _isFirstTestInFixture + DbInitBehavior.NewDbFileAndSchemaPerFixture - - if (_databaseBytes == null && - (FirstTestInSession - || Options.Database == UmbracoTestOptions.Database.NewSchemaPerTest - || FirstTestInFixture && Options.Database == UmbracoTestOptions.Database.NewSchemaPerFixture)) - { - using (var scope = ScopeProvider.CreateScope()) - { - var schemaHelper = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, Mock.Of()); - //Create the umbraco database and its base data - schemaHelper.InitializeDatabaseSchema(); - - //Special case, we need to create the xml cache tables manually since they are not part of the default - //setup. - //TODO: Remove this when we update all tests to use nucache - schemaHelper.CreateTable(); - schemaHelper.CreateTable(); - - scope.Complete(); - } - - _databaseBytes = File.ReadAllBytes(_databasePath); - } - } - - // FIXME: is this needed? - private void CloseDbConnections(IUmbracoDatabase database) - { - //Ensure that any database connections from a previous test is disposed. - //This is really just double safety as its also done in the TearDown. - database?.Dispose(); - //SqlCeContextGuardian.CloseBackgroundConnection(); - } - - private void RemoveDatabaseFile(IUmbracoDatabase database, Action onFail = null) - { - if (database != null) CloseDbConnections(database); - RemoveDatabaseFile(onFail); - } - - private void RemoveDatabaseFile(Action onFail = null) - { - var path = TestHelper.WorkingDirectory; - try - { - var filePath = string.Concat(path, "\\UmbracoNPocoTests.sdf"); - if (File.Exists(filePath)) - File.Delete(filePath); - } - catch (Exception ex) - { - LoggerFactory.CreateLogger().LogError(ex, "Could not remove the old database file"); - - // swallow this exception - that's because a sub class might require further teardown logic - onFail?.Invoke(ex); - } - } - - protected IUmbracoContextAccessor GetUmbracoContextAccessor(IUmbracoContext ctx) => new TestUmbracoContextAccessor(ctx); - - protected IUmbracoContext GetUmbracoContext(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, GlobalSettings globalSettings = null, IPublishedSnapshotService snapshotService = null) - { - // ensure we have a PublishedCachesService - var service = snapshotService ?? PublishedSnapshotService as XmlPublishedSnapshotService; - if (service == null) - throw new Exception("Not a proper XmlPublishedCache.PublishedCachesService."); - - if (service is XmlPublishedSnapshotService) - { - // re-initialize PublishedCacheService content with an Xml source with proper template id - ((XmlPublishedSnapshotService)service).XmlStore.GetXmlDocument = () => - { - var doc = new XmlDocument(); - doc.LoadXml(GetXmlContent(templateId)); - return doc; - }; - } - - var httpContext = GetHttpContextFactory(url, routeData).HttpContext; - var httpContextAccessor = TestHelper.GetHttpContextAccessor(httpContext); - var umbracoContext = new UmbracoContext( - httpContextAccessor, - service, - Mock.Of(), - globalSettings ?? new GlobalSettings(), - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - - if (setSingleton) - Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; - - return umbracoContext; - } - - protected virtual string GetXmlContent(int templateId) - { - return @" - - - - -]> - - - - - 1 - - This is some content]]> - - - - - - - - - - - - - - - - -"; - } - } -} diff --git a/tests/Umbraco.Tests/Testing/Objects/Accessors/NoHttpContextAccessor.cs b/tests/Umbraco.Tests/Testing/Objects/Accessors/NoHttpContextAccessor.cs deleted file mode 100644 index e34ff7bb45..0000000000 --- a/tests/Umbraco.Tests/Testing/Objects/Accessors/NoHttpContextAccessor.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Web; -using Umbraco.Web; - -namespace Umbraco.Tests.Testing.Objects.Accessors -{ - public class NoHttpContextAccessor : IHttpContextAccessor - { - public HttpContextBase HttpContext { get; set; } = null; - } -} diff --git a/tests/Umbraco.Tests/Testing/Objects/TestDataSource.cs b/tests/Umbraco.Tests/Testing/Objects/TestDataSource.cs deleted file mode 100644 index 97419ebe26..0000000000 --- a/tests/Umbraco.Tests/Testing/Objects/TestDataSource.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Infrastructure.PublishedCache; -using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; - -namespace Umbraco.Tests.Testing.Objects -{ - internal class TestDataSource : INuCacheContentService - { - private IPublishedModelFactory PublishedModelFactory { get; } = new NoopPublishedModelFactory(); - - public TestDataSource(params ContentNodeKit[] kits) - : this((IEnumerable) kits) - { } - - public TestDataSource(IEnumerable kits) => Kits = kits.ToDictionary(x => x.Node.Id, x => x); - - public Dictionary Kits { get; } - - // note: it is important to clone the returned kits, as the inner - // ContentNode is directly reused and modified by the snapshot service - public ContentNodeKit GetContentSource(int id) - => Kits.TryGetValue(id, out ContentNodeKit kit) ? kit.Clone(PublishedModelFactory) : default; - - public IEnumerable GetAllContentSources() - => Kits.Values - .OrderBy(x => x.Node.Level) - .ThenBy(x => x.Node.ParentContentId) - .ThenBy(x => x.Node.SortOrder) - .Select(x => x.Clone(PublishedModelFactory)); - - public IEnumerable GetBranchContentSources(int id) - => Kits.Values - .Where(x => x.Node.Path.EndsWith("," + id) || x.Node.Path.Contains("," + id + ",")) - .OrderBy(x => x.Node.Level) - .ThenBy(x => x.Node.ParentContentId) - .ThenBy(x => x.Node.SortOrder) - .Select(x => x.Clone(PublishedModelFactory)); - - public IEnumerable GetTypeContentSources(IEnumerable ids) - => Kits.Values - .Where(x => ids.Contains(x.ContentTypeId)) - .OrderBy(x => x.Node.Level) - .ThenBy(x => x.Node.ParentContentId) - .ThenBy(x => x.Node.SortOrder) - .Select(x => x.Clone(PublishedModelFactory)); - - public ContentNodeKit GetMediaSource(int id) => default; - - public IEnumerable GetAllMediaSources() => Enumerable.Empty(); - - public IEnumerable GetBranchMediaSources(int id) => Enumerable.Empty(); - - public IEnumerable GetTypeMediaSources(IEnumerable ids) => Enumerable.Empty(); - public void DeleteContentItem(IContentBase item) => throw new NotImplementedException(); - public void DeleteContentItems(IEnumerable items) => throw new NotImplementedException(); - public void RefreshContent(IContent content) => throw new NotImplementedException(); - public void RefreshEntity(IContentBase content) => throw new NotImplementedException(); - public bool VerifyContentDbCache() => throw new NotImplementedException(); - public bool VerifyMediaDbCache() => throw new NotImplementedException(); - public bool VerifyMemberDbCache() => throw new NotImplementedException(); - public void Rebuild(int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, IReadOnlyCollection memberTypeIds = null) => throw new NotImplementedException(); - } -} diff --git a/tests/Umbraco.Tests/Testing/UmbracoTestBase.cs b/tests/Umbraco.Tests/Testing/UmbracoTestBase.cs deleted file mode 100644 index 243e92ad53..0000000000 --- a/tests/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ /dev/null @@ -1,585 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Web.Routing; -using System.Xml.Linq; -using Examine; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; -using NUnit.Framework; -using Serilog; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Actions; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.ContentApps; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Dictionary; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Install; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.IO.MediaPathSchemes; -using Umbraco.Cms.Core.Logging; -using Umbraco.Cms.Core.Mail; -using Umbraco.Cms.Core.Manifest; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Media; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Net; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Sections; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Templates; -using Umbraco.Cms.Core.Trees; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure; -using Umbraco.Cms.Infrastructure.DependencyInjection; -using Umbraco.Cms.Infrastructure.Media; -using Umbraco.Cms.Infrastructure.Migrations.Install; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.Mappers; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Stubs; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.Hosting; -using Umbraco.Web.Security; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Umbraco.Tests.Testing -{ - /// - /// Provides the top-level base class for all Umbraco integration tests. - /// - /// - /// True unit tests do not need to inherit from this class, but most of Umbraco tests - /// are not true unit tests but integration tests requiring services, databases, etc. This class - /// provides all the necessary environment, through DI. Yes, DI is bad in tests - unit tests. - /// But it is OK in integration tests. - /// - public abstract class UmbracoTestBase - { - // this class - // ensures that Current is properly resetted - // ensures that a service container is properly initialized and disposed - // compose the required dependencies according to test options (UmbracoTestAttribute) - // - // everything is virtual (because, why not?) - // starting a test runs like this: - // - SetUp() // when overriding, call base.SetUp() *first* then setup your own stuff - // --- Compose() // when overriding, call base.Commpose() *first* then compose your own stuff - // --- Initialize() // same - // - test runs - // - TearDown() // when overriding, clear you own stuff *then* call base.TearDown() - // - // about attributes - // - // this class defines the SetUp and TearDown methods, with proper attributes, and - // these attributes are *inherited* so classes inheriting from this class should *not* - // add the attributes to SetUp nor TearDown again - // - // this class is *not* marked with the TestFeature attribute because it is *not* a - // test feature, and no test "base" class should be. only actual test feature classes - // should be marked with that attribute. - - protected IUmbracoBuilder Builder { get; private set; } - - protected IServiceProvider Factory { get; private set; } - - protected UmbracoTestAttribute Options { get; private set; } - - protected static bool FirstTestInSession { get; set; } = true; - - protected bool FirstTestInFixture { get; set; } = true; - - internal TestObjects TestObjects { get; private set; } - - private static TypeLoader _commonTypeLoader; - - private TypeLoader _featureTypeLoader; - - #region Accessors - protected ServiceContext ServiceContext => Factory.GetRequiredService(); - - protected ILoggerFactory LoggerFactory => Factory.GetRequiredService(); - - protected IJsonSerializer JsonNetSerializer { get; } = new JsonNetSerializer(); - - protected IIOHelper IOHelper { get; private set; } - protected UriUtility UriUtility => new UriUtility(HostingEnvironment); - protected IPublishedUrlProvider PublishedUrlProvider => Factory.GetRequiredService(); - protected IDataTypeService DataTypeService => Factory.GetRequiredService(); - protected IPasswordHasher PasswordHasher => Factory.GetRequiredService(); - protected Lazy PropertyEditorCollection => new Lazy(() => Factory.GetRequiredService()); - protected ILocalizationService LocalizationService => Factory.GetRequiredService(); - protected ILocalizedTextService LocalizedTextService { get; private set; } - protected IShortStringHelper ShortStringHelper => Factory?.GetRequiredService() ?? TestHelper.ShortStringHelper; - protected IImageUrlGenerator ImageUrlGenerator => Factory.GetRequiredService(); - protected UploadAutoFillProperties UploadAutoFillProperties => Factory.GetRequiredService(); - protected IUmbracoVersion UmbracoVersion { get; private set; } - - protected ITypeFinder TypeFinder { get; private set; } - - protected IProfiler Profiler => Factory.GetRequiredService(); - - protected virtual IProfilingLogger ProfilingLogger => Factory.GetRequiredService(); - - protected IHostingEnvironment HostingEnvironment { get; } = new AspNetHostingEnvironment(Microsoft.Extensions.Options.Options.Create(new HostingSettings())); - protected IApplicationShutdownRegistry HostingLifetime { get; } = new AspNetApplicationShutdownRegistry(); - protected IIpResolver IpResolver => Factory.GetRequiredService(); - protected IBackOfficeInfo BackOfficeInfo => Factory.GetRequiredService(); - protected AppCaches AppCaches => Factory.GetRequiredService(); - - protected virtual ISqlSyntaxProvider SqlSyntax => Factory.GetRequiredService(); - - protected IMapperCollection Mappers => Factory.GetRequiredService(); - - protected IUmbracoMapper Mapper => Factory.GetRequiredService(); - protected IHttpContextAccessor HttpContextAccessor => Factory.GetRequiredService(); - protected IContentService ContentService => Factory.GetRequiredService(); - protected IRuntimeState RuntimeState => MockRuntimeState(RuntimeLevel.Run); - private ILoggerFactory _loggerFactory; - - protected static IRuntimeState MockRuntimeState(RuntimeLevel level) - { - var runtimeState = Mock.Of(); - Mock.Get(runtimeState).Setup(x => x.Level).Returns(level); - return runtimeState; - } - #endregion - - #region Setup - - [SetUp] - public virtual void SetUp() - { - // should not need this if all other tests were clean - // but hey, never know, better avoid garbage-in - Reset(); - - // get/merge the attributes marking the method and/or the classes - Options = TestOptionAttributeBase.GetTestOptions(); - - // FIXME: align to runtimes & components - don't redo everything here !!!! Yes this is getting painful - - var loggerFactory = GetLoggerFactory(Options.Logger); - _loggerFactory = loggerFactory; - var profiler = new LogProfiler(loggerFactory.CreateLogger()); - var msLogger = loggerFactory.CreateLogger("msLogger"); - var proflogger = new ProfilingLogger(loggerFactory.CreateLogger(), profiler); - IOHelper = TestHelper.IOHelper; - - TypeFinder = new TypeFinder(loggerFactory.CreateLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly, loggerFactory), new VaryingRuntimeHash()); - var appCaches = GetAppCaches(); - var globalSettings = new GlobalSettings(); - var settings = new WebRoutingSettings(); - - IBackOfficeInfo backOfficeInfo = new AspNetBackOfficeInfo(globalSettings, IOHelper, loggerFactory.CreateLogger(), Microsoft.Extensions.Options.Options.Create(settings)); - IIpResolver ipResolver = new AspNetIpResolver(); - UmbracoVersion = new UmbracoVersion(); - - - LocalizedTextService = new LocalizedTextService(new Dictionary>(), loggerFactory.CreateLogger()); - var typeLoader = GetTypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, HostingEnvironment, loggerFactory.CreateLogger(), proflogger, Options.TypeLoader); - - var services = TestHelper.GetRegister(); - - Builder = new UmbracoBuilder(services, Mock.Of(), typeLoader); - - //TestHelper.GetConfigs().RegisterWith(register); - services.AddUnique(typeof(ILoggerFactory), loggerFactory); - services.AddTransient(typeof(ILogger<>), typeof(Logger<>)); - services.AddSingleton(msLogger); - services.AddUnique(IOHelper); - services.AddUnique(UriUtility); - services.AddUnique(UmbracoVersion); - services.AddUnique(TypeFinder); - services.AddUnique(LocalizedTextService); - services.AddUnique(typeLoader); - services.AddUnique(profiler); - services.AddUnique(proflogger); - services.AddUnique(appCaches); - services.AddUnique(HostingEnvironment); - services.AddUnique(backOfficeInfo); - services.AddUnique(ipResolver); - services.AddUnique(); - services.AddUnique(TestHelper.ShortStringHelper); - //services.AddUnique(); - - - var memberService = Mock.Of(); - var memberTypeService = Mock.Of(); - - TestObjects = new TestObjects(); - Compose(); - Current.Factory = Factory = TestHelper.CreateServiceProvider(Builder); - Initialize(); - } - - protected virtual void Compose() - { - ComposeMapper(Options.Mapper); - ComposeDatabase(Options.Database); - ComposeApplication(Options.WithApplication); - - // etc - ComposeWeb(); - ComposeMisc(); - - // not sure really - Compose(Builder); - } - - protected virtual void Compose(IUmbracoBuilder builder) - { } - - protected virtual void Initialize() - { - InitializeApplication(Options.WithApplication); - } - - #endregion - - #region Compose - - protected virtual ILoggerFactory GetLoggerFactory(UmbracoTestOptions.Logger option) - { - ILoggerFactory factory; - - switch (option) - { - case UmbracoTestOptions.Logger.Mock: - factory = NullLoggerFactory.Instance; - break; - case UmbracoTestOptions.Logger.Serilog: - factory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { builder.AddSerilog(); }); - break; - case UmbracoTestOptions.Logger.Console: - factory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { builder.AddConsole(); }); - break; - default: - throw new NotSupportedException($"Logger option {option} is not supported."); - } - - return factory; - } - - protected virtual AppCaches GetAppCaches() - { - return AppCaches.Disabled; - } - - protected virtual void ComposeWeb() - { - // imported from TestWithSettingsBase - // which was inherited by TestWithApplicationBase so pretty much used everywhere - Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); - - // web - Builder.Services.AddUnique(Current.UmbracoContextAccessor); - Builder.Services.AddUnique(Mock.Of()); - Builder.Services.AddUnique(); - Builder.WithCollectionBuilder(); - - - Builder.DataValueReferenceFactories(); - - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.SetCultureDictionaryFactory(); - Builder.Services.AddSingleton(f => f.GetRequiredService().CreateDictionary()); - // register back office sections in the order we want them rendered - Builder.WithCollectionBuilder().Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - Builder.Services.AddUnique(); - - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - - var webRoutingSettings = new WebRoutingSettings(); - Builder.Services.AddUnique(factory => - new UrlProvider( - factory.GetRequiredService(), - Microsoft.Extensions.Options.Options.Create(webRoutingSettings), - new UrlProviderCollection(Enumerable.Empty()), - new MediaUrlProviderCollection(Enumerable.Empty()), - factory.GetRequiredService())); - - - - } - - protected virtual void ComposeMisc() - { - // what else? - var runtimeStateMock = new Mock(); - runtimeStateMock.Setup(x => x.Level).Returns(RuntimeLevel.Run); - Builder.Services.AddUnique(f => runtimeStateMock.Object); - Builder.Services.AddTransient(_ => Mock.Of()); - Builder.Services.AddTransient(); - - // ah... - Builder.WithCollectionBuilder(); - Builder.WithCollectionBuilder(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - - Builder.Services.AddUnique(); - - // register empty content apps collection - Builder.WithCollectionBuilder(); - - // manifest - Builder.ManifestValueValidators(); - Builder.ManifestFilters(); - Builder.MediaUrlGenerators() - .Add() - .Add(); - - } - - protected virtual void ComposeMapper(bool configure) - { - if (configure == false) return; - - Builder - .AddCoreMappingProfiles(); - } - - protected virtual TypeLoader GetTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IHostingEnvironment hostingEnvironment, ILogger logger, IProfilingLogger profilingLogger, UmbracoTestOptions.TypeLoader option) - { - switch (option) - { - case UmbracoTestOptions.TypeLoader.Default: - return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(typeFinder, runtimeCache, logger, profilingLogger, hostingEnvironment)); - case UmbracoTestOptions.TypeLoader.PerFixture: - return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, profilingLogger, hostingEnvironment)); - case UmbracoTestOptions.TypeLoader.PerTest: - return CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, profilingLogger, hostingEnvironment); - default: - throw new ArgumentOutOfRangeException(nameof(option)); - } - } - - protected virtual TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, ILogger logger, IProfilingLogger profilingLogger, IHostingEnvironment hostingEnvironment) - { - return CreateCommonTypeLoader(typeFinder, runtimeCache, logger, profilingLogger, hostingEnvironment); - } - - // common to all tests = cannot be overriden - private static TypeLoader CreateCommonTypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, ILogger logger, IProfilingLogger profilingLogger, IHostingEnvironment hostingEnvironment) - { - return new TypeLoader(typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, profilingLogger, false, new[] - { - Assembly.Load("Umbraco.Core"), - Assembly.Load("Umbraco.Web"), - Assembly.Load("Umbraco.Tests"), - Assembly.Load("Umbraco.Infrastructure"), - }); - } - - protected virtual void ComposeDatabase(UmbracoTestOptions.Database option) - { - if (option == UmbracoTestOptions.Database.None) return; - - // create the file - // create the schema - } - - protected virtual void ComposeSettings() - { - var contentSettings = new ContentSettings(); - var coreDebugSettings = new CoreDebugSettings(); - var globalSettings = new GlobalSettings(); - var nuCacheSettings = new NuCacheSettings(); - var requestHandlerSettings = new RequestHandlerSettings(); - var userPasswordConfigurationSettings = new UserPasswordConfigurationSettings(); - var webRoutingSettings = new WebRoutingSettings(); - - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(contentSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(coreDebugSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(globalSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(requestHandlerSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(userPasswordConfigurationSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(webRoutingSettings)); - } - - protected virtual void ComposeApplication(bool withApplication) - { - ComposeSettings(); - - if (withApplication == false) return; - - // default Datalayer/Repositories/SQL/Database/etc... - Builder.AddRepositories(); - - Builder.Services.AddUnique(); - - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - - // register filesystems - Builder.Services.AddUnique(factory => TestObjects.GetFileSystemsMock()); - - - var scheme = Mock.Of(); - - var mediaFileManager = new MediaFileManager(Mock.Of(), scheme, Mock.Of>(), Mock.Of()); - Builder.Services.AddUnique(factory => mediaFileManager); - - // no factory (noop) - Builder.Services.AddUnique(); - - // register application stuff (database factory & context, services...) - Builder.WithCollectionBuilder() - .AddCoreMappers(); - - Builder.Services.AddUnique(_ => new TransientEventMessagesFactory()); - - var globalSettings = Microsoft.Extensions.Options.Options.Create(new GlobalSettings()); - var connectionStrings = Microsoft.Extensions.Options.Options.Create(new ConnectionStrings()); - - Builder.Services.AddUnique(f => new UmbracoDatabaseFactory(_loggerFactory.CreateLogger(), - LoggerFactory, - globalSettings, - connectionStrings, - new Lazy(f.GetRequiredService), - TestHelper.DbProviderFactoryCreator, - new DatabaseSchemaCreatorFactory(LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, Mock.Of()))); - - Builder.Services.AddUnique(f => f.GetService().SqlContext); - - Builder.WithCollectionBuilder(); // empty - - Builder.Services.AddUnique(factory - => TestObjects.GetScopeProvider(_loggerFactory, factory.GetService(), factory.GetService())); - Builder.Services.AddUnique(factory => (IScopeAccessor)factory.GetRequiredService()); - - Builder.AddServices(); - - // composition root is doing weird things, fix - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - - // somehow property editor ends up wanting this - Builder.WithCollectionBuilder(); - - Builder.Services.AddUnique(); - - // note - don't register collections, use builders - Builder.WithCollectionBuilder(); - Builder.Services.AddUnique(); - Builder.Services.AddUnique(); - - - Builder.Services.AddUnique(TestHelper.GetHttpContextAccessor(GetHttpContextFactory("/").HttpContext)); - } - - #endregion - - protected FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null) - { - var factory = routeData != null - ? new FakeHttpContextFactory(url, routeData) - : new FakeHttpContextFactory(url); - - return factory; - } - - #region Initialize - - protected virtual void InitializeApplication(bool withApplication) - { - if (withApplication == false) return; - - TestHelper.InitializeContentDirectories(); - } - - #endregion - - #region TearDown and Reset - - [TearDown] - public virtual void TearDown() - { - FirstTestInFixture = false; - FirstTestInSession = false; - - Reset(); - - if (Options.WithApplication) - { - TestHelper.CleanContentDirectories(); - TestHelper.CleanUmbracoSettingsConfig(); - } - } - - protected virtual void Reset() - { - try - { - // reset and dispose scopes - // ensures we don't leak an opened database connection - // which would lock eg SqlCe .sdf files - if (Factory?.GetService() is ScopeProvider scopeProvider) - { - Scope scope; - while ((scope = scopeProvider.AmbientScope) != null) - { - scope.Reset(); - scope.Dispose(); - } - } - } - catch (ObjectDisposedException ex) - { - if (!ex.ObjectName.Equals(nameof(IServiceProvider))) - throw; - } - - // reset all other static things that should not be static ;( - UriUtility.ResetAppDomainAppVirtualPath(HostingEnvironment); - } - - #endregion - } -} diff --git a/tests/Umbraco.Tests/Umbraco.Tests.csproj b/tests/Umbraco.Tests/Umbraco.Tests.csproj index 75f2e0a805..a83c0d6453 100644 --- a/tests/Umbraco.Tests/Umbraco.Tests.csproj +++ b/tests/Umbraco.Tests/Umbraco.Tests.csproj @@ -135,170 +135,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -338,47 +180,10 @@ Umbraco.Web - - - ResXFileCodeGenerator - SqlResources.Designer.cs - Designer - - - ResXFileCodeGenerator - ImportResources.Designer.cs - Designer - - - ResXFileCodeGenerator - TestFiles.Designer.cs - Designer - - - - - Designer - Always - - - Always - - - - - - Designer - - - - - - - - + $(NuGetPackageFolders.Split(';')[0]) diff --git a/tests/Umbraco.Tests/Web/HttpCookieExtensionsTests.cs b/tests/Umbraco.Tests/Web/HttpCookieExtensionsTests.cs deleted file mode 100644 index 3964017e57..0000000000 --- a/tests/Umbraco.Tests/Web/HttpCookieExtensionsTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; -using NUnit.Framework; -using Umbraco.Web; - -namespace Umbraco.Tests.Web -{ - [TestFixture] - public class HttpCookieExtensionsTests - { - [TestCase("hello=world;cookies=are fun;", "hello", "world", true)] - [TestCase("HELlo=world;cookies=are fun", "hello", "world", true)] - [TestCase("HELlo= world;cookies=are fun", "hello", "world", true)] - [TestCase("HELlo =world;cookies=are fun", "hello", "world", true)] - [TestCase("hello = world;cookies=are fun;", "hello", "world", true)] - [TestCase("hellos=world;cookies=are fun", "hello", "world", false)] - [TestCase("hello=world;cookies?=are fun?", "hello", "world", true)] - [TestCase("hel?lo=world;cookies=are fun?", "hel?lo", "world", true)] - public void Get_Cookie_Value_From_HttpRequestHeaders(string cookieHeaderVal, string cookieName, string cookieVal, bool matches) - { - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - var requestHeaders = request.Headers; - requestHeaders.Add("Cookie", cookieHeaderVal); - - var valueFromHeader = requestHeaders.GetCookieValue(cookieName); - - if (matches) - { - Assert.IsNotNull(valueFromHeader); - Assert.AreEqual(cookieVal, valueFromHeader); - } - else - { - Assert.IsNull(valueFromHeader); - } - } - } -} diff --git a/tests/Umbraco.Tests/Web/Mvc/ViewDataDictionaryExtensionTests.cs b/tests/Umbraco.Tests/Web/Mvc/ViewDataDictionaryExtensionTests.cs deleted file mode 100644 index 869fd1b329..0000000000 --- a/tests/Umbraco.Tests/Web/Mvc/ViewDataDictionaryExtensionTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Web.Mvc; -using NUnit.Framework; -using Umbraco.Web.Mvc; - -namespace Umbraco.Tests.Web.Mvc -{ - [TestFixture] - public class ViewDataDictionaryExtensionTests - { - [Test] - public void Merge_View_Data() - { - var source = new ViewDataDictionary(); - var dest = new ViewDataDictionary(); - source.Add("Test1", "Test1"); - dest.Add("Test2", "Test2"); - - dest.MergeViewDataFrom(source); - - Assert.AreEqual(2, dest.Count); - } - - [Test] - public void Merge_View_Data_Retains_Destination_Values() - { - var source = new ViewDataDictionary(); - var dest = new ViewDataDictionary(); - source.Add("Test1", "Test1"); - dest.Add("Test1", "MyValue"); - dest.Add("Test2", "Test2"); - - dest.MergeViewDataFrom(source); - - Assert.AreEqual(2, dest.Count); - Assert.AreEqual("MyValue", dest["Test1"]); - Assert.AreEqual("Test2", dest["Test2"]); - } - - } -} diff --git a/tests/Umbraco.Tests/masterpages/site1/template2.master b/tests/Umbraco.Tests/masterpages/site1/template2.master deleted file mode 100644 index f1a6da0d51..0000000000 --- a/tests/Umbraco.Tests/masterpages/site1/template2.master +++ /dev/null @@ -1,5 +0,0 @@ -<%@ Master Language="C#" MasterPageFile="~/umbraco/masterpages/default.master" AutoEventWireup="true" %> - - - -