diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 2dd5626d9a..ecfcd84dca 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -523,7 +523,7 @@ stages: inputs: targetType: inline script: | - choco install docfx --version=2.58.5 -y + choco install docfx --version=2.59.0 -y if ($lastexitcode -ne 0){ throw ("Error installing DocFX") } diff --git a/src/ApiDocs/umbracotemplate/partials/class.tmpl.partial b/src/ApiDocs/umbracotemplate/partials/class.tmpl.partial index 9153a863a4..aa50d597ba 100644 --- a/src/ApiDocs/umbracotemplate/partials/class.tmpl.partial +++ b/src/ApiDocs/umbracotemplate/partials/class.tmpl.partial @@ -15,8 +15,8 @@
{{item.name.0.value}}
{{/inheritance.0}} -
{{__global.namespace}}:{{namespace}}
-
{{__global.assembly}}:{{assemblies.0}}.dll
+
{{__global.namespace}}: {{{namespace.specName.0.value}}}
+
{{__global.assembly}}: {{assemblies.0}}.dll
{{__global.syntax}}
{{syntax.content.0.value}}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 68962caef4..d7161acb1f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,10 +3,10 @@ - 9.4.0 - 9.4.0 - 9.4.0-rc - 9.4.0 + 9.5.0 + 9.5.0 + 9.5.0-rc + 9.5.0 9.0 en-US Umbraco CMS diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index 73c5ea18f5..f9aa6b500c 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -3,6 +3,7 @@ using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Deploy.Core.Configuration.DebugConfiguration; using Umbraco.Deploy.Core.Configuration.DeployConfiguration; using Umbraco.Deploy.Core.Configuration.DeployProjectConfiguration; using Umbraco.Forms.Core.Configuration; @@ -127,6 +128,8 @@ namespace JsonSchema public DeploySettings Settings { get; set; } public DeployProjectConfig Project { get; set; } + + public DebugSettings Debug { get; set; } } } } diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj index 7e87eed2e4..d2ef20e741 100644 --- a/src/JsonSchema/JsonSchema.csproj +++ b/src/JsonSchema/JsonSchema.csproj @@ -14,6 +14,13 @@ + + + + + + + diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs index 77550b81d1..e1b65e2a32 100644 --- a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs @@ -46,6 +46,11 @@ namespace Umbraco.Cms.Core.Cache { var payloads = Deserialize(json); + Refresh(payloads); + } + + public override void Refresh(JsonPayload[] payloads) + { foreach (var payload in payloads) { foreach (var alias in GetCacheKeysForAlias(payload.Alias)) @@ -55,11 +60,13 @@ namespace Umbraco.Cms.Core.Cache if (macroRepoCache) { macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Id)); + macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Alias)); // Repository caching of macro definition by alias } } - base.Refresh(json); + base.Refresh(payloads); } + #endregion #region Json diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index 93a97355d9..e6e5c7006f 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -157,6 +157,8 @@ namespace Umbraco.Cms.Core.Configuration.Models internal const string StaticLoginBackgroundImage = "assets/img/login.jpg"; internal const string StaticLoginLogoImage = "assets/img/application/umbraco_logo_white.svg"; internal const bool StaticHideBackOfficeLogo = false; + internal const bool StaticDisableDeleteWhenReferenced = false; + internal const bool StaticDisableUnpublishWhenReferenced = false; /// /// Gets or sets a value for the content notification settings. @@ -226,6 +228,18 @@ namespace Umbraco.Cms.Core.Configuration.Models [DefaultValue(StaticHideBackOfficeLogo)] public bool HideBackOfficeLogo { get; set; } = StaticHideBackOfficeLogo; + /// + /// Gets or sets a value indicating whether to disable the deletion of items referenced by other items. + /// + [DefaultValue(StaticDisableDeleteWhenReferenced)] + public bool DisableDeleteWhenReferenced { get; set; } = StaticDisableDeleteWhenReferenced; + + /// + /// Gets or sets a value indicating whether to disable the unpublishing of items referenced by other items. + /// + [DefaultValue(StaticDisableUnpublishWhenReferenced)] + public bool DisableUnpublishWhenReferenced { get; set; } = StaticDisableUnpublishWhenReferenced; + /// /// Get or sets the model representing the global content version cleanup policy /// diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index 7e3e1a2700..5e42d3b8be 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -29,6 +29,7 @@ namespace Umbraco.Cms.Core.Configuration.Models internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml"; internal const string StaticSqlWriteLockTimeOut = "00:00:05"; internal const bool StaticSanitizeTinyMce = false; + internal const int StaticMainDomReleaseSignalPollingInterval = 2000; /// /// Gets or sets a value for the reserved URLs (must end with a comma). @@ -137,6 +138,26 @@ namespace Umbraco.Cms.Core.Configuration.Models /// public string MainDomLock { get; set; } = string.Empty; + /// + /// Gets or sets a value to discriminate MainDom boundaries. + /// + /// Generally the default should suffice but useful for advanced scenarios e.g. azure deployment slot based zero downtime deployments. + /// + /// + public string MainDomKeyDiscriminator { get; set; } = string.Empty; + + /// + /// Gets or sets the duration (in milliseconds) for which the MainDomLock release signal polling task should sleep. + /// + /// + /// Doesn't apply to MainDomSemaphoreLock. + /// + /// The default value is 2000ms. + /// + /// + [DefaultValue(StaticMainDomReleaseSignalPollingInterval)] + public int MainDomReleaseSignalPollingInterval { get; set; } = StaticMainDomReleaseSignalPollingInterval; + /// /// Gets or sets the telemetry ID. /// @@ -174,18 +195,18 @@ namespace Umbraco.Cms.Core.Configuration.Models public bool IsPickupDirectoryLocationConfigured => !string.IsNullOrWhiteSpace(Smtp?.PickupDirectoryLocation); /// - /// Gets a value indicating whether TinyMCE scripting sanitization should be applied. + /// Gets or sets a value indicating whether TinyMCE scripting sanitization should be applied. /// [DefaultValue(StaticSanitizeTinyMce)] - public bool SanitizeTinyMce => StaticSanitizeTinyMce; + public bool SanitizeTinyMce { get; set; } = StaticSanitizeTinyMce; /// - /// Gets a value representing the time in milliseconds to lock the database for a write operation. + /// An int value representing the time in milliseconds to lock the database for a write operation /// /// /// The default value is 5000 milliseconds. /// [DefaultValue(StaticSqlWriteLockTimeOut)] - public TimeSpan SqlWriteLockTimeOut { get; } = TimeSpan.Parse(StaticSqlWriteLockTimeOut); + public TimeSpan SqlWriteLockTimeOut { get; set; } = TimeSpan.Parse(StaticSqlWriteLockTimeOut); } } diff --git a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs index 6ea563c741..de8215a51b 100644 --- a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Configuration.Models [UmbracoOptions(Constants.Configuration.ConfigRichTextEditor)] public class RichTextEditorSettings { - internal const string StaticValidElements = "+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*]"; + internal const string StaticValidElements = "+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*],figure,figcaption"; internal const string StaticInvalidElements = "font"; private static readonly string[] s_default_plugins = new[] diff --git a/src/Umbraco.Core/Constants-HttpClients.cs b/src/Umbraco.Core/Constants-HttpClients.cs new file mode 100644 index 0000000000..474ec49a50 --- /dev/null +++ b/src/Umbraco.Core/Constants-HttpClients.cs @@ -0,0 +1,19 @@ +namespace Umbraco.Cms.Core +{ + /// + /// Defines constants. + /// + public static partial class Constants + { + /// + /// Defines constants for named http clients. + /// + public static class HttpClients + { + /// + /// Name for http client which ignores certificate errors. + /// + public const string IgnoreCertificateErrors = "Umbraco:HttpClients:IgnoreCertificateErrors"; + } + } +} diff --git a/src/Umbraco.Core/Constants-SystemDirectories.cs b/src/Umbraco.Core/Constants-SystemDirectories.cs index 40b0267d73..f70dd199fc 100644 --- a/src/Umbraco.Core/Constants-SystemDirectories.cs +++ b/src/Umbraco.Core/Constants-SystemDirectories.cs @@ -46,11 +46,9 @@ namespace Umbraco.Cms.Core public const string AppPlugins = "/App_Plugins"; [Obsolete("Use PluginIcons instead")] - public const string AppPluginIcons = "/Backoffice/Icons"; - public const string PluginIcons = "/backoffice/icons"; - - public const string CreatedPackages = "/created-packages"; + public static string AppPluginIcons => "/Backoffice/Icons"; + public const string PluginIcons = "/backoffice/icons"; public const string MvcViews = "~/Views"; @@ -60,6 +58,8 @@ namespace Umbraco.Cms.Core public const string Packages = Data + "/packages"; + public const string CreatedPackages = Data + "/CreatedPackages"; + public const string Preview = Data + "/preview"; /// diff --git a/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs b/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs new file mode 100644 index 0000000000..b1fb31d2aa --- /dev/null +++ b/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Core.ContentApps +{ + internal class DictionaryContentAppFactory : IContentAppFactory + { + private const int Weight = -100; + + private ContentApp _dictionaryApp; + + public ContentApp GetContentAppFor(object source, IEnumerable userGroups) + { + switch (source) + { + case IDictionaryItem _: + return _dictionaryApp ??= new ContentApp + { + Alias = "dictionaryContent", + Name = "Content", + Icon = "icon-document", + View = "views/dictionary/views/content/content.html", + Weight = Weight + }; + default: + return null; + } + } + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index 811ee35c14..a0ff6104a7 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -46,7 +46,8 @@ namespace Umbraco.Cms.Core.DependencyInjection .Append() .Append() .Append() - .Append(); + .Append() + .Append(); // all built-in finders in the correct order, // devs can then modify this list on application startup diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index c4a95d45e5..235dc71252 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -262,6 +262,7 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddSingleton(); // Register telemetry service used to gather data about installed packages + Services.AddUnique(); Services.AddUnique(); // Register a noop IHtmlSanitizer to be replaced diff --git a/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs b/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs index 0a6518a1ca..3a73173127 100644 --- a/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs +++ b/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs @@ -33,7 +33,8 @@ namespace Umbraco.Cms.Core.Events Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, true, Constants.ObjectTypes.Document, - Constants.ObjectTypes.Document); + Constants.ObjectTypes.Document, + false); _relationService.Save(relationType); } diff --git a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs index bceddf1fd6..0319be3297 100644 --- a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs +++ b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs @@ -132,7 +132,7 @@ namespace Umbraco.Extensions } } - verifiedIdentity = identity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType ? identity : new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType); + verifiedIdentity = identity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType ? identity : new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType); return true; } diff --git a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs index 37769afc53..d95fa6919d 100644 --- a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs @@ -1,10 +1,13 @@ using System; using System.Threading.Tasks; +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.Install.Models; +using Umbraco.Cms.Core.Telemetry; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Core.Install.InstallSteps { @@ -13,31 +16,29 @@ namespace Umbraco.Cms.Core.Install.InstallSteps PerformsAppRestart = false)] public class TelemetryIdentifierStep : InstallSetupStep { - private readonly ILogger _logger; private readonly IOptions _globalSettings; - private readonly IConfigManipulator _configManipulator; + private readonly ISiteIdentifierService _siteIdentifierService; - public TelemetryIdentifierStep(ILogger logger, IOptions globalSettings, IConfigManipulator configManipulator) + public TelemetryIdentifierStep( + IOptions globalSettings, + ISiteIdentifierService siteIdentifierService) { - _logger = logger; _globalSettings = globalSettings; - _configManipulator = configManipulator; + _siteIdentifierService = siteIdentifierService; + } + + [Obsolete("Use constructor that takes GlobalSettings and ISiteIdentifierService")] + public TelemetryIdentifierStep( + ILogger logger, + IOptions globalSettings, + IConfigManipulator configManipulator) + : this(globalSettings, StaticServiceProvider.Instance.GetRequiredService()) + { } public override Task ExecuteAsync(object model) { - // Generate GUID - var telemetrySiteIdentifier = Guid.NewGuid(); - - try - { - _configManipulator.SetGlobalId(telemetrySiteIdentifier.ToString()); - } - catch (Exception ex) - { - _logger.LogError(ex, "Couldn't update config files with a telemetry site identifier"); - } - + _siteIdentifierService.TryCreateSiteIdentifier(out _); return Task.FromResult(null); } diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs index d865bb01aa..0bb555f16f 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -70,6 +70,10 @@ namespace Umbraco.Cms.Core.Manifest partA = "contentType"; partB = contentType.Alias; break; + case IDictionaryItem _: + partA = "dictionary"; + partB = "*"; //Not really a different type for dictionary items + break; default: return null; diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 276219aae3..143a00e527 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -237,7 +237,7 @@ namespace Umbraco.Cms.Core.Models public bool IsCulturePublished(string culture) // just check _publishInfos // a non-available culture could not become published anyways - => _publishInfos != null && _publishInfos.ContainsKey(culture); + => !culture.IsNullOrWhiteSpace() && _publishInfos != null && _publishInfos.ContainsKey(culture); /// public bool IsCultureEdited(string culture) diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs index 41e49ba34d..d8cfaf1104 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -17,6 +17,7 @@ namespace Umbraco.Cms.Core.Models.ContentEditing { Notifications = new List(); Translations = new List(); + ContentApps = new List(); } /// @@ -37,5 +38,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing /// [DataMember(Name = "translations")] public List Translations { get; private set; } + + /// + /// Apps for the dictionary item + /// + [DataMember(Name = "apps")] + public List ContentApps { get; private set; } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs index b7bfb32808..a0d9bbbcb3 100644 --- a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs +++ b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs @@ -1,17 +1,34 @@ using System.Runtime.Serialization; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models.ContentEditing { [DataContract(Name = "historyCleanup", Namespace = "")] - public class HistoryCleanup + public class HistoryCleanup : BeingDirtyBase { + private bool _preventCleanup; + private int? _keepAllVersionsNewerThanDays; + private int? _keepLatestVersionPerDayForDays; + [DataMember(Name = "preventCleanup")] - public bool PreventCleanup { get; set; } + public bool PreventCleanup + { + get => _preventCleanup; + set => SetPropertyValueAndDetectChanges(value, ref _preventCleanup, nameof(PreventCleanup)); + } [DataMember(Name = "keepAllVersionsNewerThanDays")] - public int? KeepAllVersionsNewerThanDays { get; set; } + public int? KeepAllVersionsNewerThanDays + { + get => _keepAllVersionsNewerThanDays; + set => SetPropertyValueAndDetectChanges(value, ref _keepAllVersionsNewerThanDays, nameof(KeepAllVersionsNewerThanDays)); + } [DataMember(Name = "keepLatestVersionPerDayForDays")] - public int? KeepLatestVersionPerDayForDays { get; set; } + public int? KeepLatestVersionPerDayForDays + { + get => _keepLatestVersionPerDayForDays; + set => SetPropertyValueAndDetectChanges(value, ref _keepLatestVersionPerDayForDays, nameof(KeepLatestVersionPerDayForDays)); + } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs index 27f0f525df..6a4c8e5f81 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs @@ -55,5 +55,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing /// [DataMember(Name = "notifications")] public List Notifications { get; private set; } + + /// + /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries. + /// + [DataMember(Name = "isDependency", IsRequired = true)] + public bool IsDependency { get; set; } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs index b72a03eec4..f541158095 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs @@ -23,5 +23,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing /// [DataMember(Name = "childObjectType", IsRequired = false)] public Guid? ChildObjectType { get; set; } + + /// + /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries. + /// + [DataMember(Name = "isDependency", IsRequired = true)] + public bool IsDependency { get; set; } } } diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 6ff94f57f3..a252aa4723 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -96,7 +96,13 @@ namespace Umbraco.Cms.Core.Models } } - public HistoryCleanup HistoryCleanup { get; set; } + private HistoryCleanup _historyCleanup; + + public HistoryCleanup HistoryCleanup + { + get => _historyCleanup; + set => SetPropertyValueAndDetectChanges(value, ref _historyCleanup, nameof(HistoryCleanup)); + } /// /// Determines if AllowedTemplates contains templateId @@ -162,5 +168,8 @@ namespace Umbraco.Cms.Core.Models /// IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) => (IContentType)DeepCloneWithResetIdentities(newAlias); + + /// + public override bool IsDirty() => base.IsDirty() || HistoryCleanup.IsDirty(); } } diff --git a/src/Umbraco.Core/Models/IRelationType.cs b/src/Umbraco.Core/Models/IRelationType.cs index 9efde4b939..3ee1517f55 100644 --- a/src/Umbraco.Core/Models/IRelationType.cs +++ b/src/Umbraco.Core/Models/IRelationType.cs @@ -4,6 +4,15 @@ using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models { + public interface IRelationTypeWithIsDependency : IRelationType + { + /// + /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries. + /// + [DataMember] + bool IsDependency { get; set; } + } + public interface IRelationType : IEntity, IRememberBeingDirty { /// diff --git a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs index 3cfcc89085..e424dabb93 100644 --- a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Cms.Core.ContentApps; @@ -48,6 +48,11 @@ namespace Umbraco.Cms.Core.Models.Mapping } public IEnumerable GetContentApps(IUmbracoEntity source) + { + return GetContentAppsForEntity(source); + } + + public IEnumerable GetContentAppsForEntity(IEntity source) { var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray(); diff --git a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs index 3625e90a14..2f85a95953 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs @@ -133,7 +133,7 @@ namespace Umbraco.Cms.Core.Models.Mapping if (target is IContentTypeWithHistoryCleanup targetWithHistoryCleanup) { - targetWithHistoryCleanup.HistoryCleanup = source.HistoryCleanup; + MapHistoryCleanup(source, targetWithHistoryCleanup); } target.AllowedTemplates = source.AllowedTemplates @@ -147,6 +147,34 @@ namespace Umbraco.Cms.Core.Models.Mapping : _fileService.GetTemplate(source.DefaultTemplate)); } + private static void MapHistoryCleanup(DocumentTypeSave source, IContentTypeWithHistoryCleanup target) + { + // If source history cleanup is null we don't have to map all properties + if (source.HistoryCleanup is null) + { + target.HistoryCleanup = null; + return; + } + + // We need to reset the dirty properties, because it is otherwise true, just because the json serializer has set properties + target.HistoryCleanup.ResetDirtyProperties(false); + if (target.HistoryCleanup.PreventCleanup != source.HistoryCleanup.PreventCleanup) + { + target.HistoryCleanup.PreventCleanup = source.HistoryCleanup.PreventCleanup; + } + + if (target.HistoryCleanup.KeepAllVersionsNewerThanDays != source.HistoryCleanup.KeepAllVersionsNewerThanDays) + { + target.HistoryCleanup.KeepAllVersionsNewerThanDays = source.HistoryCleanup.KeepAllVersionsNewerThanDays; + } + + if (target.HistoryCleanup.KeepLatestVersionPerDayForDays != + source.HistoryCleanup.KeepLatestVersionPerDayForDays) + { + target.HistoryCleanup.KeepLatestVersionPerDayForDays = source.HistoryCleanup.KeepLatestVersionPerDayForDays; + } + } + // no MapAll - take care private void Map(MediaTypeSave source, IMediaType target, MapperContext context) { @@ -196,7 +224,7 @@ namespace Umbraco.Cms.Core.Models.Mapping target.AllowCultureVariant = source.VariesByCulture(); target.AllowSegmentVariant = source.VariesBySegment(); - target.ContentApps = _commonMapper.GetContentApps(source); + target.ContentApps = _commonMapper.GetContentAppsForEntity(source); //sync templates target.AllowedTemplates = context.MapEnumerable(source.AllowedTemplates); @@ -328,7 +356,10 @@ namespace Umbraco.Cms.Core.Models.Mapping if (source.GroupId > 0) { - target.PropertyGroupId = new Lazy(() => source.GroupId, false); + if (target.PropertyGroupId?.Value != source.GroupId) + { + target.PropertyGroupId = new Lazy(() => source.GroupId, false); + } } target.Alias = source.Alias; @@ -523,7 +554,15 @@ namespace Umbraco.Cms.Core.Models.Mapping target.Thumbnail = source.Thumbnail; target.AllowedAsRoot = source.AllowAsRoot; - target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)); + + bool allowedContentTypesUnchanged = target.AllowedContentTypes.Select(x => x.Id.Value) + .SequenceEqual(source.AllowedContentTypes); + + if (allowedContentTypesUnchanged is false) + { + target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)); + } + if (!(target is IMemberType)) { @@ -574,13 +613,21 @@ namespace Umbraco.Cms.Core.Models.Mapping // ensure no duplicate alias, then assign the group properties collection EnsureUniqueAliases(destProperties); - destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); + if (destGroup.PropertyTypes.SupportsPublishing != isPublishing || destGroup.PropertyTypes.SequenceEqual(destProperties) is false) + { + destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); + } + destGroups.Add(destGroup); } // ensure no duplicate name, then assign the groups collection EnsureUniqueAliases(destGroups); - target.PropertyGroups = new PropertyGroupCollection(destGroups); + + if (target.PropertyGroups.SequenceEqual(destGroups) is false) + { + target.PropertyGroups = new PropertyGroupCollection(destGroups); + } // because the property groups collection was rebuilt, there is no need to remove // the old groups - they are just gone and will be cleared by the repository diff --git a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs index 4c000f0173..b93f99c5c5 100644 --- a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Services; @@ -14,12 +15,20 @@ namespace Umbraco.Cms.Core.Models.Mapping public class DictionaryMapDefinition : IMapDefinition { private readonly ILocalizationService _localizationService; + private readonly CommonMapper _commonMapper; + [Obsolete("Use the constructor with the CommonMapper")] public DictionaryMapDefinition(ILocalizationService localizationService) { _localizationService = localizationService; } + public DictionaryMapDefinition(ILocalizationService localizationService, CommonMapper commonMapper) + { + _localizationService = localizationService; + _commonMapper = commonMapper; + } + public void DefineMaps(IUmbracoMapper mapper) { mapper.Define((source, context) => new EntityBasic(), Map); @@ -44,6 +53,10 @@ namespace Umbraco.Cms.Core.Models.Mapping target.Name = source.ItemKey; target.ParentId = source.ParentId ?? Guid.Empty; target.Udi = Udi.Create(Constants.UdiEntityType.DictionaryItem, source.Key); + if (_commonMapper != null) + { + target.ContentApps.AddRange(_commonMapper.GetContentAppsForEntity(source)); + } // build up the path to make it possible to set active item in tree // TODO: check if there is a better way diff --git a/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs index 41caa526e2..2b333652b9 100644 --- a/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs @@ -30,6 +30,11 @@ namespace Umbraco.Cms.Core.Models.Mapping target.ChildObjectType = source.ChildObjectType; target.Id = source.Id; target.IsBidirectional = source.IsBidirectional; + + if (source is IRelationTypeWithIsDependency sourceWithIsDependency) + { + target.IsDependency = sourceWithIsDependency.IsDependency; + } target.Key = source.Key; target.Name = source.Name; target.Alias = source.Alias; @@ -74,6 +79,11 @@ namespace Umbraco.Cms.Core.Models.Mapping target.ChildObjectType = source.ChildObjectType; target.Id = source.Id.TryConvertTo().Result; target.IsBidirectional = source.IsBidirectional; + if (target is IRelationTypeWithIsDependency targetWithIsDependency) + { + targetWithIsDependency.IsDependency = source.IsDependency; + } + target.Key = source.Key; target.Name = source.Name; target.ParentObjectType = source.ParentObjectType; diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs new file mode 100644 index 0000000000..cebbc20951 --- /dev/null +++ b/src/Umbraco.Core/Models/RelationItem.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Core.Models +{ + [DataContract(Name = "relationItem", Namespace = "")] + public class RelationItem + { + [DataMember(Name = "id")] + public int NodeId { get; set; } + + [DataMember(Name = "key")] + public Guid NodeKey { get; set; } + + [DataMember(Name = "name")] + public string NodeName { get; set; } + + [DataMember(Name = "type")] + public string NodeType { get; set; } + + [DataMember(Name = "udi")] + public Udi NodeUdi => Udi.Create(NodeType, NodeKey); + + [DataMember(Name = "icon")] + public string ContentTypeIcon { get; set; } + + [DataMember(Name = "alias")] + public string ContentTypeAlias { get; set; } + + [DataMember(Name = "contentTypeName")] + public string ContentTypeName { get; set; } + + [DataMember(Name = "relationTypeName")] + public string RelationTypeName { get; set; } + + [DataMember(Name = "relationTypeIsBidirectional")] + public bool RelationTypeIsBidirectional { get; set; } + + [DataMember(Name = "relationTypeIsDependency")] + public bool RelationTypeIsDependency { get; set; } + + } +} diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index 2def0eb636..5de0aaac8f 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -9,20 +9,28 @@ namespace Umbraco.Cms.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class RelationType : EntityBase, IRelationType + public class RelationType : EntityBase, IRelationType, IRelationTypeWithIsDependency { private string _name; private string _alias; private bool _isBidirectional; + private bool _isDependency; private Guid? _parentObjectType; private Guid? _childObjectType; public RelationType(string alias, string name) - : this(name: name, alias: alias, false, null, null) + : this(name: name, alias: alias, false, null, null, false) { } + [Obsolete("Use ctor with isDependency parameter")] public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType) + :this(name,alias,isBidrectional, parentObjectType, childObjectType, false) + { + + } + + public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType, bool isDependency) { if (name == null) throw new ArgumentNullException(nameof(name)); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); @@ -32,6 +40,7 @@ namespace Umbraco.Cms.Core.Models _name = name; _alias = alias; _isBidirectional = isBidrectional; + _isDependency = isDependency; _parentObjectType = parentObjectType; _childObjectType = childObjectType; } @@ -88,5 +97,11 @@ namespace Umbraco.Cms.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType)); } + + public bool IsDependency + { + get => _isDependency; + set => SetPropertyValueAndDetectChanges(value, ref _isDependency, nameof(IsDependency)); + } } } diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 36b7a5d5d5..ac09655476 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; -using System.Text; using System.Xml.Linq; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -33,7 +32,7 @@ namespace Umbraco.Cms.Core.Packaging private readonly IEntityXmlSerializer _serializer; private readonly IHostingEnvironment _hostingEnvironment; private readonly string _packageRepositoryFileName; - private readonly string _mediaFolderPath; + private readonly string _createdPackagesFolderPath; private readonly string _packagesFolderPath; private readonly string _tempFolderPath; private readonly PackageDefinitionXmlParser _parser; @@ -93,7 +92,7 @@ namespace Umbraco.Cms.Core.Packaging _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; _packagesFolderPath = packagesFolderPath ?? Constants.SystemDirectories.Packages; - _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages); + _createdPackagesFolderPath = mediaFolderPath ?? Constants.SystemDirectories.CreatedPackages; _parser = new PackageDefinitionXmlParser(); _mediaService = mediaService; @@ -250,15 +249,8 @@ namespace Umbraco.Cms.Core.Packaging } } - - - var directoryName = - _hostingEnvironment.MapPathWebRoot(Path.Combine(_mediaFolderPath, definition.Name.Replace(' ', '_'))); - - if (Directory.Exists(directoryName) == false) - { - Directory.CreateDirectory(directoryName); - } + var directoryName = _hostingEnvironment.MapPathContentRoot(Path.Combine(_createdPackagesFolderPath, definition.Name.Replace(' ', '_'))); + Directory.CreateDirectory(directoryName); var finalPackagePath = Path.Combine(directoryName, fileName); @@ -276,14 +268,14 @@ namespace Umbraco.Cms.Core.Packaging } finally { - //Clean up + // Clean up Directory.Delete(temporaryPath, true); } } private void ValidatePackage(PackageDefinition definition) { - //ensure it's valid + // ensure it's valid var context = new ValidationContext(definition, serviceProvider: null, items: null); var results = new List(); var isValid = Validator.TryValidateObject(definition, context, results); @@ -732,7 +724,6 @@ namespace Umbraco.Cms.Core.Packaging private XDocument EnsureStorage(out string packagesFile) { var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath); - //ensure it exists Directory.CreateDirectory(packagesFolder); packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile); @@ -740,6 +731,8 @@ namespace Umbraco.Cms.Core.Packaging { var xml = new XDocument(new XElement("packages")); xml.Save(packagesFile); + + return xml; } var packagesXml = XDocument.Load(packagesFile); @@ -749,9 +742,16 @@ namespace Umbraco.Cms.Core.Packaging public void DeleteLocalRepositoryFiles() { var packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile); - File.Delete(packagesFile); + if (File.Exists(packagesFile)) + { + File.Delete(packagesFile); + } + var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath); - Directory.Delete(packagesFolder); + if (Directory.Exists(packagesFolder)) + { + Directory.Delete(packagesFolder); + } } } } diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs index 5312bf6886..3c0b2c4d28 100644 --- a/src/Umbraco.Core/Persistence/Constants-Locks.cs +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -65,6 +65,11 @@ namespace Umbraco.Cms.Core /// All languages. /// public const int Languages = -340; + + /// + /// ScheduledPublishing job. + /// + public const int ScheduledPublishing = -341; } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs new file mode 100644 index 0000000000..f6cd27ad60 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Persistence.Repositories +{ + [Obsolete("This interface will be merged with IMacroRepository in Umbraco 11")] + public interface IMacroWithAliasRepository : IMacroRepository + { + IMacro GetByAlias(string alias); + + IEnumerable GetAllByAlias(string[] aliases); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index cff64d4a79..c863ef4d8e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; diff --git a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs new file mode 100644 index 0000000000..e6ca8eaa50 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Persistence.Repositories +{ + public interface ITrackedReferencesRepository + { + /// + /// Gets a page of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + /// The identifier of the entity to retrieve relations for. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// The total count of the items with reference to the current item. + /// An enumerable list of objects. + IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); + + /// + /// Gets a page of items used in any kind of relation from selected integer ids. + /// + /// The identifiers of the entities to check for relations. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// The total count of the items in any kind of relation. + /// An enumerable list of objects. + IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); + + /// + /// Gets a page of the descending items that have any references, given a parent id. + /// + /// The unique identifier of the parent to retrieve descendants for. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// The total count of descending items. + /// An enumerable list of objects. + IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs index 048ad40ac0..4d88431e7c 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs @@ -1,5 +1,5 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -28,5 +28,17 @@ namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors DefaultConfiguration.Add("minNumber",0 ); DefaultConfiguration.Add("maxNumber", 0); } + + protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute); + + internal class MultipleContentPickerParamateterValueEditor : MultiplePickerParamateterValueEditorBase + { + public MultipleContentPickerParamateterValueEditor(ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) + { + } + + public override string UdiEntityType { get; } = Constants.UdiEntityType.Document; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Document; + } } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs index d8f74b1b28..dfdd6f9b9c 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs @@ -1,5 +1,9 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; +using System; +using System.Collections.Generic; +using System.Reflection.Metadata; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -26,5 +30,17 @@ namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors { DefaultConfiguration.Add("multiPicker", "1"); } + + protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute); + + internal class MultipleMediaPickerPropertyValueEditor : MultiplePickerParamateterValueEditorBase + { + public MultipleMediaPickerPropertyValueEditor(ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) + { + } + + public override string UdiEntityType { get; } = Constants.UdiEntityType.Media; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Media; + } } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs new file mode 100644 index 0000000000..2c4f27b560 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; + +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +{ + internal abstract class MultiplePickerParamateterValueEditorBase : DataValueEditor, IDataValueReference + { + private readonly IEntityService _entityService; + + public MultiplePickerParamateterValueEditorBase( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + IEntityService entityService) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + { + _entityService = entityService; + } + + public abstract string UdiEntityType { get; } + public abstract UmbracoObjectTypes UmbracoObjectType { get; } + public IEnumerable GetReferences(object value) + { + var asString = value is string str ? str : value?.ToString(); + + if (string.IsNullOrEmpty(asString)) + { + yield break; + } + + foreach (var udiStr in asString.Split(',')) + { + if (UdiParser.TryParse(udiStr, out Udi udi)) + { + yield return new UmbracoEntityReference(udi); + } + + // this is needed to support the legacy case when the multiple media picker parameter editor stores ints not udis + if (int.TryParse(udiStr, out var id)) + { + Attempt guidAttempt = _entityService.GetKey(id, UmbracoObjectType); + Guid guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty; + + if (guid != Guid.Empty) + { + yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid)); + } + + } + } + } + } +} diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index 5c27760b2a..eea53aaa9c 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -159,7 +159,7 @@ namespace Umbraco.Cms.Core.Routing : 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 is null || culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase)) + if (domainUri is not null || string.IsNullOrEmpty(culture) || culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase)) { var url = AssembleUrl(domainUri, path, current, mode).ToString(); return UrlInfo.Url(url, culture); diff --git a/src/Umbraco.Core/Runtime/IMainDomKeyGenerator.cs b/src/Umbraco.Core/Runtime/IMainDomKeyGenerator.cs new file mode 100644 index 0000000000..5b8fb819e6 --- /dev/null +++ b/src/Umbraco.Core/Runtime/IMainDomKeyGenerator.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Cms.Core.Runtime +{ + /// + /// Defines a class which can generate a distinct key for a MainDom boundary. + /// + public interface IMainDomKeyGenerator + { + /// + /// Returns a key that signifies a MainDom boundary. + /// + string GenerateKey(); + } +} diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index 08d11db5cd..d22176d9cf 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -87,7 +87,7 @@ namespace Umbraco.Cms.Core.Runtime if (_isMainDom.HasValue == false) { - throw new InvalidOperationException("Register called when MainDom has not been acquired"); + throw new InvalidOperationException("Register called before IsMainDom has been established"); } else if (_isMainDom == false) { @@ -225,7 +225,7 @@ namespace Umbraco.Cms.Core.Runtime { if (!_isMainDom.HasValue) { - throw new InvalidOperationException("MainDom has not been acquired yet"); + throw new InvalidOperationException("IsMainDom has not been established yet"); } return _isMainDom.Value; } diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index ca3045a4de..b8c7596b2d 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -8,12 +8,17 @@ namespace Umbraco.Cms.Core.Security { /// - /// Handles password hashing and formatting for legacy hashing algorithms + /// Handles password hashing and formatting for legacy hashing algorithms. /// + /// + /// Should probably be internal. + /// public class LegacyPasswordSecurity { + // TODO: Remove v11 // Used for tests [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] public string HashPasswordForStorage(string algorithmType, string password) { if (string.IsNullOrWhiteSpace(password)) @@ -24,13 +29,15 @@ namespace Umbraco.Cms.Core.Security return FormatPasswordForStorage(algorithmType, hashed, salt); } + // TODO: Remove v11 // Used for tests [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] public string FormatPasswordForStorage(string algorithmType, string hashedPassword, string salt) { - if (IsLegacySHA1Algorithm(algorithmType)) + if (!SupportHashAlgorithm(algorithmType)) { - return hashedPassword; + throw new InvalidOperationException($"{algorithmType} is not supported"); } return salt + hashedPassword; @@ -45,10 +52,15 @@ namespace Umbraco.Cms.Core.Security /// public bool VerifyPassword(string algorithm, string password, string dbPassword) { - if (string.IsNullOrWhiteSpace(dbPassword)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword)); + if (string.IsNullOrWhiteSpace(dbPassword)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword)); + } if (dbPassword.StartsWith(Constants.Security.EmptyPasswordPrefix)) + { return false; + } try { @@ -61,7 +73,6 @@ namespace Umbraco.Cms.Core.Security //This can happen if the length of the password is wrong and a salt cannot be extracted. return false; } - } /// @@ -69,12 +80,13 @@ namespace Umbraco.Cms.Core.Security /// public bool VerifyLegacyHashedPassword(string password, string dbPassword) { - var hashAlgorith = new HMACSHA1 + var hashAlgorithm = new HMACSHA1 { //the legacy salt was actually the password :( Key = Encoding.Unicode.GetBytes(password) }; - var hashed = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password))); + + var hashed = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password))); return dbPassword == hashed; } @@ -87,6 +99,8 @@ namespace Umbraco.Cms.Core.Security /// /// // TODO: Do we need this method? We shouldn't be using this class to create new password hashes for storage + // TODO: Remove v11 + [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] public string HashNewPassword(string algorithm, string newPassword, out string salt) { salt = GenerateSalt(); @@ -102,15 +116,15 @@ namespace Umbraco.Cms.Core.Security /// public string ParseStoredHashPassword(string algorithm, string storedString, out string salt) { - if (string.IsNullOrWhiteSpace(storedString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString)); - - // This is for the <= v4 hashing algorithm for which there was no salt - if (IsLegacySHA1Algorithm(algorithm)) + if (string.IsNullOrWhiteSpace(storedString)) { - salt = string.Empty; - return storedString; + throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString)); } + if (!SupportHashAlgorithm(algorithm)) + { + throw new InvalidOperationException($"{algorithm} is not supported"); + } var saltLen = GenerateSalt(); salt = storedString.Substring(0, saltLen.Length); @@ -133,12 +147,12 @@ namespace Umbraco.Cms.Core.Security /// private string HashPassword(string algorithmType, string pass, string salt) { - if (IsLegacySHA1Algorithm(algorithmType)) + if (!SupportHashAlgorithm(algorithmType)) { - return HashLegacySHA1Password(pass); + throw new InvalidOperationException($"{algorithmType} is not supported"); } - //This is the correct way to implement this (as per the sql membership provider) + // This is the correct way to implement this (as per the sql membership provider) var bytes = Encoding.Unicode.GetBytes(pass); var saltBytes = Convert.FromBase64String(salt); @@ -209,42 +223,17 @@ namespace Umbraco.Cms.Core.Security { // This is for the v6-v8 hashing algorithm if (algorithm.InvariantEquals(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)) + { return true; + } - // This is for the <= v4 hashing algorithm - if (IsLegacySHA1Algorithm(algorithm)) + // Default validation value for old machine keys (switched to HMACSHA256 aspnet 4 https://docs.microsoft.com/en-us/aspnet/whitepapers/aspnet4/breaking-changes) + if (algorithm.InvariantEquals("SHA1")) + { return true; + } return false; } - - private bool IsLegacySHA1Algorithm(string algorithm) => algorithm.InvariantEquals(Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName); - - /// - /// Hashes the password with the old v4 algorithm - /// - /// The password. - /// The encoded password. - private string HashLegacySHA1Password(string password) - { - using var hashAlgorithm = GetLegacySHA1Algorithm(password); - var hash = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password))); - return hash; - } - - /// - /// Returns the old v4 algorithm and settings - /// - /// - /// - private HashAlgorithm GetLegacySHA1Algorithm(string password) - { - return new HMACSHA1 - { - //the legacy salt was actually the password :( - Key = Encoding.Unicode.GetBytes(password) - }; - } - } } diff --git a/src/Umbraco.Core/Services/IMacroService.cs b/src/Umbraco.Core/Services/IMacroService.cs index c4bc34997f..e1eb97ac00 100644 --- a/src/Umbraco.Core/Services/IMacroService.cs +++ b/src/Umbraco.Core/Services/IMacroService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Cms.Core.Models; @@ -17,13 +17,6 @@ namespace Umbraco.Cms.Core.Services /// An object IMacro GetByAlias(string alias); - ///// - ///// Gets a list all available objects - ///// - ///// Optional array of aliases to limit the results - ///// An enumerable list of objects - //IEnumerable GetAll(params string[] aliases); - IEnumerable GetAll(); IEnumerable GetAll(params int[] ids); diff --git a/src/Umbraco.Core/Services/IMacroWithAliasService.cs b/src/Umbraco.Core/Services/IMacroWithAliasService.cs new file mode 100644 index 0000000000..6e72777bfa --- /dev/null +++ b/src/Umbraco.Core/Services/IMacroWithAliasService.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + [Obsolete("This interface will be merged with IMacroService in Umbraco 11")] + public interface IMacroWithAliasService : IMacroService + { + /// + /// Gets a list of available objects by alias. + /// + /// Optional array of aliases to limit the results + /// An enumerable list of objects + IEnumerable GetAll(params string[] aliases); + } +} diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index ce00f774f8..4d0e977d38 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -215,7 +215,7 @@ namespace Umbraco.Cms.Core.Services /// /// /// - /// + /// An enumerable list of IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes); /// @@ -225,7 +225,7 @@ namespace Umbraco.Cms.Core.Services /// /// /// - /// + /// An enumerable list of IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes); /// diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs new file mode 100644 index 0000000000..dea99c0f6d --- /dev/null +++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs @@ -0,0 +1,38 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + public interface ITrackedReferencesService + { + /// + /// Gets a paged result of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + /// The identifier of the entity to retrieve relations for. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// A paged result of objects. + PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency); + + /// + /// Gets a paged result of the descending items that have any references, given a parent id. + /// + /// The unique identifier of the parent to retrieve descendants for. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// A paged result of objects. + PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency); + + /// + /// Gets a paged result of items used in any kind of relation from selected integer ids. + /// + /// The identifiers of the entities to check for relations. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// A paged result of objects. + PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency); + } +} diff --git a/src/Umbraco.Core/StaticApplicationLogging.cs b/src/Umbraco.Core/StaticApplicationLogging.cs index e216011014..d7dfc8dd9a 100644 --- a/src/Umbraco.Core/StaticApplicationLogging.cs +++ b/src/Umbraco.Core/StaticApplicationLogging.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -5,18 +6,14 @@ namespace Umbraco.Cms.Core { public static class StaticApplicationLogging { - private static ILoggerFactory _loggerFactory; + private static ILoggerFactory s_loggerFactory; - public static void Initialize(ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - } + public static void Initialize(ILoggerFactory loggerFactory) => s_loggerFactory = loggerFactory; public static ILogger Logger => CreateLogger(); - public static ILogger CreateLogger() - { - return _loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); - } + public static ILogger CreateLogger() => s_loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); + + public static ILogger CreateLogger(Type type) => s_loggerFactory?.CreateLogger(type) ?? NullLogger.Instance; } } diff --git a/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs b/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs new file mode 100644 index 0000000000..7fd0ee5a85 --- /dev/null +++ b/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs @@ -0,0 +1,31 @@ +using System; + +namespace Umbraco.Cms.Core.Telemetry +{ + /// + /// Used to get and create the site identifier + /// + public interface ISiteIdentifierService + { + + /// + /// Tries to get the site identifier + /// + /// True if success. + bool TryGetSiteIdentifier(out Guid siteIdentifier); + + /// + /// Creates the site identifier and writes it to config. + /// + /// asd. + /// True if success. + bool TryCreateSiteIdentifier(out Guid createdGuid); + + /// + /// Tries to get the site identifier or otherwise create it if it doesn't exist. + /// + /// The out parameter for the existing or create site identifier. + /// True if success. + bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier); + } +} diff --git a/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs b/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs new file mode 100644 index 0000000000..b6e40665c1 --- /dev/null +++ b/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs @@ -0,0 +1,81 @@ +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; + +namespace Umbraco.Cms.Core.Telemetry +{ + /// + internal class SiteIdentifierService : ISiteIdentifierService + { + private GlobalSettings _globalSettings; + private readonly IConfigManipulator _configManipulator; + private readonly ILogger _logger; + + public SiteIdentifierService( + IOptionsMonitor optionsMonitor, + IConfigManipulator configManipulator, + ILogger logger) + { + _globalSettings = optionsMonitor.CurrentValue; + optionsMonitor.OnChange(globalSettings => _globalSettings = globalSettings); + _configManipulator = configManipulator; + _logger = logger; + } + + /// + public bool TryGetSiteIdentifier(out Guid siteIdentifier) + { + // Parse telemetry string as a GUID & verify its a GUID and not some random string + // since users may have messed with or decided to empty the app setting or put in something random + if (Guid.TryParse(_globalSettings.Id, out var parsedTelemetryId) is false + || parsedTelemetryId == Guid.Empty) + { + siteIdentifier = Guid.Empty; + return false; + } + + siteIdentifier = parsedTelemetryId; + return true; + } + + /// + public bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier) + { + if (TryGetSiteIdentifier(out Guid existingId)) + { + siteIdentifier = existingId; + return true; + } + + if (TryCreateSiteIdentifier(out Guid createdId)) + { + siteIdentifier = createdId; + return true; + } + + siteIdentifier = Guid.Empty; + return false; + } + + /// + public bool TryCreateSiteIdentifier(out Guid createdGuid) + { + createdGuid = Guid.NewGuid(); + + try + { + _configManipulator.SetGlobalId(createdGuid.ToString()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Couldn't update config files with a telemetry site identifier"); + createdGuid = Guid.Empty; + return false; + } + + return true; + } + } +} diff --git a/src/Umbraco.Core/Telemetry/TelemetryService.cs b/src/Umbraco.Core/Telemetry/TelemetryService.cs index 63e4e1ff49..d5a3acac98 100644 --- a/src/Umbraco.Core/Telemetry/TelemetryService.cs +++ b/src/Umbraco.Core/Telemetry/TelemetryService.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Telemetry.Models; using Umbraco.Extensions; @@ -12,27 +10,27 @@ namespace Umbraco.Cms.Core.Telemetry /// internal class TelemetryService : ITelemetryService { - private readonly IOptionsMonitor _globalSettings; private readonly IManifestParser _manifestParser; private readonly IUmbracoVersion _umbracoVersion; + private readonly ISiteIdentifierService _siteIdentifierService; /// /// Initializes a new instance of the class. /// public TelemetryService( - IOptionsMonitor globalSettings, IManifestParser manifestParser, - IUmbracoVersion umbracoVersion) + IUmbracoVersion umbracoVersion, + ISiteIdentifierService siteIdentifierService) { _manifestParser = manifestParser; _umbracoVersion = umbracoVersion; - _globalSettings = globalSettings; + _siteIdentifierService = siteIdentifierService; } /// public bool TryGetTelemetryReportData(out TelemetryReportData telemetryReportData) { - if (TryGetTelemetryId(out Guid telemetryId) is false) + if (_siteIdentifierService.TryGetOrCreateSiteIdentifier(out Guid telemetryId) is false) { telemetryReportData = null; return false; @@ -42,28 +40,14 @@ namespace Umbraco.Cms.Core.Telemetry { Id = telemetryId, Version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(), - Packages = GetPackageTelemetry() + Packages = GetPackageTelemetry(), }; return true; } - private bool TryGetTelemetryId(out Guid telemetryId) - { - // Parse telemetry string as a GUID & verify its a GUID and not some random string - // since users may have messed with or decided to empty the app setting or put in something random - if (Guid.TryParse(_globalSettings.CurrentValue.Id, out var parsedTelemetryId) is false) - { - telemetryId = Guid.Empty; - return false; - } - - telemetryId = parsedTelemetryId; - return true; - } - private IEnumerable GetPackageTelemetry() { - List packages = new (); + List packages = new(); IEnumerable manifests = _manifestParser.GetManifests(); foreach (PackageManifest manifest in manifests) @@ -76,7 +60,7 @@ namespace Umbraco.Cms.Core.Telemetry packages.Add(new PackageTelemetry { Name = manifest.PackageName, - Version = manifest.Version ?? string.Empty + Version = manifest.Version ?? string.Empty, }); } diff --git a/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs b/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs index ba3945c6e5..33230a296d 100644 --- a/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs +++ b/src/Umbraco.Examine.Lucene/LuceneIndexDiagnostics.cs @@ -27,7 +27,11 @@ namespace Umbraco.Cms.Infrastructure.Examine IOptionsMonitor indexOptions) { _hostingEnvironment = hostingEnvironment; - _indexOptions = indexOptions.Get(index.Name); + if (indexOptions != null) + { + _indexOptions = indexOptions.Get(index.Name); + + } Index = index; Logger = logger; } @@ -35,7 +39,7 @@ namespace Umbraco.Cms.Infrastructure.Examine public LuceneIndex Index { get; } public ILogger Logger { get; } - + public Attempt IsHealthy() { @@ -72,12 +76,12 @@ namespace Umbraco.Cms.Infrastructure.Examine { d[nameof(LuceneDirectoryIndexOptions.DirectoryFactory)] = _indexOptions.DirectoryFactory.GetType(); } - + if (_indexOptions.IndexDeletionPolicy != null) { d[nameof(LuceneDirectoryIndexOptions.IndexDeletionPolicy)] = _indexOptions.IndexDeletionPolicy.GetType(); - } - + } + } return d; diff --git a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs index f9cc387929..d72f17d559 100644 --- a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs +++ b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs @@ -2,26 +2,45 @@ using System; using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Core.Configuration { public class JsonConfigManipulator : IConfigManipulator { private readonly IConfiguration _configuration; + private readonly ILogger _logger; private readonly object _locker = new object(); - public JsonConfigManipulator(IConfiguration configuration) => _configuration = configuration; + [Obsolete] + public JsonConfigManipulator(IConfiguration configuration) + : this(configuration, StaticServiceProvider.Instance.GetRequiredService>()) + { } - public string UmbracoConnectionPath { get; } = $"ConnectionStrings:{ Cms.Core.Constants.System.UmbracoConnectionName}"; + public JsonConfigManipulator( + IConfiguration configuration, + ILogger logger) + { + _configuration = configuration; + _logger = logger; + } + + public string UmbracoConnectionPath { get; } = $"ConnectionStrings:{Cms.Core.Constants.System.UmbracoConnectionName}"; public void RemoveConnectionString() { var provider = GetJsonConfigurationProvider(UmbracoConnectionPath); var json = GetJson(provider); + if (json is null) + { + _logger.LogWarning("Failed to remove connection string from JSON configuration."); + return; + } RemoveJsonKey(json, UmbracoConnectionPath); @@ -33,6 +52,11 @@ namespace Umbraco.Cms.Core.Configuration var provider = GetJsonConfigurationProvider(); var json = GetJson(provider); + if (json is null) + { + _logger.LogWarning("Failed to save connection string in JSON configuration."); + return; + } var item = GetConnectionItem(connectionString, providerName); @@ -47,6 +71,11 @@ namespace Umbraco.Cms.Core.Configuration var provider = GetJsonConfigurationProvider(); var json = GetJson(provider); + if (json is null) + { + _logger.LogWarning("Failed to save configuration key \"{Key}\" in JSON configuration.", key); + return; + } JToken token = json; foreach (var propertyName in key.Split(new[] { ':' })) @@ -73,6 +102,11 @@ namespace Umbraco.Cms.Core.Configuration var provider = GetJsonConfigurationProvider(); var json = GetJson(provider); + if (json is null) + { + _logger.LogWarning("Failed to save enabled/disabled state for redirect URL tracking in JSON configuration."); + return; + } var item = GetDisableRedirectUrlItem(disable); @@ -86,6 +120,11 @@ namespace Umbraco.Cms.Core.Configuration var provider = GetJsonConfigurationProvider(); var json = GetJson(provider); + if (json is null) + { + _logger.LogWarning("Failed to save global identifier in JSON configuration."); + return; + } var item = GetGlobalIdItem(id); @@ -170,13 +209,20 @@ namespace Umbraco.Cms.Core.Configuration { var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path); - using (var sw = new StreamWriter(jsonFilePath, false)) - using (var jsonTextWriter = new JsonTextWriter(sw) + try { - Formatting = Formatting.Indented, - }) + using (var sw = new StreamWriter(jsonFilePath, false)) + using (var jsonTextWriter = new JsonTextWriter(sw) + { + Formatting = Formatting.Indented, + }) + { + json?.WriteTo(jsonTextWriter); + } + } + catch (IOException exception) { - json?.WriteTo(jsonTextWriter); + _logger.LogWarning(exception, "JSON configuration could not be written: {path}", jsonFilePath); } } } @@ -186,19 +232,25 @@ namespace Umbraco.Cms.Core.Configuration { lock (_locker) { - if (provider.Source.FileProvider is PhysicalFileProvider physicalFileProvider) + if (provider.Source.FileProvider is not PhysicalFileProvider physicalFileProvider) { - var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path); - - var serializer = new JsonSerializer(); - using (var sr = new StreamReader(jsonFilePath)) - using (var jsonTextReader = new JsonTextReader(sr)) - { - return serializer.Deserialize(jsonTextReader); - } + return null; } - return null; + var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path); + + try + { + var serializer = new JsonSerializer(); + using var sr = new StreamReader(jsonFilePath); + using var jsonTextReader = new JsonTextReader(sr); + return serializer.Deserialize(jsonTextReader); + } + catch (IOException exception) + { + _logger.LogWarning(exception, "JSON configuration could not be read: {path}", jsonFilePath); + return null; + } } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index f4a4866beb..c4b9a6367c 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -218,6 +218,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection private static IUmbracoBuilder AddMainDom(this IUmbracoBuilder builder) { + builder.Services.AddSingleton(); builder.Services.AddSingleton(factory => { var globalSettings = factory.GetRequiredService>(); @@ -229,15 +230,20 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var loggerFactory = factory.GetRequiredService(); var npocoMappers = factory.GetRequiredService(); + var mainDomKeyGenerator = factory.GetRequiredService(); + + if (globalSettings.Value.MainDomLock == "FileSystemMainDomLock") + { + return new FileSystemMainDomLock(loggerFactory.CreateLogger(), mainDomKeyGenerator, hostingEnvironment, factory.GetRequiredService>()); + } return globalSettings.Value.MainDomLock.Equals("SqlMainDomLock") || isWindows == false ? (IMainDomLock)new SqlMainDomLock( - loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbCreator, - hostingEnvironment, + mainDomKeyGenerator, databaseSchemaCreatorFactory, npocoMappers) : new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs index e0958bfdb7..d750eb15e0 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs @@ -1,7 +1,10 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Install.InstallSteps; using Umbraco.Cms.Core.Install.Models; +using Umbraco.Cms.Core.Telemetry; using Umbraco.Cms.Infrastructure.Install; using Umbraco.Cms.Infrastructure.Install.InstallSteps; using Umbraco.Extensions; @@ -19,7 +22,12 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddScoped(provider => + { + return new TelemetryIdentifierStep( + provider.GetRequiredService>(), + provider.GetRequiredService()); + }); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index f9dc43cbd5..13196c1879 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Extensions; @@ -47,6 +46,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 90d08b93fe..157d49fd39 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -18,9 +18,9 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; -using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Services.Implement; +using Umbraco.Cms.Infrastructure.Templates; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DependencyInjection @@ -63,6 +63,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(); @@ -91,6 +92,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddSingleton(); builder.Services.AddUnique(); + builder.Services.AddUnique(); return builder; } diff --git a/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs b/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs index 4222ac800e..b06248c79e 100644 --- a/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs +++ b/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs @@ -65,7 +65,7 @@ namespace Umbraco.Cms.Core.Events var documentObjectType = Constants.ObjectTypes.Document; const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName; - relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); + relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType, false); _relationService.Save(relationType); } @@ -123,7 +123,7 @@ namespace Umbraco.Cms.Core.Events { var documentObjectType = Constants.ObjectTypes.Document; const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName; - relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); + relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType, false); _relationService.Save(relationType); } diff --git a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs index 5f3aba5f3f..d037c91d86 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs @@ -32,7 +32,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IContentVersionService service, IMainDom mainDom, IServerRoleAccessor serverRoleAccessor) - : base(TimeSpan.FromHours(1), TimeSpan.FromMinutes(3)) + : base(logger, TimeSpan.FromHours(1), TimeSpan.FromMinutes(3)) { _runtimeState = runtimeState; _logger = logger; diff --git a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs index 6a0828fad3..e6d8e75304 100644 --- a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs @@ -61,6 +61,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IProfilingLogger profilingLogger, ICronTabParser cronTabParser) : base( + logger, healthChecksSettings.Value.Notification.Period, healthChecksSettings.Value.GetNotificationDelay(cronTabParser, DateTime.Now, DefaultDelay)) { diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs index b564e7948d..3233cfa8f2 100644 --- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs +++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs @@ -7,6 +7,7 @@ using System.Net.Http; 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.Hosting; using Umbraco.Cms.Core.Logging; @@ -48,7 +49,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IProfilingLogger profilingLogger, IServerRoleAccessor serverRegistrar, IHttpClientFactory httpClientFactory) - : base(TimeSpan.FromMinutes(5), DefaultDelay) + : base(logger, TimeSpan.FromMinutes(5), DefaultDelay) { _hostingEnvironment = hostingEnvironment; _mainDom = mainDom; @@ -99,7 +100,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices try { var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl); - HttpClient httpClient = _httpClientFactory.CreateClient(); + HttpClient httpClient = _httpClientFactory.CreateClient(Constants.HttpClients.IgnoreCertificateErrors); _ = await httpClient.SendAsync(request); } catch (Exception ex) diff --git a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs index 27d9c29e8d..79c1c4b8ea 100644 --- a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs +++ b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IScopeProvider scopeProvider, ILogger logger, IProfilingLogger profilingLogger) - : base(TimeSpan.FromHours(4), DefaultDelay) + : base(logger, TimeSpan.FromHours(4), DefaultDelay) { _mainDom = mainDom; _serverRegistrar = serverRegistrar; diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index b6f2b469f6..5247a125bc 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -5,6 +5,8 @@ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -21,25 +23,39 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// protected static readonly TimeSpan DefaultDelay = TimeSpan.FromMinutes(3); + private readonly ILogger _logger; private TimeSpan _period; private readonly TimeSpan _delay; private Timer _timer; + private bool _disposedValue; /// /// Initializes a new instance of the class. /// - /// Timepsan representing how often the task should recur. - /// Timespan represeting the initial delay after application start-up before the first run of the task occurs. - protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) + /// Logger. + /// Timespan representing how often the task should recur. + /// Timespan representing the initial delay after application start-up before the first run of the task occurs. + protected RecurringHostedServiceBase(ILogger logger, TimeSpan period, TimeSpan delay) { + _logger = logger; _period = period; _delay = delay; } + // Scheduled for removal in V11 + [Obsolete("Please use constructor that takes an ILogger instead")] + protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) + : this(null, period, delay) + { } + /// public Task StartAsync(CancellationToken cancellationToken) { - _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); + using (!ExecutionContext.IsFlowSuppressed() ? (IDisposable)ExecutionContext.SuppressFlow() : null) + { + _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); + } + return Task.CompletedTask; } @@ -60,6 +76,11 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Hat-tip: https://stackoverflow.com/a/14207615/489433 await PerformExecuteAsync(state); } + catch (Exception ex) + { + ILogger logger = _logger ?? StaticApplicationLogging.CreateLogger(GetType()); + logger.LogError(ex, "Unhandled exception in recurring hosted service."); + } finally { // Resume now that the task is complete - Note we use period in both because we don't want to execute again after the delay. @@ -78,7 +99,24 @@ namespace Umbraco.Cms.Infrastructure.HostedServices return Task.CompletedTask; } + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _timer?.Dispose(); + } + + _disposedValue = true; + } + } + /// - public void Dispose() => _timer?.Dispose(); + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs index 7591290bf4..6e5d412e71 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs @@ -23,7 +23,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices public ReportSiteTask( ILogger logger, ITelemetryService telemetryService) - : base(TimeSpan.FromDays(1), TimeSpan.FromMinutes(1)) + : base(logger, TimeSpan.FromDays(1), TimeSpan.FromMinutes(1)) { _logger = logger; _telemetryService = telemetryService; @@ -59,9 +59,6 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Send data to LIVE telemetry s_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); - // Set a low timeout - no need to use a larger default timeout for this POST request - s_httpClient.Timeout = new TimeSpan(0, 0, 1); - #if DEBUG // Send data to DEBUG telemetry service s_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); diff --git a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs index d59ea4fad3..fd70c05fc1 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs @@ -5,13 +5,15 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Runtime; -using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -27,20 +29,16 @@ namespace Umbraco.Cms.Infrastructure.HostedServices private readonly IMainDom _mainDom; private readonly IRuntimeState _runtimeState; private readonly IServerMessenger _serverMessenger; + private readonly IScopeProvider _scopeProvider; private readonly IServerRoleAccessor _serverRegistrar; private readonly IUmbracoContextFactory _umbracoContextFactory; /// /// Initializes a new instance of the class. /// - /// Representation of the state of the Umbraco runtime. - /// Representation of the main application domain. - /// Provider of server registrations to the distributed cache. - /// Service for handling content operations. - /// Service for creating and managing Umbraco context. - /// The typed logger. - /// Service broadcasting cache notifications to registered servers. - /// Creates and manages instances. + // Note: Ignoring the two version notice rule as this class should probably be internal. + // We don't expect anyone downstream to be instantiating a HostedService + [Obsolete("This constructor will be removed in version 10, please use an alternative constructor.")] public ScheduledPublishing( IRuntimeState runtimeState, IMainDom mainDom, @@ -49,7 +47,31 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger) - : base(TimeSpan.FromMinutes(1), DefaultDelay) + : this( + runtimeState, + mainDom, + serverRegistrar, + contentService, + umbracoContextFactory, + logger, + serverMessenger, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + /// + /// Initializes a new instance of the class. + /// + public ScheduledPublishing( + IRuntimeState runtimeState, + IMainDom mainDom, + IServerRoleAccessor serverRegistrar, + IContentService contentService, + IUmbracoContextFactory umbracoContextFactory, + ILogger logger, + IServerMessenger serverMessenger, + IScopeProvider scopeProvider) + : base(logger, TimeSpan.FromMinutes(1), DefaultDelay) { _runtimeState = runtimeState; _mainDom = mainDom; @@ -58,6 +80,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices _umbracoContextFactory = umbracoContextFactory; _logger = logger; _serverMessenger = serverMessenger; + _scopeProvider = scopeProvider; } public override Task PerformExecuteAsync(object state) @@ -93,8 +116,6 @@ namespace Umbraco.Cms.Infrastructure.HostedServices try { - // We don't need an explicit scope here because PerformScheduledPublish creates it's own scope - // so it's safe as it will create it's own ambient scope. // Ensure we run with an UmbracoContext, because this will run in a background task, // and developers may be using the UmbracoContext in the event handlers. @@ -105,6 +126,14 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // - and we should definitively *not* have to flush it here (should be auto) using UmbracoContextReference contextReference = _umbracoContextFactory.EnsureUmbracoContext(); + using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + + /* We used to assume that there will never be two instances running concurrently where (IsMainDom && ServerRole == SchedulingPublisher) + * However this is possible during an azure deployment slot swap for the SchedulingPublisher instance when trying to achieve zero downtime deployments. + * If we take a distributed write lock, we are certain that the multiple instances of the job will not run in parallel. + * It's possible that during the swapping process we may run this job more frequently than intended but this is not of great concern and it's + * only until the old SchedulingPublisher shuts down. */ + scope.EagerWriteLock(Constants.Locks.ScheduledPublishing); try { // Run diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs index 57354aafdb..3aa49f3f71 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs @@ -20,6 +20,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration private readonly IRuntimeState _runtimeState; private readonly IServerMessenger _messenger; private readonly ILogger _logger; + private bool _disposedValue; /// /// Initializes a new instance of the class. @@ -29,7 +30,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration /// The typed logger. /// The configuration for global settings. public InstructionProcessTask(IRuntimeState runtimeState, IServerMessenger messenger, ILogger logger, IOptions globalSettings) - : base(globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) + : base(logger, globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) { _runtimeState = runtimeState; _messenger = messenger; @@ -54,5 +55,20 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration return Task.CompletedTask; } + + protected override void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing && _messenger is IDisposable disposable) + { + disposable.Dispose(); + } + + _disposedValue = true; + } + + base.Dispose(disposing); + } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index d54d67338e..5f20a3654e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -44,7 +44,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration ILogger logger, IOptions globalSettings, IServerRoleAccessor serverRoleAccessor) - : base(globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) + : base(logger, globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) { _runtimeState = runtimeState; _serverRegistrationService = serverRegistrationService ?? throw new ArgumentNullException(nameof(serverRegistrationService)); diff --git a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs index e59cca5fbd..8a2a312455 100644 --- a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs @@ -33,7 +33,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// Representation of the main application domain. /// The typed logger. public TempFileCleanup(IIOHelper ioHelper, IMainDom mainDom, ILogger logger) - : base(TimeSpan.FromMinutes(60), DefaultDelay) + : base(logger, TimeSpan.FromMinutes(60), DefaultDelay) { _ioHelper = ioHelper; _mainDom = mainDom; diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index 4f2ef1f2e9..b19802996b 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -175,6 +175,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Cms.Core.Constants.Locks.Domains, Name = "Domains" }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Cms.Core.Constants.Locks.KeyValues, Name = "KeyValues" }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Cms.Core.Constants.Locks.Languages, Name = "Languages" }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Cms.Core.Constants.Locks.ScheduledPublishing, Name = "ScheduledPublishing" }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Cms.Core.Constants.Locks.MainDom, Name = "MainDom" }); } @@ -420,21 +421,21 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install private void CreateRelationTypeData() { - var relationType = new RelationTypeDto { Id = 1, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = true, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; + var relationType = new RelationTypeDto { Id = 1, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = true, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, IsDependency = false}; relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - relationType = new RelationTypeDto { Id = 2, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; + relationType = new RelationTypeDto { Id = 2, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName, IsDependency = false }; relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - relationType = new RelationTypeDto { Id = 3, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Media, ParentObjectType = Cms.Core.Constants.ObjectTypes.Media, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName }; + relationType = new RelationTypeDto { Id = 3, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Media, ParentObjectType = Cms.Core.Constants.ObjectTypes.Media, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName, IsDependency = false }; relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - relationType = new RelationTypeDto { Id = 4, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaName }; + relationType = new RelationTypeDto { Id = 4, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaName, IsDependency = true }; relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - relationType = new RelationTypeDto { Id = 5, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentName }; + relationType = new RelationTypeDto { Id = 5, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentName, IsDependency = true }; relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 39d7d886b3..d3a920a60b 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -16,6 +16,7 @@ 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.Cms.Infrastructure.Migrations.Upgrade.V_9_3_0; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_4_0; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade @@ -280,6 +281,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade To("{CA7A1D9D-C9D4-4914-BC0A-459E7B9C3C8C}"); To("{0828F206-DCF7-4F73-ABBB-6792275532EB}"); + // TO 9.4.0 + To("{DBBA1EA0-25A1-4863-90FB-5D306FB6F1E1}"); + To("{DED98755-4059-41BB-ADBD-3FEAB12D1D7B}"); } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_4_0/AddScheduledPublishingLock.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_4_0/AddScheduledPublishingLock.cs new file mode 100644 index 0000000000..01cfb22a3d --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_4_0/AddScheduledPublishingLock.cs @@ -0,0 +1,15 @@ +using Umbraco.Cms.Infrastructure.Persistence.Dtos; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_4_0 +{ + internal class AddScheduledPublishingLock : MigrationBase + { + public AddScheduledPublishingLock(IMigrationContext context) + : base(context) + { + } + + protected override void Migrate() => + Database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Cms.Core.Constants.Locks.ScheduledPublishing, Name = "ScheduledPublishing" }); + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_4_0/UpdateRelationTypesToHandleDependencies.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_4_0/UpdateRelationTypesToHandleDependencies.cs new file mode 100644 index 0000000000..1c8fe7ed72 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_4_0/UpdateRelationTypesToHandleDependencies.cs @@ -0,0 +1,34 @@ +using System.Linq; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_4_0 +{ + internal class UpdateRelationTypesToHandleDependencies : MigrationBase + { + public UpdateRelationTypesToHandleDependencies(IMigrationContext context) + : base(context) + { + } + + protected override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); + + AddColumnIfNotExists(columns, "isDependency"); + + var aliasesWithDependencies = new[] + { + Core.Constants.Conventions.RelationTypes.RelatedDocumentAlias, + Core.Constants.Conventions.RelationTypes.RelatedMediaAlias + }; + + Database.Execute( + Sql() + .Update(u => u.Set(x => x.IsDependency, true)) + .WhereIn(x => x.Alias, aliasesWithDependencies)); + + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/RelationTypeDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/RelationTypeDto.cs index 50d7960ff8..388fa58941 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/RelationTypeDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/RelationTypeDto.cs @@ -40,5 +40,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos [Length(100)] [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")] public string Alias { get; set; } + + [Constraint(Default = "0")] + [Column("isDependency")] + public bool IsDependency { get; set; } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/RelationTypeFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/RelationTypeFactory.cs index 51f5261199..93cb74cd74 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/RelationTypeFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/RelationTypeFactory.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories public static IRelationType BuildEntity(RelationTypeDto dto) { - var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ParentObjectType, dto.ChildObjectType); + var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ParentObjectType, dto.ChildObjectType, dto.IsDependency); try { @@ -30,11 +30,17 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories public static RelationTypeDto BuildDto(IRelationType entity) { + var isDependency = false; + if (entity is IRelationTypeWithIsDependency relationTypeWithIsDependency) + { + isDependency = relationTypeWithIsDependency.IsDependency; + } var dto = new RelationTypeDto { Alias = entity.Alias, ChildObjectType = entity.ChildObjectType, Dual = entity.IsBidirectional, + IsDependency = isDependency, Name = entity.Name, ParentObjectType = entity.ParentObjectType, UniqueId = entity.Key @@ -47,6 +53,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories return dto; } + + #endregion } } diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs index 965a659631..732563fef7 100644 --- a/src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs +++ b/src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs @@ -22,6 +22,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Mappers DefineMap(nameof(RelationType.Alias), nameof(RelationTypeDto.Alias)); DefineMap(nameof(RelationType.ChildObjectType), nameof(RelationTypeDto.ChildObjectType)); DefineMap(nameof(RelationType.IsBidirectional), nameof(RelationTypeDto.Dual)); + DefineMap(nameof(RelationType.IsDependency), nameof(RelationTypeDto.IsDependency)); DefineMap(nameof(RelationType.Name), nameof(RelationTypeDto.Name)); DefineMap(nameof(RelationType.ParentObjectType), nameof(RelationTypeDto.ParentObjectType)); } diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs index 05f15f7372..47cca58ce2 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs @@ -72,7 +72,27 @@ namespace Umbraco.Extensions /// The Sql statement. public static Sql WhereIn(this Sql sql, Expression> field, Sql values) { - return sql.WhereIn(field, values, false); + return WhereIn(sql, field, values, false, null); + } + + public static Sql WhereIn(this Sql sql, Expression> field, Sql values, string tableAlias) + { + return sql.WhereIn(field, values, false, tableAlias); + } + + + public static Sql WhereLike(this Sql sql, Expression> fieldSelector, Sql valuesSql) + { + var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector); + sql.Where(fieldName + " LIKE (" + valuesSql.SQL + ")", valuesSql.Arguments); + return sql; + } + + public static Sql WhereLike(this Sql sql, Expression> fieldSelector, string likeValue) + { + var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector); + sql.Where(fieldName + " LIKE ('" + likeValue + "')"); + return sql; } /// @@ -130,7 +150,12 @@ namespace Umbraco.Extensions private static Sql WhereIn(this Sql sql, Expression> fieldSelector, Sql valuesSql, bool not) { - var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector); + return WhereIn(sql, fieldSelector, valuesSql, not, null); + } + + private static Sql WhereIn(this Sql sql, Expression> fieldSelector, Sql valuesSql, bool not, string tableAlias) + { + var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector, tableAlias); sql.Where(fieldName + (not ? " NOT" : "") +" IN (" + valuesSql.SQL + ")", valuesSql.Arguments); return sql; } @@ -252,7 +277,7 @@ namespace Umbraco.Extensions /// The Sql statement. public static Sql OrderByDescending(this Sql sql, Expression> field) { - return sql.OrderBy("(" + sql.SqlContext.SqlSyntax.GetFieldName(field) + ") DESC"); + return sql.OrderByDescending(sql.SqlContext.SqlSyntax.GetFieldName(field)); } /// @@ -268,7 +293,7 @@ namespace Umbraco.Extensions var columns = fields.Length == 0 ? sql.GetColumns(withAlias: false) : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray(); - return sql.OrderBy(columns.Select(x => x + " DESC")); + return sql.OrderByDescending(columns); } /// @@ -634,6 +659,12 @@ namespace Umbraco.Extensions return sql; } + public static Sql SelectDistinct(this Sql sql, params object[] columns) + { + sql.Append("SELECT DISTINCT " + string.Join(", ", columns)); + return sql; + } + //this.Append("SELECT " + string.Join(", ", columns), new object[0]); /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index be1a31c2c9..b705bcabca 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -39,7 +39,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement private readonly IMacroService _macroService; private readonly IContentTypeService _contentTypeService; private readonly string _tempFolderPath; - private readonly string _mediaFolderPath; + private readonly string _createdPackagesFolderPath; /// /// Initializes a new instance of the class. @@ -76,9 +76,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement _macroService = macroService; _contentTypeService = contentTypeService; _xmlParser = new PackageDefinitionXmlParser(); - _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages); - _tempFolderPath = - tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; + _createdPackagesFolderPath = mediaFolderPath ?? Constants.SystemDirectories.CreatedPackages; + _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData + "/PackageFiles"; } public IEnumerable GetAll() @@ -192,17 +191,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public string ExportPackage(PackageDefinition definition) { - // Ensure it's valid ValidatePackage(definition); // Create a folder for building this package - var temporaryPath = - _hostingEnvironment.MapPathContentRoot(_tempFolderPath.EnsureEndsWith('/') + Guid.NewGuid()); - if (Directory.Exists(temporaryPath) == false) - { - Directory.CreateDirectory(temporaryPath); - } + var temporaryPath = _hostingEnvironment.MapPathContentRoot(Path.Combine(_tempFolderPath, Guid.NewGuid().ToString())); + Directory.CreateDirectory(temporaryPath); try { @@ -218,8 +212,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement PackageTemplates(definition, root); PackageStylesheets(definition, root); PackageStaticFiles(definition.Scripts, root, "Scripts", "Script", _fileSystems.ScriptsFileSystem); - PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View", - _fileSystems.PartialViewsFileSystem); + PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View", _fileSystems.PartialViewsFileSystem); PackageMacros(definition, root); PackageDictionaryItems(definition, root); PackageLanguages(definition, root); @@ -265,27 +258,25 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } } - var directoryName = - _hostingEnvironment.MapPathWebRoot( - Path.Combine(_mediaFolderPath, definition.Name.Replace(' ', '_'))); - - if (Directory.Exists(directoryName) == false) - { - Directory.CreateDirectory(directoryName); - } + var directoryName = _hostingEnvironment.MapPathContentRoot(Path.Combine(_createdPackagesFolderPath, definition.Name.Replace(' ', '_'))); + Directory.CreateDirectory(directoryName); var finalPackagePath = Path.Combine(directoryName, fileName); - if (File.Exists(finalPackagePath)) + // Clean existing files + foreach (var packagePath in new[] { - File.Delete(finalPackagePath); - } - - if (File.Exists(finalPackagePath.Replace("zip", "xml"))) - { - File.Delete(finalPackagePath.Replace("zip", "xml")); + definition.PackagePath, + finalPackagePath + }) + { + if (File.Exists(packagePath)) + { + File.Delete(packagePath); + } } + // Move to final package path File.Move(tempPackagePath, finalPackagePath); definition.PackagePath = finalPackagePath; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs index 535895e8ed..21638027ea 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs @@ -18,14 +18,16 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { - internal class MacroRepository : EntityRepositoryBase, IMacroRepository + internal class MacroRepository : EntityRepositoryBase, IMacroWithAliasRepository { private readonly IShortStringHelper _shortStringHelper; + private readonly IRepositoryCachePolicy _macroByAliasCachePolicy; public MacroRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IShortStringHelper shortStringHelper) : base(scopeAccessor, cache, logger) { _shortStringHelper = shortStringHelper; + _macroByAliasCachePolicy = new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); } protected override IMacro PerformGet(int id) @@ -68,6 +70,38 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return Get(id) != null; } + public IMacro GetByAlias(string alias) + { + return _macroByAliasCachePolicy.Get(alias, PerformGetByAlias, PerformGetAllByAlias); + } + + public IEnumerable GetAllByAlias(string[] aliases) + { + if (aliases.Any() is false) + { + return base.GetMany(); + } + + return _macroByAliasCachePolicy.GetAll(aliases, PerformGetAllByAlias); + } + + private IMacro PerformGetByAlias(string alias) + { + var query = Query().Where(x => x.Alias.Equals(alias)); + return PerformGetByQuery(query).FirstOrDefault(); + } + + private IEnumerable PerformGetAllByAlias(params string[] aliases) + { + if (aliases.Any() is false) + { + return base.GetMany(); + } + + var query = Query().Where(x => aliases.Contains(x.Alias)); + return PerformGetByQuery(query); + } + protected override IEnumerable PerformGetAll(params int[] ids) { return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 6ab29aa47e..5e9a8413b4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -60,14 +60,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var urlHash = url.GenerateHash(); Sql sql = GetBaseQuery(false) .Where(x => x.Url == url && x.UrlHash == urlHash && - (x.Culture == culture.ToLower() || x.Culture == string.Empty)) + (x.Culture == culture.ToLower() || x.Culture == null || 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); + dto = dtos.FirstOrDefault(f => string.IsNullOrWhiteSpace(f.Culture)); } return dto == null ? null : Map(dto); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs index 749fc9d77b..7ba20d1db5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core; @@ -171,6 +172,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } public IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes) + { + return GetPagedParentEntitiesByChildId(childId, pageIndex, pageSize, out totalRecords, new int[0], entityTypes); + } + + public IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, int[] relationTypes, params Guid[] entityTypes) { // var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member } // we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data @@ -184,10 +190,20 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement sql.Where(rel => rel.ChildId == childId); sql.Where((rel, node) => rel.ParentId == childId || node.NodeId != childId); + + if (relationTypes != null && relationTypes.Any()) + { + sql.WhereIn(rel => rel.RelationType, relationTypes); + } }); } public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes) + { + return GetPagedChildEntitiesByParentId(parentId, pageIndex, pageSize, out totalRecords, new int[0], entityTypes); + } + + public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, int[] relationTypes, params Guid[] entityTypes) { // var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member } // we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data @@ -201,6 +217,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement sql.Where(rel => rel.ParentId == parentId); sql.Where((rel, node) => rel.ChildId == parentId || node.NodeId != parentId); + + if (relationTypes != null && relationTypes.Any()) + { + sql.WhereIn(rel => rel.RelationType, relationTypes); + } }); } @@ -399,4 +420,40 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement sql.OrderByDescending(orderBy); } } + + internal class RelationItemDto + { + [Column(Name = "nodeId")] + public int ChildNodeId { get; set; } + + [Column(Name = "nodeKey")] + public Guid ChildNodeKey { get; set; } + + [Column(Name = "nodeName")] + public string ChildNodeName { get; set; } + + [Column(Name = "nodeObjectType")] + public Guid ChildNodeObjectType { get; set; } + + [Column(Name = "contentTypeIcon")] + public string ChildContentTypeIcon { get; set; } + + [Column(Name = "contentTypeAlias")] + public string ChildContentTypeAlias { get; set; } + + [Column(Name = "contentTypeName")] + public string ChildContentTypeName { get; set; } + + [Column(Name = "relationTypeName")] + public string RelationTypeName { get; set; } + + [Column(Name = "relationTypeAlias")] + public string RelationTypeAlias { get; set; } + + [Column(Name = "relationTypeIsDependency")] + public bool RelationTypeIsDependency { get; set; } + + [Column(Name = "relationTypeIsBidirectional")] + public bool RelationTypeIsBidirectional { get; set; } + } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs new file mode 100644 index 0000000000..0e70d47cbf --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +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 TrackedReferencesRepository : ITrackedReferencesRepository + { + private readonly IScopeAccessor _scopeAccessor; + + public TrackedReferencesRepository(IScopeAccessor scopeAccessor) + { + _scopeAccessor = scopeAccessor; + } + + /// + /// Gets a page of items used in any kind of relation from selected integer ids. + /// + public IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) + { + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( + "[pn].[id] as nodeId", + "[pn].[uniqueId] as nodeKey", + "[pn].[text] as nodeName", + "[pn].[nodeObjectType] as nodeObjectType", + "[ct].[icon] as contentTypeIcon", + "[ct].[alias] as contentTypeAlias", + "[ctn].[text] as contentTypeName", + "[umbracoRelationType].[alias] as relationTypeAlias", + "[umbracoRelationType].[name] as relationTypeName", + "[umbracoRelationType].[isDependency] as relationTypeIsDependency", + "[umbracoRelationType].[dual] as relationTypeIsBidirectional") + .From("r") + .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") + .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") + .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", aliasRight: "c") + .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") + .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn"); + + if (ids.Any()) + { + sql = sql.Where(x => ids.Contains(x.NodeId), "pn"); + } + + if (filterMustBeIsDependency) + { + sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType"); + } + + // Ordering is required for paging + sql = sql.OrderBy(x => x.Alias); + + var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); + totalRecords = pagedResult.TotalItems; + + return pagedResult.Items.Select(MapDtoToEntity); + } + + /// + /// Gets a page of the descending items that have any references, given a parent id. + /// + public IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) + { + var syntax = _scopeAccessor.AmbientScope.Database.SqlContext.SqlSyntax; + + // Gets the path of the parent with ",%" added + var subsubQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() + .Select(syntax.GetConcat("[node].[path]", "',%'")) + .From("node") + .Where(x => x.NodeId == parentId, "node"); + + // Gets the descendants of the parent node + Sql subQuery; + + if (_scopeAccessor.AmbientScope.Database.DatabaseType.IsSqlCe()) + { + // SqlCE does not support nested selects that returns a scalar. So we need to do this in multiple queries + + var pathForLike = _scopeAccessor.AmbientScope.Database.ExecuteScalar(subsubQuery); + + subQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() + .Select(x => x.NodeId) + .From() + .WhereLike(x => x.Path, pathForLike); + } + else + { + subQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() + .Select(x => x.NodeId) + .From() + .WhereLike(x => x.Path, subsubQuery); + } + + // Get all relations where parent is in the sub query + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( + "[pn].[id] as nodeId", + "[pn].[uniqueId] as nodeKey", + "[pn].[text] as nodeName", + "[pn].[nodeObjectType] as nodeObjectType", + "[ct].[icon] as contentTypeIcon", + "[ct].[alias] as contentTypeAlias", + "[ctn].[text] as contentTypeName", + "[umbracoRelationType].[alias] as relationTypeAlias", + "[umbracoRelationType].[name] as relationTypeName", + "[umbracoRelationType].[isDependency] as relationTypeIsDependency", + "[umbracoRelationType].[dual] as relationTypeIsBidirectional") + .From("r") + .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") + .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") + .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", aliasRight: "c") + .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") + .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn") + .WhereIn((System.Linq.Expressions.Expression>)(x => x.NodeId), subQuery, "pn"); + + if (filterMustBeIsDependency) + { + sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType"); + } + + // Ordering is required for paging + sql = sql.OrderBy(x => x.Alias); + + var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); + totalRecords = pagedResult.TotalItems; + + return pagedResult.Items.Select(MapDtoToEntity); + } + + /// + /// Gets a page of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + public IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) + { + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( + "[cn].[id] as nodeId", + "[cn].[uniqueId] as nodeKey", + "[cn].[text] as nodeName", + "[cn].[nodeObjectType] as nodeObjectType", + "[ct].[icon] as contentTypeIcon", + "[ct].[alias] as contentTypeAlias", + "[ctn].[text] as contentTypeName", + "[umbracoRelationType].[alias] as relationTypeAlias", + "[umbracoRelationType].[name] as relationTypeName", + "[umbracoRelationType].[isDependency] as relationTypeIsDependency", + "[umbracoRelationType].[dual] as relationTypeIsBidirectional") + .From("r") + .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") + .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") + .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "cn", aliasRight: "c") + .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") + .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn") + .Where(x => x.NodeId == id, "pn"); + + if (filterMustBeIsDependency) + { + sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType"); + } + + // Ordering is required for paging + sql = sql.OrderBy(x => x.Alias); + + var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); + totalRecords = pagedResult.TotalItems; + + return pagedResult.Items.Select(MapDtoToEntity); + } + + private RelationItem MapDtoToEntity(RelationItemDto dto) + { + return new RelationItem() + { + NodeId = dto.ChildNodeId, + NodeKey = dto.ChildNodeKey, + NodeType = ObjectTypes.GetUdiType(dto.ChildNodeObjectType), + NodeName = dto.ChildNodeName, + RelationTypeName = dto.RelationTypeName, + RelationTypeIsBidirectional = dto.RelationTypeIsBidirectional, + RelationTypeIsDependency = dto.RelationTypeIsDependency, + ContentTypeAlias = dto.ChildContentTypeAlias, + ContentTypeIcon = dto.ChildContentTypeIcon, + ContentTypeName = dto.ChildContentTypeName, + }; + } + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs index f149757919..c3d8be8f50 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; @@ -14,6 +15,8 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Templates; +using Umbraco.Cms.Infrastructure.Templates; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -37,6 +40,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly RichTextEditorPastedImages _pastedImages; private readonly HtmlLocalLinkParser _localLinkParser; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlMacroParameterParser _macroParameterParser; public GridPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -45,7 +49,8 @@ namespace Umbraco.Cms.Core.PropertyEditors RichTextEditorPastedImages pastedImages, HtmlLocalLinkParser localLinkParser, IIOHelper ioHelper, - IImageUrlGenerator imageUrlGenerator) + IImageUrlGenerator imageUrlGenerator, + IHtmlMacroParameterParser macroParameterParser) : base(dataValueEditorFactory) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -54,6 +59,20 @@ namespace Umbraco.Cms.Core.PropertyEditors _pastedImages = pastedImages; _localLinkParser = localLinkParser; _imageUrlGenerator = imageUrlGenerator; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")] + public GridPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser, + IIOHelper ioHelper, + IImageUrlGenerator imageUrlGenerator) + : this (dataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, pastedImages, localLinkParser, ioHelper, imageUrlGenerator, StaticServiceProvider.Instance.GetRequiredService()) + { } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -74,6 +93,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly RichTextPropertyEditor.RichTextPropertyValueEditor _richTextPropertyValueEditor; private readonly MediaPickerPropertyEditor.MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlMacroParameterParser _macroParameterParser; public GridPropertyValueEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -85,7 +105,8 @@ namespace Umbraco.Cms.Core.PropertyEditors IShortStringHelper shortStringHelper, IImageUrlGenerator imageUrlGenerator, IJsonSerializer jsonSerializer, - IIOHelper ioHelper) + IIOHelper ioHelper, + IHtmlMacroParameterParser macroParameterParser) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -96,6 +117,25 @@ namespace Umbraco.Cms.Core.PropertyEditors _mediaPickerPropertyValueEditor = dataValueEditorFactory.Create(attribute); _imageUrlGenerator = imageUrlGenerator; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")] + public GridPropertyValueEditor( + IDataValueEditorFactory dataValueEditorFactory, + DataEditorAttribute attribute, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + ILocalizedTextService localizedTextService, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + IShortStringHelper shortStringHelper, + IImageUrlGenerator imageUrlGenerator, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper) + : this (dataValueEditorFactory, attribute, backOfficeSecurityAccessor, localizedTextService, + imageSourceParser, pastedImages, shortStringHelper, imageUrlGenerator, jsonSerializer, ioHelper, + StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -120,7 +160,7 @@ namespace Umbraco.Cms.Core.PropertyEditors var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - var grid = DeserializeGridValue(rawJson, out var rtes, out _); + var grid = DeserializeGridValue(rawJson, out var rtes, out _, out _); var userId = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser?.Id ?? Constants.Security.SuperUserId; @@ -154,7 +194,7 @@ namespace Umbraco.Cms.Core.PropertyEditors if (val.IsNullOrWhiteSpace()) return string.Empty; - var grid = DeserializeGridValue(val, out var rtes, out _); + var grid = DeserializeGridValue(val, out var rtes, out _, out _); //process the rte values foreach (var rte in rtes.ToList()) @@ -168,7 +208,7 @@ namespace Umbraco.Cms.Core.PropertyEditors return grid; } - private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues, out IEnumerable mediaValues) + private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues, out IEnumerable mediaValues, out IEnumerable macroValues) { var grid = JsonConvert.DeserializeObject(rawJson); @@ -177,6 +217,9 @@ namespace Umbraco.Cms.Core.PropertyEditors richTextValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "rte"); mediaValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "media"); + // Find all the macros + macroValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "macro"); + return grid; } @@ -192,7 +235,7 @@ namespace Umbraco.Cms.Core.PropertyEditors if (rawJson.IsNullOrWhiteSpace()) yield break; - DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues); + DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues, out var macroValues); foreach (var umbracoEntityReference in richTextEditorValues.SelectMany(x => _richTextPropertyValueEditor.GetReferences(x.Value))) @@ -201,6 +244,9 @@ namespace Umbraco.Cms.Core.PropertyEditors foreach (var umbracoEntityReference in mediaValues.Where(x => x.Value.HasValues) .SelectMany(x => _mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"]))) yield return umbracoEntityReference; + + foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromGridControlMacros(macroValues)) + yield return umbracoEntityReference; } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 1cfbc3449e..18c3fe0902 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -3,8 +3,7 @@ using System; using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; @@ -16,6 +15,8 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Templates; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Cms.Infrastructure.Macros; +using Umbraco.Cms.Infrastructure.Templates; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -36,12 +37,13 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; + private readonly IHtmlMacroParameterParser _macroParameterParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IIOHelper _ioHelper; private readonly IImageUrlGenerator _imageUrlGenerator; /// - /// The constructor will setup the property editor based on the attribute if one is found + /// The constructor will setup the property editor based on the attribute if one is found. /// public RichTextPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -50,7 +52,8 @@ namespace Umbraco.Cms.Core.PropertyEditors HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IIOHelper ioHelper, - IImageUrlGenerator imageUrlGenerator) + IImageUrlGenerator imageUrlGenerator, + IHtmlMacroParameterParser macroParameterParser) : base(dataValueEditorFactory) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -59,6 +62,20 @@ namespace Umbraco.Cms.Core.PropertyEditors _pastedImages = pastedImages; _ioHelper = ioHelper; _imageUrlGenerator = imageUrlGenerator; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")] + public RichTextPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IIOHelper ioHelper, + IImageUrlGenerator imageUrlGenerator) + : this (dataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, localLinkParser, pastedImages, ioHelper, imageUrlGenerator, StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -79,6 +96,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; + private readonly IHtmlMacroParameterParser _macroParameterParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IImageUrlGenerator _imageUrlGenerator; private readonly IHtmlSanitizer _htmlSanitizer; @@ -94,7 +112,8 @@ namespace Umbraco.Cms.Core.PropertyEditors IImageUrlGenerator imageUrlGenerator, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - IHtmlSanitizer htmlSanitizer) + IHtmlSanitizer htmlSanitizer, + IHtmlMacroParameterParser macroParameterParser) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -103,6 +122,26 @@ namespace Umbraco.Cms.Core.PropertyEditors _pastedImages = pastedImages; _imageUrlGenerator = imageUrlGenerator; _htmlSanitizer = htmlSanitizer; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an HtmlMacroParameterParser instead")] + public RichTextPropertyValueEditor( + DataEditorAttribute attribute, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IImageUrlGenerator imageUrlGenerator, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + IHtmlSanitizer htmlSanitizer) + : this(attribute, backOfficeSecurityAccessor, localizedTextService, shortStringHelper, imageSourceParser, + localLinkParser, pastedImages, imageUrlGenerator, jsonSerializer, ioHelper, htmlSanitizer, + StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -182,6 +221,10 @@ namespace Umbraco.Cms.Core.PropertyEditors yield return new UmbracoEntityReference(udi); //TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs + //UPDATE: We are getting the Macros in 'FindUmbracoEntityReferencesFromEmbeddedMacros' - perhaps we just return the macro Udis here too or do they need their own relationAlias? + + foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromEmbeddedMacros(asString)) + yield return umbracoEntityReference; } } diff --git a/src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs index 7f99b32b02..2ef2034d3d 100644 --- a/src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs +++ b/src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs @@ -99,25 +99,32 @@ namespace Umbraco.Cms.Core.Routing { return; } - var contentCache = publishedSnapshot.Content; - var entityContent = contentCache?.GetById(entity.Id); - if (entityContent == null) + + IPublishedContentCache contentCache = publishedSnapshot.Content; + IPublishedContent entityContent = contentCache?.GetById(entity.Id); + if (entityContent is null) + { return; + } // get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures) var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray() ?? new[] { (string)null }; - foreach (var x in entityContent.DescendantsOrSelf(_variationContextAccessor)) + + foreach (IPublishedContent publishedContent in entityContent.DescendantsOrSelf(_variationContextAccessor)) { // if this entity defines specific cultures, use those instead of the default ones - var cultures = x.Cultures.Any() ? x.Cultures.Keys : defaultCultures; + IEnumerable cultures = publishedContent.Cultures.Any() ? publishedContent.Cultures.Keys : defaultCultures; foreach (var culture in cultures) { - var route = contentCache.GetRouteById(x.Id, culture); + var route = contentCache.GetRouteById(publishedContent.Id, culture); if (IsNotRoute(route)) + { continue; - oldRoutes[new ContentIdAndCulture(x.Id, culture)] = new ContentKeyAndOldRoute(x.Key, route); + } + + oldRoutes[new ContentIdAndCulture(publishedContent.Id, culture)] = new ContentKeyAndOldRoute(publishedContent.Key, route); } } } @@ -135,13 +142,16 @@ namespace Umbraco.Cms.Core.Routing { _logger.LogWarning("Could not track redirects because there is no current published snapshot available."); return; - } + } - foreach (var oldRoute in oldRoutes) + foreach (KeyValuePair oldRoute in oldRoutes) { var newRoute = contentCache.GetRouteById(oldRoute.Key.ContentId, oldRoute.Key.Culture); if (IsNotRoute(newRoute) || oldRoute.Value.OldRoute == newRoute) + { continue; + } + _redirectUrlService.Register(oldRoute.Value.OldRoute, oldRoute.Value.ContentKey, oldRoute.Key.Culture); } } diff --git a/src/Umbraco.Infrastructure/Runtime/DefaultMainDomKeyGenerator.cs b/src/Umbraco.Infrastructure/Runtime/DefaultMainDomKeyGenerator.cs new file mode 100644 index 0000000000..11944e776c --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/DefaultMainDomKeyGenerator.cs @@ -0,0 +1,34 @@ +using System; +using System.Security.Cryptography; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Runtime +{ + + internal class DefaultMainDomKeyGenerator : IMainDomKeyGenerator + { + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IOptionsMonitor _globalSettings; + + public DefaultMainDomKeyGenerator(IHostingEnvironment hostingEnvironment, IOptionsMonitor globalSettings) + { + _hostingEnvironment = hostingEnvironment; + _globalSettings = globalSettings; + } + + public string GenerateKey() + { + var machineName = Environment.MachineName; + var mainDomId = MainDom.GetMainDomId(_hostingEnvironment); + var discriminator = _globalSettings.CurrentValue.MainDomKeyDiscriminator; + + var rawKey = $"{machineName}{mainDomId}{discriminator}"; + + return rawKey.GenerateHash(); + } + } +} diff --git a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs new file mode 100644 index 0000000000..c4cbcef588 --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs @@ -0,0 +1,122 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Runtime; + +namespace Umbraco.Cms.Infrastructure.Runtime +{ + internal class FileSystemMainDomLock : IMainDomLock + { + private readonly ILogger _logger; + private readonly IOptionsMonitor _globalSettings; + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private readonly string _lockFilePath; + private readonly string _releaseSignalFilePath; + + private FileStream _lockFileStream; + private Task _listenForReleaseSignalFileTask; + + public FileSystemMainDomLock( + ILogger logger, + IMainDomKeyGenerator mainDomKeyGenerator, + IHostingEnvironment hostingEnvironment, + IOptionsMonitor globalSettings) + { + _logger = logger; + _globalSettings = globalSettings; + + var lockFileName = $"MainDom_{mainDomKeyGenerator.GenerateKey()}.lock"; + _lockFilePath = Path.Combine(hostingEnvironment.LocalTempPath, lockFileName); + _releaseSignalFilePath = $"{_lockFilePath}_release"; + } + + public Task AcquireLockAsync(int millisecondsTimeout) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + do + { + try + { + _logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath); + _lockFileStream = File.Open(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + DeleteLockReleaseSignalFile(); + return Task.FromResult(true); + } + catch (IOException) + { + _logger.LogDebug("Couldn't obtain MainDom lock file handle, signalling for release of {lockFilePath}", _lockFilePath); + CreateLockReleaseSignalFile(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected exception attempting to obtain MainDom lock file handle {lockFilePath}, giving up", _lockFilePath); + _lockFileStream?.Close(); + return Task.FromResult(false); + } + } + while (stopwatch.ElapsedMilliseconds < millisecondsTimeout); + + return Task.FromResult(false); + } + + public void CreateLockReleaseSignalFile() => + File.Open(_releaseSignalFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete) + .Close(); + + public void DeleteLockReleaseSignalFile() => + File.Delete(_releaseSignalFilePath); + + // Create a long running task to poll to check if anyone has created a lock release file. + public Task ListenAsync() + { + if (_listenForReleaseSignalFileTask != null) + { + return _listenForReleaseSignalFileTask; + } + + _listenForReleaseSignalFileTask = Task.Factory.StartNew( + ListeningLoop, + _cancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Default); + + return _listenForReleaseSignalFileTask; + } + + private void ListeningLoop() + { + while (true) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + _logger.LogDebug("ListenAsync Task canceled, exiting loop"); + return; + } + + if (File.Exists(_releaseSignalFilePath)) + { + _logger.LogDebug("Found lock release signal file, releasing lock on {lockFilePath}", _lockFilePath); + _lockFileStream?.Close(); + _lockFileStream = null; + break; + } + + Thread.Sleep(_globalSettings.CurrentValue.MainDomReleaseSignalPollingInterval); + } + } + + public void Dispose() + { + _lockFileStream?.Close(); + _lockFileStream = null; + } + } +} diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs index 8d1c74b619..8a6698b92a 100644 --- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NPoco; @@ -18,6 +19,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; using MapperCollection = Umbraco.Cms.Infrastructure.Persistence.Mappers.MapperCollection; @@ -30,7 +32,6 @@ namespace Umbraco.Cms.Infrastructure.Runtime private const string UpdatedSuffix = "_updated"; private readonly ILogger _logger; private readonly IOptions _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; private readonly IUmbracoDatabase _db; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private SqlServerSyntaxProvider _sqlServerSyntax; @@ -41,6 +42,9 @@ namespace Umbraco.Cms.Infrastructure.Runtime private bool _hasTable = false; private bool _acquireWhenTablesNotAvailable = false; + // Note: Ignoring the two version notice rule as this class should probably be internal. + // We don't expect anyone downstream to be instantiating a SqlMainDomLock, only resolving IMainDomLock + [Obsolete("This constructor will be removed in version 10, please use an alternative constructor.")] public SqlMainDomLock( ILogger logger, ILoggerFactory loggerFactory, @@ -51,25 +55,20 @@ namespace Umbraco.Cms.Infrastructure.Runtime DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory, NPocoMapperCollection npocoMappers, string connectionStringName) - { - // unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer - _lockId = Guid.NewGuid().ToString(); - _logger = logger; - _globalSettings = globalSettings; - _sqlServerSyntax = new SqlServerSyntaxProvider(_globalSettings); - _hostingEnvironment = hostingEnvironment; - _dbFactory = new UmbracoDatabaseFactory( - loggerFactory.CreateLogger(), + : this( loggerFactory, - _globalSettings, - new MapperCollection(() => Enumerable.Empty()), + globalSettings, + connectionStrings, dbProviderFactoryCreator, + StaticServiceProvider.Instance.GetRequiredService(), databaseSchemaCreatorFactory, - npocoMappers, - connectionStringName); - MainDomKey = MainDomKeyPrefix + "-" + (Environment.MachineName + MainDom.GetMainDomId(_hostingEnvironment)).GenerateHash(); + npocoMappers) + { } + // Note: Ignoring the two version notice rule as this class should probably be internal. + // We don't expect anyone downstream to be instantiating a SqlMainDomLock, only resolving IMainDomLock + [Obsolete("This constructor will be removed in version 10, please use an alternative constructor.")] public SqlMainDomLock( ILogger logger, ILoggerFactory loggerFactory, @@ -80,18 +79,42 @@ namespace Umbraco.Cms.Infrastructure.Runtime DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory, NPocoMapperCollection npocoMappers) : this( - logger, loggerFactory, globalSettings, connectionStrings, dbProviderFactoryCreator, - hostingEnvironment, + StaticServiceProvider.Instance.GetRequiredService(), databaseSchemaCreatorFactory, - npocoMappers, - connectionStrings.CurrentValue.UmbracoConnectionString.ConnectionString - ) + npocoMappers) { + } + public SqlMainDomLock( + ILoggerFactory loggerFactory, + IOptions globalSettings, + IOptionsMonitor connectionStrings, + IDbProviderFactoryCreator dbProviderFactoryCreator, + IMainDomKeyGenerator mainDomKeyGenerator, + DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory, + NPocoMapperCollection npocoMappers) + { + // unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer + _lockId = Guid.NewGuid().ToString(); + _logger = loggerFactory.CreateLogger(); + _globalSettings = globalSettings; + _sqlServerSyntax = new SqlServerSyntaxProvider(_globalSettings); + + _dbFactory = new UmbracoDatabaseFactory( + loggerFactory.CreateLogger(), + loggerFactory, + _globalSettings, + new MapperCollection(() => Enumerable.Empty()), + dbProviderFactoryCreator, + databaseSchemaCreatorFactory, + npocoMappers, + connectionStrings.CurrentValue.UmbracoConnectionString.ConnectionString); + + MainDomKey = MainDomKeyPrefix + "-" + mainDomKeyGenerator.GenerateKey(); } public async Task AcquireLockAsync(int millisecondsTimeout) @@ -213,7 +236,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime { // poll every couple of seconds // local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO - Thread.Sleep(2000); + Thread.Sleep(_globalSettings.Value.MainDomReleaseSignalPollingInterval); if (!_dbFactory.Configured) { diff --git a/src/Umbraco.Infrastructure/Security/ClaimsIdentityExtensions.cs b/src/Umbraco.Infrastructure/Security/ClaimsIdentityExtensions.cs index de5b6206fc..9e11916223 100644 --- a/src/Umbraco.Infrastructure/Security/ClaimsIdentityExtensions.cs +++ b/src/Umbraco.Infrastructure/Security/ClaimsIdentityExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. + using System.Linq; using System.Security.Claims; using Umbraco.Cms.Core; @@ -11,7 +12,8 @@ namespace Umbraco.Extensions { // Ignore these Claims when merging, these claims are dynamically added whenever the ticket // is re-issued and we don't want to merge old values of these. - private static readonly string[] s_ignoredClaims = new[] { ClaimTypes.CookiePath, Constants.Security.SessionIdClaimType }; + // We do however want to merge these when the SecurityStampValidator refreshes the principal since it's still the same login session + private static readonly string[] s_ignoredClaims = { ClaimTypes.CookiePath, Constants.Security.SessionIdClaimType }; public static void MergeAllClaims(this ClaimsIdentity destination, ClaimsIdentity source) { diff --git a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs index e470bf0a6c..02aef30217 100644 --- a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs +++ b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs @@ -71,6 +71,7 @@ namespace Umbraco.Cms.Core.Security return result; } } + // We need to check for clear text passwords from members as the first thing. This was possible in v8 :( else if (IsSuccessfulLegacyPassword(hashedPassword, providedPassword)) { @@ -138,7 +139,7 @@ namespace Umbraco.Cms.Core.Security } var result = LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName, providedPassword, hashedPassword); - return result || LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName, providedPassword, hashedPassword); + return result || LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword); } private static string DecryptLegacyPassword(string encryptedPassword, string algorithmName, string decryptionKey) diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 420d66b0b4..345a404fcf 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -181,6 +181,7 @@ namespace Umbraco.Cms.Core.Security { // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(MemberIdentityUser.Logins)); + var isTokensPropertyDirty = user.IsPropertyDirty(nameof(MemberIdentityUser.LoginTokens)); MemberDataChangeType memberChangeType = UpdateMemberProperties(found, user); if (memberChangeType == MemberDataChangeType.FullSave) @@ -203,6 +204,16 @@ namespace Umbraco.Cms.Core.Security x.ProviderKey, x.UserData))); } + + if (isTokensPropertyDirty) + { + _externalLoginService.Save( + found.Key, + user.LoginTokens.Select(x => new ExternalLoginToken( + x.LoginProvider, + x.Name, + x.Value))); + } } return Task.FromResult(IdentityResult.Success); @@ -535,6 +546,37 @@ namespace Umbraco.Cms.Core.Security return found; } + /// + /// Overridden to support Umbraco's own data storage requirements + /// + /// + /// The base class's implementation of this calls into FindTokenAsync and AddUserTokenAsync, both methods will only work with ORMs that are change + /// tracking ORMs like EFCore. + /// + /// + public override Task SetTokenAsync(MemberIdentityUser user, string loginProvider, string name, string value, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + IIdentityUserToken token = user.LoginTokens.FirstOrDefault(x => x.LoginProvider.InvariantEquals(loginProvider) && x.Name.InvariantEquals(name)); + if (token == null) + { + user.LoginTokens.Add(new IdentityUserToken(loginProvider, name, value, user.Id)); + } + else + { + token.Value = value; + } + + return Task.CompletedTask; + } + private MemberIdentityUser AssignLoginsCallback(MemberIdentityUser user) { if (user != null) diff --git a/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs index da08bc8713..2847f13dc4 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Models.Membership; @@ -10,7 +11,6 @@ namespace Umbraco.Cms.Core.Security where TUser: UmbracoIdentityUser { private readonly IJsonSerializer _jsonSerializer; - private readonly PasswordHasher _aspnetV2PasswordHasher = new PasswordHasher(new V2PasswordHasherOptions()); public UmbracoPasswordHasher(LegacyPasswordSecurity legacyPasswordSecurity, IJsonSerializer jsonSerializer) { @@ -43,57 +43,64 @@ namespace Umbraco.Cms.Core.Security { if (user is null) { - throw new System.ArgumentNullException(nameof(user)); + throw new ArgumentNullException(nameof(user)); } - if (!user.PasswordConfig.IsNullOrWhiteSpace()) + try { - // check if the (legacy) password security supports this hash algorith and if so then use it - var deserialized = _jsonSerializer.Deserialize(user.PasswordConfig); - if (LegacyPasswordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm)) + // Best case and most likely scenario, a modern hash supported by ASP.Net identity. + PasswordVerificationResult upstreamResult = base.VerifyHashedPassword(user, hashedPassword, providedPassword); + if (upstreamResult != PasswordVerificationResult.Failed) { - var result = LegacyPasswordSecurity.VerifyPassword(deserialized.HashAlgorithm, providedPassword, hashedPassword); - - //We need to special handle this case, apparently v8 still saves the user algorithm as {"hashAlgorithm":"HMACSHA256"}, when using legacy encoding and hasinging. - if (result == false) - { - result = LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword); - } - - return result - ? PasswordVerificationResult.SuccessRehashNeeded - : PasswordVerificationResult.Failed; - } - - // We will explicitly detect names here - // The default is PBKDF2.ASPNETCORE.V3: - // PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. - // The underlying class only lets us change 2 things which is the version: options.CompatibilityMode and the iteration count - // The PBKDF2.ASPNETCORE.V2 settings are: - // PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations. - - switch (deserialized.HashAlgorithm) - { - case Constants.Security.AspNetCoreV3PasswordHashAlgorithmName: - return base.VerifyHashedPassword(user, hashedPassword, providedPassword); - case Constants.Security.AspNetCoreV2PasswordHashAlgorithmName: - var legacyResult = _aspnetV2PasswordHasher.VerifyHashedPassword(user, hashedPassword, providedPassword); - if (legacyResult == PasswordVerificationResult.Success) - return PasswordVerificationResult.SuccessRehashNeeded; - return legacyResult; + return upstreamResult; } } - - // else go the default (v3) - return base.VerifyHashedPassword(user, hashedPassword, providedPassword); - } - - private class V2PasswordHasherOptions : IOptions - { - public PasswordHasherOptions Value => new PasswordHasherOptions + catch (FormatException) { - CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2 - }; + // hash wasn't a valid base64 encoded string, MS concat the salt bytes and hash bytes and base 64 encode both together. + // We however historically base 64 encoded the salt bytes and hash bytes separately then concat the strings so we got 2 sets of padding. + // both salt bytes and hash bytes lengths were not evenly divisible by 3 hence 2 sets of padding. + + // We could check upfront with TryFromBase64String, but not whilst we target netstandard 2.0 + // so might as well just deal with the exception. + } + + // At this point we either have a legacy password or a bad attempt. + + // Check the supported worst case scenario, a "useLegacyEncoding" password - HMACSHA1 but with password used as key so not unique for users sharing same password + // This was the standard for v4. + // Do this first because with useLegacyEncoding the algorithm stored in the database is irrelevant. + if (LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword)) + { + return PasswordVerificationResult.SuccessRehashNeeded; + } + + // For users we expect to know the historic algorithm. + // NOTE: MemberPasswordHasher subclasses this class to deal with the fact that PasswordConfig wasn't stored. + if (user.PasswordConfig.IsNullOrWhiteSpace()) + { + return PasswordVerificationResult.Failed; + } + + PersistedPasswordSettings deserialized; + try + { + deserialized = _jsonSerializer.Deserialize(user.PasswordConfig); + } + catch + { + return PasswordVerificationResult.Failed; + } + + if (!LegacyPasswordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm)) + { + return PasswordVerificationResult.Failed; + } + + // Last chance must be HMACSHA256 or SHA1 + return LegacyPasswordSecurity.VerifyPassword(deserialized.HashAlgorithm, providedPassword, hashedPassword) + ? PasswordVerificationResult.SuccessRehashNeeded + : PasswordVerificationResult.Failed; } } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs b/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs index a037cd1095..b4af98ad0a 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs @@ -247,6 +247,13 @@ namespace Umbraco.Cms.Core.Services.Implement /// private bool TryDeserializeInstructions(CacheInstruction instruction, out JArray jsonInstructions) { + if (instruction.Instructions is null) + { + _logger.LogError("Failed to deserialize instructions ({DtoId}: 'null').", instruction.Id); + jsonInstructions = null; + return false; + } + try { jsonInstructions = JsonConvert.DeserializeObject(instruction.Instructions); diff --git a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceFileSources.cs b/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceFileSources.cs index 5dac893cf4..9c393b1308 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceFileSources.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceFileSources.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; diff --git a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs index a79d9fddce..a1d556d805 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs @@ -13,13 +13,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// Represents the Macro Service, which is an easy access to operations involving /// - internal class MacroService : RepositoryService, IMacroService + internal class MacroService : RepositoryService, IMacroWithAliasService { private readonly IMacroRepository _macroRepository; private readonly IAuditRepository _auditRepository; - public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IMacroRepository macroRepository, IAuditRepository auditRepository) + public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMacroRepository macroRepository, IAuditRepository auditRepository) : base(provider, loggerFactory, eventMessagesFactory) { _macroRepository = macroRepository; @@ -33,10 +32,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// An object public IMacro GetByAlias(string alias) { + if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) + { + return GetAll().FirstOrDefault(x => x.Alias == alias); + } + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - var q = Query().Where(x => x.Alias == alias); - return _macroRepository.Get(q).FirstOrDefault(); + return macroWithAliasRepository.GetByAlias(alias); } } @@ -61,6 +64,20 @@ namespace Umbraco.Cms.Core.Services.Implement } } + public IEnumerable GetAll(params string[] aliases) + { + if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) + { + var hashset = new HashSet(aliases); + return GetAll().Where(x => hashset.Contains(x.Alias)); + } + + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + return macroWithAliasRepository.GetAllByAlias(aliases); + } + } + public IMacro GetById(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) diff --git a/src/Umbraco.Infrastructure/Services/Implement/RelationService.cs b/src/Umbraco.Infrastructure/Services/Implement/RelationService.cs index 7a5d10c222..d52411a9c8 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/RelationService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/RelationService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; diff --git a/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs b/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs new file mode 100644 index 0000000000..ec22e1095c --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs @@ -0,0 +1,59 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; + +namespace Umbraco.Cms.Core.Services.Implement +{ + public class TrackedReferencesService : ITrackedReferencesService + { + private readonly ITrackedReferencesRepository _trackedReferencesRepository; + private readonly IScopeProvider _scopeProvider; + private readonly IEntityService _entityService; + + public TrackedReferencesService(ITrackedReferencesRepository trackedReferencesRepository, IScopeProvider scopeProvider, IEntityService entityService) + { + _trackedReferencesRepository = trackedReferencesRepository; + _scopeProvider = scopeProvider; + _entityService = entityService; + } + + /// + /// Gets a paged result of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + public PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency) + { + using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + var items = _trackedReferencesRepository.GetPagedRelationsForItem(id, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); + + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; + } + + /// + /// Gets a paged result of items used in any kind of relation from selected integer ids. + /// + public PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency) + { + using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + var items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); + + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; + } + + /// + /// Gets a paged result of the descending items that have any references, given a parent id. + /// + public PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency) + { + using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + + var items = _trackedReferencesRepository.GetPagedDescendantsInReferences( + parentId, + pageIndex, + pageSize, + filterMustBeIsDependency, + out var totalItems); + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; + } + } +} diff --git a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs index 10556b7fe6..5f11d1578a 100644 --- a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Infrastructure.Sync /// /// An that works by storing messages in the database. /// - public abstract class DatabaseServerMessenger : ServerMessengerBase + public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable { /* * this messenger writes ALL instructions to the database, @@ -39,6 +39,7 @@ namespace Umbraco.Cms.Infrastructure.Sync private DateTime _lastPruned; private readonly Lazy _initialized; private bool _syncing; + private bool _disposedValue; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly CancellationToken _cancellationToken; @@ -280,6 +281,28 @@ namespace Umbraco.Cms.Infrastructure.Sync } } + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _syncIdle?.Dispose(); + } + + _disposedValue = true; + } + } + + /// + /// Dispose + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + #endregion } } diff --git a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs new file mode 100644 index 0000000000..6323139137 --- /dev/null +++ b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Macros; + +namespace Umbraco.Cms.Infrastructure.Templates +{ + public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser + { + private readonly IMacroService _macroService; + private readonly ILogger _logger; + private readonly ParameterEditorCollection _parameterEditors; + + public HtmlMacroParameterParser(IMacroService macroService, ILogger logger, ParameterEditorCollection parameterEditors) + { + _macroService = macroService; + _logger = logger; + _parameterEditors = parameterEditors; + } + + /// + /// Parses out media UDIs from an HTML string based on embedded macro parameter values. + /// + /// HTML string + /// + public IEnumerable FindUmbracoEntityReferencesFromEmbeddedMacros(string text) + { + // There may be more than one macro with the same alias on the page so using a tuple + var foundMacros = new List>>(); + + // This legacy ParseMacros() already finds the macros within a Rich Text Editor using regexes + // It seems to lowercase the macro parameter alias - so making the dictionary case insensitive + MacroTagParser.ParseMacros(text, textblock => { }, (macroAlias, macroAttributes) => foundMacros.Add(new Tuple>(macroAlias, new Dictionary(macroAttributes, StringComparer.OrdinalIgnoreCase)))); + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros)) + { + yield return umbracoEntityReference; + } + } + + /// + /// Parses out media UDIs from Macro Grid Control parameters. + /// + /// + /// + public IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroGridControls) + { + var foundMacros = new List>>(); + + foreach (var macroGridControl in macroGridControls) + { + // Deserialise JSON of Macro Grid Control to a class + var gridMacro = macroGridControl.Value.ToObject(); + // Collect any macro parameters that contain the media udi format + if (gridMacro is not null && gridMacro.MacroParameters is not null && gridMacro.MacroParameters.Any()) + { + foundMacros.Add(new Tuple>(gridMacro.MacroAlias, gridMacro.MacroParameters)); + } + } + + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros)) + { + yield return umbracoEntityReference; + } + } + + private IEnumerable GetUmbracoEntityReferencesFromMacros(List>> macros) + { + + if (_macroService is not IMacroWithAliasService macroWithAliasService) + { + yield break; + } + + var uniqueMacroAliases = macros.Select(f => f.Item1).Distinct(); + // TODO: Tracking Macro references + // Here we are finding the used macros' Udis (there should be a Related Macro relation type - but Relations don't accept 'Macro' as an option) + var foundMacroUmbracoEntityReferences = new List(); + // Get all the macro configs in one hit for these unique macro aliases - this is now cached with a custom cache policy + var macroConfigs = macroWithAliasService.GetAll(uniqueMacroAliases.ToArray()); + + foreach (var macro in macros) + { + var macroConfig = macroConfigs.FirstOrDefault(f => f.Alias == macro.Item1); + if (macroConfig is null) + { + continue; + } + foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key))); + // Only do this if the macros actually have parameters + if (macroConfig.Properties is not null && macroConfig.Properties.Keys.Any(f => f != "macroAlias")) + { + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(macro.Item2, macroConfig, _parameterEditors)) + { + yield return umbracoEntityReference; + } + } + } + } + + /// + /// Finds media UDIs in Macro Parameter Values by calling the GetReference method for all the Macro Parameter Editors for a particular macro. + /// + /// The parameters for the macro a dictionary of key/value strings + /// The macro configuration for this particular macro - contains the types of editors used for each parameter + /// A list of all the registered parameter editors used in the Umbraco implmentation - to look up the corresponding property editor for a macro parameter + /// + private IEnumerable GetUmbracoEntityReferencesFromMacroParameters(Dictionary macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors) + { + var foundUmbracoEntityReferences = new List(); + foreach (var parameter in macroConfig.Properties) + { + if (macroParameters.TryGetValue(parameter.Alias, out string parameterValue)) + { + var parameterEditorAlias = parameter.EditorAlias; + // Lookup propertyEditor from the registered ParameterEditors with the implmementation to avoid looking up for each parameter + var parameterEditor = parameterEditors.FirstOrDefault(f => string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase)); + if (parameterEditor is not null) + { + // Get the ParameterValueEditor for this PropertyEditor (where the GetReferences method is implemented) - cast as IDataValueReference to determine if 'it is' implemented for the editor + if (parameterEditor.GetValueEditor() is IDataValueReference parameterValueEditor) + { + foreach (var entityReference in parameterValueEditor.GetReferences(parameterValue)) + { + foundUmbracoEntityReferences.Add(entityReference); + } + } + else + { + _logger.LogInformation("{0} doesn't have a ValueEditor that implements IDataValueReference", parameterEditor.Alias); + } + } + } + } + + return foundUmbracoEntityReferences; + } + + // Poco class to deserialise the Json for a Macro Control + private class GridMacro + { + [JsonProperty("macroAlias")] + public string MacroAlias { get; set; } + + [JsonProperty("macroParamsDictionary")] + public Dictionary MacroParameters { get; set; } + } + } +} diff --git a/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs new file mode 100644 index 0000000000..6e484cc30a --- /dev/null +++ b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; + +namespace Umbraco.Cms.Infrastructure.Templates +{ + /// + /// Provides methods to parse referenced entities as Macro parameters. + /// + public interface IHtmlMacroParameterParser + { + /// + /// Parses out media UDIs from an HTML string based on embedded macro parameter values. + /// + /// HTML string + /// + IEnumerable FindUmbracoEntityReferencesFromEmbeddedMacros(string text); + + /// + /// Parses out media UDIs from Macro Grid Control parameters. + /// + /// + /// + IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroGridControls); + } +} diff --git a/src/Umbraco.PublishedCache.NuCache/CacheKeys.cs b/src/Umbraco.PublishedCache.NuCache/CacheKeys.cs index 3d8f14afd3..0ec6f0b7cb 100644 --- a/src/Umbraco.PublishedCache.NuCache/CacheKeys.cs +++ b/src/Umbraco.PublishedCache.NuCache/CacheKeys.cs @@ -13,9 +13,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string LangId(string culture) - { - return culture != null ? ("-L:" + culture) : string.Empty; - } + => string.IsNullOrEmpty(culture) ? string.Empty : ("-L:" + culture); public static string PublishedContentChildren(Guid contentUid, bool previewing) { diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index 240e6c8861..98fc4a3ffe 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -608,7 +608,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache throw new ArgumentException("Kit content cannot have children.", nameof(kit)); // ReSharper restore LocalizableElement - _logger.LogDebug("Set content ID: {KitNodeId}", kit.Node.Id); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Set content ID: {KitNodeId}", kit.Node.Id); + } // get existing _contentNodes.TryGetValue(kit.Node.Id, out var link); @@ -727,7 +730,11 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache previousNode = null; // there is no previous sibling } - _logger.LogDebug("Set {thisNodeId} with parent {thisNodeParentContentId}", thisNode.Id, thisNode.ParentContentId); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Set {thisNodeId} with parent {thisNodeParentContentId}", thisNode.Id, thisNode.ParentContentId); + } + SetValueLocked(_contentNodes, thisNode.Id, thisNode); // if we are initializing from the database source ensure the local db is updated @@ -784,7 +791,12 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache ok = false; continue; // skip that one } - _logger.LogDebug("Set {kitNodeId} with parent {kitNodeParentContentId}", kit.Node.Id, kit.Node.ParentContentId); + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Set {kitNodeId} with parent {kitNodeParentContentId}", kit.Node.Id, kit.Node.ParentContentId); + } + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); @@ -873,7 +885,11 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache if (link?.Value == null) return false; var content = link.Value; - _logger.LogDebug("Clear content ID: {ContentId}", content.Id); + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Clear content ID: {ContentId}", content.Id); + } // clear the entire branch ClearBranchLocked(content); diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index 0d9d8b903d..b74c4ea7b0 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -100,7 +100,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var keepOnlyKeys = new Dictionary { {"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl", "previewHubUrl", "iconApiBaseUrl"}}, - {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage", "loginLogoImage", "canSendRequiredEmail", "usernameIsEmail", "minimumPasswordLength", "minimumPasswordNonAlphaNum", "hideBackofficeLogo"}}, + {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage", "loginLogoImage", "canSendRequiredEmail", "usernameIsEmail", "minimumPasswordLength", "minimumPasswordNonAlphaNum", "hideBackofficeLogo", "disableDeleteWhenReferenced", "disableUnpublishWhenReferenced"}}, {"application", new[] {"applicationPath", "cacheBuster"}}, {"isDebuggingEnabled", new string[] { }}, {"features", new [] {"disabledFeatures"}} @@ -378,6 +378,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { "previewHubUrl", _previewRoutes.GetPreviewHubRoute() }, + { + "trackedReferencesApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( + controller => controller.GetPagedReferences(0, 1, 1, false)) + } } }, { @@ -409,6 +413,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers {"loginBackgroundImage", _contentSettings.LoginBackgroundImage}, {"loginLogoImage", _contentSettings.LoginLogoImage }, {"hideBackofficeLogo", _contentSettings.HideBackOfficeLogo }, + {"disableDeleteWhenReferenced", _contentSettings.DisableDeleteWhenReferenced }, + {"disableUnpublishWhenReferenced", _contentSettings.DisableUnpublishWhenReferenced }, {"showUserInvite", _emailSender.CanSendRequiredEmail()}, {"canSendRequiredEmail", _emailSender.CanSendRequiredEmail()}, {"showAllowSegmentationForDocumentTypes", false}, diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 37d4889b4d..451cb17af0 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -820,11 +820,17 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers contentItem.Variants.Where(x => x.Save).Select(x => x.Culture).ToArray(), defaultCulture); + //get the updated model + bool isBlueprint = contentItem.PersistedContent.Blueprint; + + var contentSavedHeader = isBlueprint ? "editBlueprintSavedHeader" : "editContentSavedHeader"; + var contentSavedText = isBlueprint ? "editBlueprintSavedText" : "editContentSavedText"; + switch (contentItem.Action) { case ContentSaveAction.Save: case ContentSaveAction.SaveNew: - SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentSavedText", "editVariantSavedText", cultureForInvariantErrors, out wasCancelled); + SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, contentSavedHeader, contentSavedText, "editVariantSavedText", cultureForInvariantErrors, out wasCancelled); break; case ContentSaveAction.Schedule: case ContentSaveAction.ScheduleNew: @@ -834,7 +840,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers wasCancelled = false; break; } - SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentScheduledSavedText", "editVariantSavedText", cultureForInvariantErrors, out wasCancelled); + + SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentSavedHeader", "editContentScheduledSavedText", "editVariantSavedText", cultureForInvariantErrors, out wasCancelled); break; case ContentSaveAction.SendPublish: @@ -883,7 +890,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (!await ValidatePublishBranchPermissionsAsync(contentItem)) { globalNotifications.AddErrorNotification( - _localizedTextService.Localize(null,"publish"), + _localizedTextService.Localize(null, "publish"), _localizedTextService.Localize("publish", "invalidPublishBranchPermissions")); wasCancelled = false; break; @@ -900,7 +907,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (!await ValidatePublishBranchPermissionsAsync(contentItem)) { globalNotifications.AddErrorNotification( - _localizedTextService.Localize(null,"publish"), + _localizedTextService.Localize(null, "publish"), _localizedTextService.Localize("publish", "invalidPublishBranchPermissions")); wasCancelled = false; break; @@ -914,7 +921,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers throw new ArgumentOutOfRangeException(); } - //get the updated model + // We have to map do display after we've actually saved the content, otherwise we'll miss information that's set when saving content, such as ID var display = mapToDisplay(contentItem.PersistedContent); //merge the tracked success messages with the outgoing model @@ -1041,7 +1048,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// private void SaveAndNotify(ContentItemSave contentItem, Func saveMethod, int variantCount, Dictionary notifications, SimpleNotificationModel globalNotifications, - string invariantSavedLocalizationAlias, string variantSavedLocalizationAlias, string cultureForInvariantErrors, + string savedContentHeaderLocalizationAlias, string invariantSavedLocalizationAlias, string variantSavedLocalizationAlias, string cultureForInvariantErrors, out bool wasCancelled) { var saveResult = saveMethod(contentItem.PersistedContent); @@ -1061,15 +1068,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var variantName = GetVariantName(culture, segment); AddSuccessNotification(notifications, culture, segment, - _localizedTextService.Localize("speechBubbles", "editContentSavedHeader"), - _localizedTextService.Localize(null,variantSavedLocalizationAlias, new[] { variantName })); + _localizedTextService.Localize("speechBubbles", savedContentHeaderLocalizationAlias), + _localizedTextService.Localize(null, variantSavedLocalizationAlias, new[] { variantName })); } } else if (ModelState.IsValid) { globalNotifications.AddSuccessNotification( - _localizedTextService.Localize("speechBubbles", "editContentSavedHeader"), - _localizedTextService.Localize("speechBubbles",invariantSavedLocalizationAlias)); + _localizedTextService.Localize("speechBubbles", savedContentHeaderLocalizationAlias), + _localizedTextService.Localize("speechBubbles", invariantSavedLocalizationAlias)); } } } @@ -2117,21 +2124,34 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } var variantIndex = 0; + var defaultCulture = _allLangs.Value.Values.FirstOrDefault(x => x.IsDefault)?.IsoCode; - //loop through each variant, set the correct name and property values + // loop through each variant, set the correct name and property values foreach (var variant in contentSave.Variants) { - //Don't update anything for this variant if Save is not true - if (!variant.Save) continue; + // Don't update anything for this variant if Save is not true + if (!variant.Save) + { + continue; + } - //Don't update the name if it is empty + // Don't update the name if it is empty if (!variant.Name.IsNullOrWhiteSpace()) { if (contentSave.PersistedContent.ContentType.VariesByCulture()) { if (variant.Culture.IsNullOrWhiteSpace()) + { throw new InvalidOperationException($"Cannot set culture name without a culture."); + } + contentSave.PersistedContent.SetCultureName(variant.Name, variant.Culture); + + // If the variant culture is the default culture we also want to update the name on the Content itself. + if (variant.Culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase)) + { + contentSave.PersistedContent.Name = variant.Name; + } } else { @@ -2139,7 +2159,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } - //This is important! We only want to process invariant properties with the first variant, for any other variant + // This is important! We only want to process invariant properties with the first variant, for any other variant // we need to exclude invariant properties from being processed, otherwise they will be double processed for the // same value which can cause some problems with things such as file uploads. var propertyCollection = variantIndex == 0 @@ -2147,10 +2167,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers : new ContentPropertyCollectionDto { Properties = variant.PropertyCollectionDto.Properties.Where( - x => !x.Culture.IsNullOrWhiteSpace() || !x.Segment.IsNullOrWhiteSpace()) + x => !x.Culture.IsNullOrWhiteSpace() || !x.Segment.IsNullOrWhiteSpace()), }; - //for each variant, map the property values + // for each variant, map the property values MapPropertyValuesForPersistence( contentSave, propertyCollection, @@ -2171,6 +2191,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers variantIndex++; } + // Map IsDirty cultures to edited cultures, to make it easier to verify changes on specific variants on Saving and Saved events. + IEnumerable editedCultures = contentSave.PersistedContent.CultureInfos.Values + .Where(x => x.IsDirty()) + .Select(x => x.Culture); + contentSave.PersistedContent.SetCultureEdited(editedCultures); + // handle template if (string.IsNullOrWhiteSpace(contentSave.TemplateAlias)) // cleared: clear if not already null { @@ -2181,10 +2207,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } else // set: update if different { - var template = _fileService.GetTemplate(contentSave.TemplateAlias); - if (template == null) + ITemplate template = _fileService.GetTemplate(contentSave.TemplateAlias); + if (template is null) { - //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); + // ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); _logger.LogWarning("No template exists with the specified alias: {TemplateAlias}", contentSave.TemplateAlias); } else if (template.Id != contentSave.PersistedContent.TemplateId) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs index f389641777..b22a7d715c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs @@ -224,7 +224,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { // if there's already a default event message, don't add our default one IEventMessagesFactory messages = EventMessages; - if (messages != null && messages.GetOrDefault().GetAll().Any(x => x.IsDefaultEventMessage)) + if (messages?.GetOrDefault()?.GetAll().Any(x => x.IsDefaultEventMessage) == true) { return; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs b/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs index 955081fa73..342686ceb3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; @@ -19,10 +20,12 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Core.Telemetry; using Umbraco.Cms.Web.BackOffice.Filters; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.Filters; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -43,10 +46,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly IDashboardService _dashboardService; private readonly IUmbracoVersion _umbracoVersion; private readonly IShortStringHelper _shortStringHelper; + private readonly ISiteIdentifierService _siteIdentifierService; private readonly ContentDashboardSettings _dashboardSettings; + /// /// Initializes a new instance of the with all its dependencies. /// + [ActivatorUtilitiesConstructor] public DashboardController( IBackOfficeSecurityAccessor backOfficeSecurityAccessor, AppCaches appCaches, @@ -54,7 +60,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IDashboardService dashboardService, IUmbracoVersion umbracoVersion, IShortStringHelper shortStringHelper, - IOptions dashboardSettings) + IOptions dashboardSettings, + ISiteIdentifierService siteIdentifierService) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -63,9 +70,32 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _dashboardService = dashboardService; _umbracoVersion = umbracoVersion; _shortStringHelper = shortStringHelper; + _siteIdentifierService = siteIdentifierService; _dashboardSettings = dashboardSettings.Value; } + + [Obsolete("Use the constructor that accepts ISiteIdentifierService")] + public DashboardController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + AppCaches appCaches, + ILogger logger, + IDashboardService dashboardService, + IUmbracoVersion umbracoVersion, + IShortStringHelper shortStringHelper, + IOptions dashboardSettings) + : this( + backOfficeSecurityAccessor, + appCaches, + logger, + dashboardService, + umbracoVersion, + shortStringHelper, + dashboardSettings, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + //we have just one instance of HttpClient shared for the entire application private static readonly HttpClient HttpClient = new HttpClient(); @@ -79,6 +109,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var language = user.Language; var version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(); var isAdmin = user.IsAdmin(); + _siteIdentifierService.TryGetOrCreateSiteIdentifier(out Guid siteIdentifier); if (!IsAllowedUrl(baseUrl)) { @@ -90,14 +121,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return JObject.Parse(errorJson); } - var url = string.Format("{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}", + var url = string.Format("{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}&siteid={7}", baseUrl, _dashboardSettings.ContentDashboardPath, section, allowedSections, language, version, - isAdmin); + isAdmin, + siteIdentifier); var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section; var content = _appCaches.RuntimeCache.GetCacheItem(key); diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index 564d0dcdd9..327884689e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -1,9 +1,11 @@ using System; using System.IO; +using System.Web; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -53,13 +55,26 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// public IActionResult GetResized(string imagePath, int width) { - var ext = Path.GetExtension(imagePath); + // We have to use HttpUtility to encode the path here, for non-ASCII characters + // We cannot use the WebUtility, as we only want to encode the path, and not the entire string + var encodedImagePath = HttpUtility.UrlPathEncode(imagePath); + + + var ext = Path.GetExtension(encodedImagePath); + + // check if imagePath is local to prevent open redirect + if (!Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + { + return Unauthorized(); + } // we need to check if it is an image by extension if (_imageUrlGenerator.IsSupportedImageFormat(ext) == false) + { return NotFound(); + } - //redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file + // redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file DateTimeOffset? imageLastModified = null; try { @@ -74,14 +89,20 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null; - var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) + var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(encodedImagePath) { Width = width, ImageCropMode = ImageCropMode.Max, CacheBusterValue = rnd }); - - return new RedirectResult(imageUrl, false); + if (Url.IsLocalUrl(imageUrl)) + { + return new LocalRedirectResult(imageUrl, false); + } + else + { + return Unauthorized(); + } } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index a4001ce79f..b9cda9fea6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -174,6 +174,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return ValidationProblem(ModelState); } + // Update language + CultureInfo cultureAfterChange; + try + { + // language has the CultureName of the previous lang so we get information about new culture. + cultureAfterChange = CultureInfo.GetCultureInfo(language.IsoCode); + } + catch (CultureNotFoundException) + { + ModelState.AddModelError("IsoCode", "No Culture found with name " + language.IsoCode); + return ValidationProblem(ModelState); + } + existingById.CultureName = cultureAfterChange.DisplayName; existingById.IsDefault = language.IsDefault; existingById.FallbackLanguageId = language.FallbackLanguageId; existingById.IsoCode = language.IsoCode; diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 14a9080586..c4328da2d4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -33,12 +33,9 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Web.BackOffice.ActionResults; using Umbraco.Cms.Web.BackOffice.Authorization; -using Umbraco.Cms.Web.BackOffice.Extensions; using Umbraco.Cms.Web.BackOffice.Filters; using Umbraco.Cms.Web.BackOffice.ModelBinders; -using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Extensions; @@ -676,12 +673,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { return new ActionResult(parentIdResult.Result); } + var parentId = parentIdResult.Value; if (!parentId.HasValue) { return NotFound("The passed id doesn't exist"); } + var isFolderAllowed = IsFolderCreationAllowedHere(parentId.Value); + if (isFolderAllowed == false) + { + return ValidationProblem(_localizedTextService.Localize("speechBubbles", "folderCreationNotAllowed")); + } + var f = _mediaService.CreateMedia(folder.Name, parentId.Value, Constants.Conventions.MediaTypes.Folder); _mediaService.Save(f, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -722,10 +726,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var tempFiles = new PostedFiles(); - //in case we pass a path with a folder in it, we will create it and upload media to it. if (!string.IsNullOrEmpty(path)) { + if (!IsFolderCreationAllowedHere(parentId.Value)) + { + AddCancelMessage(tempFiles, _localizedTextService.Localize("speechBubbles", "folderUploadNotAllowed")); + return Ok(tempFiles); + } var folders = path.Split(Constants.CharArrays.ForwardSlash); @@ -735,7 +743,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IMedia folderMediaItem; //if uploading directly to media root and not a subfolder - if (parentId == -1) + if (parentId == Constants.System.Root) { //look for matching folder folderMediaItem = @@ -768,11 +776,50 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _mediaService.Save(folderMediaItem); } } + //set the media root to the folder id so uploaded files will end there. parentId = folderMediaItem.Id; } } + var mediaTypeAlias = string.Empty; + var allMediaTypes = _mediaTypeService.GetAll().ToList(); + var allowedContentTypes = new HashSet(); + + if (parentId != Constants.System.Root) + { + var mediaFolderItem = _mediaService.GetById(parentId.Value); + var mediaFolderType = allMediaTypes.FirstOrDefault(x => x.Alias == mediaFolderItem.ContentType.Alias); + + if (mediaFolderType != null) + { + IMediaType mediaTypeItem = null; + + foreach (ContentTypeSort allowedContentType in mediaFolderType.AllowedContentTypes) + { + IMediaType checkMediaTypeItem = allMediaTypes.FirstOrDefault(x => x.Id == allowedContentType.Id.Value); + allowedContentTypes.Add(checkMediaTypeItem); + + var fileProperty = checkMediaTypeItem?.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == Constants.Conventions.Media.File); + if (fileProperty != null) + { + mediaTypeItem = checkMediaTypeItem; + } + } + + //Only set the permission-based mediaType if we only allow 1 specific file under this parent. + if (allowedContentTypes.Count == 1 && mediaTypeItem != null) + { + mediaTypeAlias = mediaTypeItem.Alias; + } + } + } + else + { + var typesAllowedAtRoot = allMediaTypes.Where(x => x.AllowedAsRoot).ToList(); + allowedContentTypes.UnionWith(typesAllowedAtRoot); + } + //get the files foreach (var formFile in file) { @@ -780,71 +827,82 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var safeFileName = fileName.ToSafeFileName(ShortStringHelper); var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); - if (_contentSettings.IsFileAllowedForUpload(ext)) - { - var mediaType = Constants.Conventions.MediaTypes.File; - - if (contentTypeAlias == Constants.Conventions.MediaTypes.AutoSelect) - { - var mediaTypes = _mediaTypeService.GetAll(); - // Look up MediaTypes - foreach (var mediaTypeItem in mediaTypes) - { - var fileProperty = mediaTypeItem.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == "umbracoFile"); - if (fileProperty != null) - { - var dataTypeKey = fileProperty.DataTypeKey; - var dataType = _dataTypeService.GetDataType(dataTypeKey); - - if (dataType != null && dataType.Configuration is IFileExtensionsConfig fileExtensionsConfig) - { - var fileExtensions = fileExtensionsConfig.FileExtensions; - if (fileExtensions != null) - { - if (fileExtensions.Where(x => x.Value == ext).Count() != 0) - { - mediaType = mediaTypeItem.Alias; - break; - } - } - } - } - } - - // If media type is still File then let's check if it's an image. - if (mediaType == Constants.Conventions.MediaTypes.File && _imageUrlGenerator.SupportedImageFileTypes.Contains(ext)) - { - mediaType = Constants.Conventions.MediaTypes.Image; - } - } - else - { - mediaType = contentTypeAlias; - } - - var mediaItemName = fileName.ToFriendlyName(); - - var f = _mediaService.CreateMedia(mediaItemName, parentId.Value, mediaType, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); - - - await using (var stream = formFile.OpenReadStream()) - { - f.SetValue(_mediaFileManager, _mediaUrlGenerators, _shortStringHelper, _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, fileName, stream); - } - - - var saveResult = _mediaService.Save(f, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); - if (saveResult == false) - { - AddCancelMessage(tempFiles, _localizedTextService.Localize("speechBubbles", "operationCancelledText") + " -- " + mediaItemName); - } - } - else + if (!_contentSettings.IsFileAllowedForUpload(ext)) { tempFiles.Notifications.Add(new BackOfficeNotification( _localizedTextService.Localize("speechBubbles", "operationFailedHeader"), _localizedTextService.Localize("media", "disallowedFileType"), NotificationStyle.Warning)); + continue; + } + + if (string.IsNullOrEmpty(mediaTypeAlias)) + { + mediaTypeAlias = Constants.Conventions.MediaTypes.File; + + if (contentTypeAlias == Constants.Conventions.MediaTypes.AutoSelect) + { + // Look up MediaTypes + foreach (var mediaTypeItem in allMediaTypes) + { + var fileProperty = mediaTypeItem.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == Constants.Conventions.Media.File); + if (fileProperty == null) + { + continue; + } + + var dataTypeKey = fileProperty.DataTypeKey; + var dataType = _dataTypeService.GetDataType(dataTypeKey); + + if (dataType == null || dataType.Configuration is not IFileExtensionsConfig fileExtensionsConfig) + { + continue; + } + + var fileExtensions = fileExtensionsConfig.FileExtensions; + if (fileExtensions == null || fileExtensions.All(x => x.Value != ext)) + { + continue; + } + + mediaTypeAlias = mediaTypeItem.Alias; + break; + } + + // If media type is still File then let's check if it's an image. + if (mediaTypeAlias == Constants.Conventions.MediaTypes.File && _imageUrlGenerator.SupportedImageFileTypes.Contains(ext)) + { + mediaTypeAlias = Constants.Conventions.MediaTypes.Image; + } + } + else + { + mediaTypeAlias = contentTypeAlias; + } + } + + if (allowedContentTypes.Any(x => x.Alias == mediaTypeAlias) == false) + { + tempFiles.Notifications.Add(new BackOfficeNotification( + _localizedTextService.Localize("speechBubbles", "operationFailedHeader"), + _localizedTextService.Localize("media", "disallowedMediaType", new[] { mediaTypeAlias }), + NotificationStyle.Warning)); + continue; + } + + var mediaItemName = fileName.ToFriendlyName(); + + var createdMediaItem = _mediaService.CreateMedia(mediaItemName, parentId.Value, mediaTypeAlias, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); + + await using (var stream = formFile.OpenReadStream()) + { + createdMediaItem.SetValue(_mediaFileManager, _mediaUrlGenerators, _shortStringHelper, _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, fileName, stream); + } + + var saveResult = _mediaService.Save(createdMediaItem, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); + if (saveResult == false) + { + AddCancelMessage(tempFiles, _localizedTextService.Localize("speechBubbles", "operationCancelledText") + " -- " + mediaItemName); } } @@ -861,6 +919,29 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return Ok(tempFiles); } + private bool IsFolderCreationAllowedHere(int parentId) + { + var allMediaTypes = _mediaTypeService.GetAll().ToList(); + var isFolderAllowed = false; + if (parentId == Constants.System.Root) + { + var typesAllowedAtRoot = allMediaTypes.Where(ct => ct.AllowedAsRoot).ToList(); + isFolderAllowed = typesAllowedAtRoot.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder); + } + else + { + var parentMediaType = _mediaService.GetById(parentId); + var mediaFolderType = allMediaTypes.FirstOrDefault(x => x.Alias == parentMediaType.ContentType.Alias); + if (mediaFolderType != null) + { + isFolderAllowed = + mediaFolderType.AllowedContentTypes.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder); + } + } + + return isFolderAllowed; + } + private IMedia FindInChildren(int mediaId, string nameToFind, string contentTypeAlias) { const int pageSize = 500; @@ -1001,7 +1082,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return new ActionResult(toMove); } - + [Obsolete("Please use TrackedReferencesController.GetPagedRelationsForItem() instead. Scheduled for removal in V11.")] public PagedResult GetPagedReferences(int id, string entityType, int pageNumber = 1, int pageSize = 100) { if (pageNumber <= 0 || pageSize <= 0) diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs index d63f6b0eda..197148a2a3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs @@ -148,7 +148,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers relationType.Name.ToSafeAlias(_shortStringHelper, true), relationType.IsBidirectional, relationType.ParentObjectType, - relationType.ChildObjectType); + relationType.ChildObjectType, + relationType.IsDependency); try { diff --git a/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs b/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs new file mode 100644 index 0000000000..aa1a0ee86e --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.BackOffice.ModelBinders; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Web.BackOffice.Controllers +{ + [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessContentOrMedia)] + public class TrackedReferencesController : BackOfficeNotificationsController + { + private readonly ITrackedReferencesService _relationService; + + public TrackedReferencesController(ITrackedReferencesService relationService) + { + _relationService = relationService; + } + + /// + /// Gets a page list of tracked references for the current item, so you can see where an item is being used. + /// + /// + /// Used by info tabs on content, media etc. and for the delete and unpublish of single items. + /// This is basically finding parents of relations. + /// + public ActionResult> GetPagedReferences(int id, int pageNumber = 1, int pageSize = 100, bool filterMustBeIsDependency = false) + { + if (pageNumber <= 0 || pageSize <= 0) + { + return BadRequest("Both pageNumber and pageSize must be greater than zero"); + } + + return _relationService.GetPagedRelationsForItem(id, pageNumber - 1, pageSize, filterMustBeIsDependency); + } + + /// + /// Gets a page list of the child nodes of the current item used in any kind of relation. + /// + /// + /// Used when deleting and unpublishing a single item to check if this item has any descending items that are in any kind of relation. + /// This is basically finding the descending items which are children in relations. + /// + public ActionResult> GetPagedDescendantsInReferences(int parentId, int pageNumber = 1, int pageSize = 100, bool filterMustBeIsDependency = true) + { + if (pageNumber <= 0 || pageSize <= 0) + { + return BadRequest("Both pageNumber and pageSize must be greater than zero"); + } + + return _relationService.GetPagedDescendantsInReferences(parentId, pageNumber - 1, pageSize, filterMustBeIsDependency); + } + + /// + /// Gets a page list of the items used in any kind of relation from selected integer ids. + /// + /// + /// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view). + /// This is basically finding children of relations. + /// + [HttpGet] + [HttpPost] + public ActionResult> GetPagedReferencedItems([FromJsonPath] int[] ids, int pageNumber = 1, int pageSize = 100, bool filterMustBeIsDependency = true) + { + if (pageNumber <= 0 || pageSize <= 0) + { + return BadRequest("Both pageNumber and pageSize must be greater than zero"); + } + + return _relationService.GetPagedItemsWithRelations(ids, pageNumber - 1, pageSize, filterMustBeIsDependency); + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs index e234fa1115..08cfc49a9d 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs @@ -129,7 +129,7 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping target.AllowedActions = GetActions(source, parent, context); target.AllowedTemplates = GetAllowedTemplates(source); - target.ContentApps = _commonMapper.GetContentApps(source); + target.ContentApps = _commonMapper.GetContentAppsForEntity(source); target.ContentTypeId = source.ContentType.Id; target.ContentTypeKey = source.ContentType.Key; target.ContentTypeAlias = source.ContentType.Alias; diff --git a/src/Umbraco.Web.BackOffice/Mapping/MediaMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/MediaMapDefinition.cs index 03bca1ee70..9bff86e90b 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/MediaMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/MediaMapDefinition.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; @@ -57,7 +57,7 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping // Umbraco.Code.MapAll -Properties -Errors -Edited -Updater -Alias -IsContainer private void Map(IMedia source, MediaItemDisplay target, MapperContext context) { - target.ContentApps = _commonMapper.GetContentApps(source); + target.ContentApps = _commonMapper.GetContentAppsForEntity(source); target.ContentType = _commonMapper.GetContentType(source, context); target.ContentTypeId = source.ContentType.Id; target.ContentTypeAlias = source.ContentType.Alias; diff --git a/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs index f0647b9efb..8da173ce68 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs @@ -41,7 +41,7 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping // Umbraco.Code.MapAll -Trashed -IsContainer -VariesByCulture private void Map(IMember source, MemberDisplay target, MapperContext context) { - target.ContentApps = _commonMapper.GetContentApps(source); + target.ContentApps = _commonMapper.GetContentAppsForEntity(source); target.ContentType = _commonMapper.GetContentType(source, context); target.ContentTypeId = source.ContentType.Id; target.ContentTypeAlias = source.ContentType.Alias; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs index d62edcc1f9..8d4e04d2c0 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs @@ -58,16 +58,15 @@ namespace Umbraco.Cms.Web.BackOffice.Security // TODO: We could override and throw NotImplementedException for other methods? // Ensures that the sign in scheme is always the Umbraco back office external type - private class EnsureBackOfficeScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions + internal class EnsureBackOfficeScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions { public void PostConfigure(string name, TOptions options) { - if (!name.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix)) + // ensure logic only applies to backoffice authentication schemes + if (name.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix)) { - return; + options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType; } - - options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType; } } } diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index 58a6862300..916bfb17c0 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -15,6 +15,7 @@ using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Security @@ -92,7 +93,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security /// public void Configure(CookieAuthenticationOptions options) { - options.SlidingExpiration = true; + options.SlidingExpiration = false; options.ExpireTimeSpan = _globalSettings.TimeOut; options.Cookie.Domain = _securitySettings.AuthCookieDomain; options.Cookie.Name = _securitySettings.AuthCookieName; @@ -150,8 +151,6 @@ namespace Umbraco.Cms.Web.BackOffice.Security // ensure the thread culture is set backOfficeIdentity.EnsureCulture(); - await EnsureValidSessionId(ctx); - await securityStampValidator.ValidateAsync(ctx); EnsureTicketRenewalIfKeepUserLoggedIn(ctx); // add or update a claim to track when the cookie expires, we use this to track time remaining @@ -163,6 +162,28 @@ namespace Umbraco.Cms.Web.BackOffice.Security Constants.Security.BackOfficeAuthenticationType, backOfficeIdentity)); + await securityStampValidator.ValidateAsync(ctx); + + // This might have been called from GetRemainingTimeoutSeconds, in this case we don't want to ensure valid session + // since that in it self will keep the session valid since we renew the lastVerified date. + // Similarly don't renew the token + if (IsRemainingSecondsRequest(ctx)) + { + return; + } + + // This relies on IssuedUtc, so call it before updating it. + await EnsureValidSessionId(ctx); + + // We have to manually specify Issued and Expires, + // because the SecurityStampValidator refreshes the principal every 30 minutes, + // When the principal is refreshed the Issued is update to time of refresh, however, the Expires remains unchanged + // When we then try and renew, the difference of issued and expires effectively becomes the new ExpireTimeSpan + // meaning we effectively lose 30 minutes of our ExpireTimeSpan for EVERY principal refresh if we don't + // https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Cookies/src/CookieAuthenticationHandler.cs#L115 + ctx.Properties.IssuedUtc = _systemClock.UtcNow; + ctx.Properties.ExpiresUtc = _systemClock.UtcNow.Add(_globalSettings.TimeOut); + ctx.ShouldRenew = true; }, OnSigningIn = ctx => { @@ -226,7 +247,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security } return Task.CompletedTask; - } + }, }; } @@ -276,5 +297,21 @@ namespace Umbraco.Cms.Web.BackOffice.Security } } } + + private bool IsRemainingSecondsRequest(CookieValidatePrincipalContext context) + { + var routeValues = context.HttpContext.Request.RouteValues; + if (routeValues.TryGetValue("controller", out var controllerName) && + routeValues.TryGetValue("action", out var action)) + { + if (controllerName?.ToString() == ControllerExtensions.GetControllerName() + && action?.ToString() == nameof(AuthenticationController.GetRemainingTimeoutSeconds)) + { + return true; + } + } + + return false; + } } } diff --git a/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs b/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs index 03451a60fd..e610ca1ee7 100644 --- a/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs +++ b/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs @@ -13,15 +13,13 @@ namespace Umbraco.Cms.Web.BackOffice.Services public class ConflictingRouteService : IConflictingRouteService { private readonly TypeLoader _typeLoader; - private readonly IEnumerable _endpointDataSources; /// /// Initializes a new instance of the class. /// - public ConflictingRouteService(TypeLoader typeLoader, IEnumerable endpointDataSources) + public ConflictingRouteService(TypeLoader typeLoader) { _typeLoader = typeLoader; - _endpointDataSources = endpointDataSources; } /// diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index db714bb675..ee7d480731 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -349,7 +349,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var actionContext = new ActionContext(HttpContext, routeData, actionDescriptor); var proxyControllerContext = new ControllerContext(actionContext); - var controller = (TreeController)_controllerFactory.CreateController(proxyControllerContext); + var controller = (TreeControllerBase)_controllerFactory.CreateController(proxyControllerContext); // TODO: What about other filters? Will they execute? var isAllowed = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(actionContext); diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs b/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs index 08f6d7b400..42a15cccc8 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs @@ -64,15 +64,34 @@ namespace Umbraco.Cms.Web.BackOffice.Trees AddTreeController(controllerType); } - public void RemoveTreeController() => RemoveTreeController(typeof(T)); + public void RemoveTree(Tree treeDefinition) + { + if (treeDefinition == null) + throw new ArgumentNullException(nameof(treeDefinition)); + _trees.Remove(treeDefinition); + } + public void RemoveTreeController() + where T : TreeControllerBase + => RemoveTreeController(typeof(T)); + + // TODO: Change parameter name to "controllerType" in a major version to make it consistent with AddTreeController method. public void RemoveTreeController(Type type) { - var tree = _trees.FirstOrDefault(it => it.TreeControllerType == type); + if (!typeof(TreeControllerBase).IsAssignableFrom(type)) + throw new ArgumentException($"Type {type} does not inherit from {typeof(TreeControllerBase).FullName}."); + + var tree = _trees.FirstOrDefault(x => x.TreeControllerType == type); if (tree != null) { _trees.Remove(tree); } } + + public void RemoveTreeControllers(IEnumerable controllerTypes) + { + foreach (var controllerType in controllerTypes) + RemoveTreeController(controllerType); + } } } diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs index b6f2948965..20d0e1a305 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs @@ -47,6 +47,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// + [Obsolete("See GetTreeNodesAsync")] protected abstract ActionResult GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); /// @@ -55,8 +56,40 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// + [Obsolete("See GetMenuForNodeAsync")] protected abstract ActionResult GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); + /// + /// The method called to render the contents of the tree structure + /// + /// + /// + /// All of the query string parameters passed from jsTree + /// + /// + /// If overriden, GetTreeNodes will not be called + /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end + /// to the back end to be used in the query for model data. + /// + protected virtual async Task> GetTreeNodesAsync(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) + { + return GetTreeNodes(id, queryStrings); + } + + /// + /// Returns the menu structure for the node + /// + /// + /// + /// + /// + /// If overriden, GetMenuForNode will not be called + /// + protected virtual async Task> GetMenuForNodeAsync(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) + { + return GetMenuForNode(id, queryStrings); + } + /// /// The name to display on the root node /// @@ -132,7 +165,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees public async Task> GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; - var nodesResult = GetTreeNodes(id, queryStrings); + var nodesResult = await GetTreeNodesAsync(id, queryStrings); if (!(nodesResult.Result is null)) { @@ -150,7 +183,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees node.RoutePath = "#"; //raise the event - await _eventAggregator.PublishAsync(new TreeNodesRenderingNotification(nodes, queryStrings, TreeAlias)); + await _eventAggregator.PublishAsync(new TreeNodesRenderingNotification(nodes, queryStrings, TreeAlias, id)); return nodes; } @@ -164,7 +197,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees public async Task> GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; - var menuResult = GetMenuForNode(id, queryStrings); + var menuResult = await GetMenuForNodeAsync(id, queryStrings); if (!(menuResult.Result is null)) { return menuResult.Result; diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeNodesRenderingNotification.cs b/src/Umbraco.Web.BackOffice/Trees/TreeNodesRenderingNotification.cs index f022b23a20..b7334c0e5b 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeNodesRenderingNotification.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeNodesRenderingNotification.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.AspNetCore.Http; using Umbraco.Cms.Core.Trees; @@ -11,26 +12,57 @@ namespace Umbraco.Cms.Core.Notifications /// public class TreeNodesRenderingNotification : INotification { - /// - /// The tree nodes being rendered - /// - public TreeNodeCollection Nodes { get; } /// - /// The query string of the current request + /// Initializes a new instance of the class. /// - public FormCollection QueryString { get; } + /// The tree nodes being rendered + /// The query string of the current request + /// The alias of the tree rendered + /// The id of the node rendered + public TreeNodesRenderingNotification(TreeNodeCollection nodes, FormCollection queryString, string treeAlias, string id) + { + Nodes = nodes; + QueryString = queryString; + TreeAlias = treeAlias; + Id = id; + } /// - /// The alias of the tree rendered + /// Initializes a new instance of the class. + /// Constructor /// - public string TreeAlias { get; } - + /// The tree nodes being rendered + /// The query string of the current request + /// The alias of the tree rendered + [Obsolete("Use ctor with all parameters")] public TreeNodesRenderingNotification(TreeNodeCollection nodes, FormCollection queryString, string treeAlias) { Nodes = nodes; QueryString = queryString; TreeAlias = treeAlias; + Id = default; } + + /// + /// Gets the tree nodes being rendered + /// + public TreeNodeCollection Nodes { get; } + + /// + /// Gets the query string of the current request + /// + public FormCollection QueryString { get; } + + /// + /// Gets the alias of the tree rendered + /// + public string TreeAlias { get; } + + /// + /// Gets the id of the node rendered + /// + public string Id { get; } + } } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 819076bbb8..d2cbf5bd14 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -3,6 +3,7 @@ using System.Data.Common; using System.Data.SqlClient; using System.IO; using System.Linq; +using System.Net.Http; using System.Reflection; using Dazinator.Extensions.FileProviders.GlobPatternFilter; using Microsoft.AspNetCore.Builder; @@ -191,6 +192,11 @@ namespace Umbraco.Extensions private static IUmbracoBuilder AddHttpClients(this IUmbracoBuilder builder) { builder.Services.AddHttpClient(); + builder.Services.AddHttpClient(Constants.HttpClients.IgnoreCertificateErrors) + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }); return builder; } diff --git a/src/Umbraco.Web.Common/Extensions/ControllerExtensions.cs b/src/Umbraco.Web.Common/Extensions/ControllerExtensions.cs index 6ae94ab57f..911ecee8e5 100644 --- a/src/Umbraco.Web.Common/Extensions/ControllerExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ControllerExtensions.cs @@ -24,7 +24,7 @@ namespace Umbraco.Extensions /// public static string GetControllerName(Type controllerType) { - if (!controllerType.Name.EndsWith("Controller")) + if (!controllerType.Name.EndsWith("Controller") && !controllerType.Name.EndsWith("Controller`1")) { throw new InvalidOperationException("The controller type " + controllerType + " does not follow conventions, MVC Controller class names must be suffixed with the term 'Controller'"); } diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs index 2aeb2555eb..7d9ce136ef 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Routing; @@ -136,5 +137,25 @@ namespace Umbraco.Extensions return new Uri(routingSettings.UmbracoApplicationUrl); } + + /// + /// Gets the Umbraco `ufprt` encrypted string from the current request + /// + /// The current request + /// The extracted `ufprt` token. + public static string GetUfprt(this HttpRequest request) + { + if (request.HasFormContentType && request.Form.TryGetValue("ufprt", out StringValues formVal) && formVal != StringValues.Empty) + { + return formVal.ToString(); + } + + if (request.Query.TryGetValue("ufprt", out StringValues queryVal) && queryVal != StringValues.Empty) + { + return queryVal.ToString(); + } + + return null; + } } } diff --git a/src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs b/src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs index ed86d7c783..0c51edd4e1 100644 --- a/src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs @@ -42,7 +42,7 @@ namespace Umbraco.Cms.Web.Common.Filters { if (context == null) throw new ArgumentNullException(nameof(context)); - var ufprt = context.HttpContext.Request.Form["ufprt"]; + var ufprt = context.HttpContext.Request.GetUfprt(); if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) { diff --git a/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs b/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs index 03bdf8f4dd..66cf97fd4c 100644 --- a/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs +++ b/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs @@ -30,7 +30,8 @@ namespace Umbraco.Cms.Web.Common.Security ClaimsIdentity newIdentity = refreshingPrincipal.NewPrincipal.Identities.First(); ClaimsIdentity currentIdentity = refreshingPrincipal.CurrentPrincipal.Identities.First(); - newIdentity.MergeClaimsFromCookieIdentity(currentIdentity); + // Since this is refreshing an existing principal, we want to merge all claims. + newIdentity.MergeAllClaims(currentIdentity); return Task.CompletedTask; }; diff --git a/src/Umbraco.Web.UI.Client/.babelrc b/src/Umbraco.Web.UI.Client/.babelrc index 748cd6a810..5d148e346a 100644 --- a/src/Umbraco.Web.UI.Client/.babelrc +++ b/src/Umbraco.Web.UI.Client/.babelrc @@ -8,6 +8,8 @@ ] ], "plugins": [ + "@babel/plugin-proposal-nullish-coalescing-operator", + "@babel/plugin-proposal-optional-chaining", [ "@babel/plugin-proposal-object-rest-spread", { diff --git a/src/Umbraco.Web.UI.Client/.eslintrc b/src/Umbraco.Web.UI.Client/.eslintrc index b3e410109e..f727714466 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc +++ b/src/Umbraco.Web.UI.Client/.eslintrc @@ -8,7 +8,7 @@ }, "parserOptions": { - "ecmaVersion": 2018 + "ecmaVersion": 2020 }, "globals": { diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index c124c1f48a..6aa1f42bbe 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -3,6 +3,15 @@ "requires": true, "lockfileVersion": 1, "dependencies": { + "@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.0" + } + }, "@babel/code-frame": { "version": "7.15.8", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", @@ -13,198 +22,351 @@ } }, "@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==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", + "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", "dev": true }, "@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==", + "version": "7.17.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", + "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", "dev": true, "requires": { - "@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", + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.17.2", + "@babel/parser": "^7.17.3", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", + "convert-source-map": "^1.7.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" + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", + "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", "dev": true, "requires": { - "@babel/types": "^7.15.6", + "@babel/types": "^7.17.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", + "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.16.4", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "dependencies": { + "browserslist": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", + "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001312", + "electron-to-chromium": "^1.4.71", + "escalade": "^3.1.1", + "node-releases": "^2.0.2", + "picocolors": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz", + "integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw==", + "dev": true + }, + "node-releases": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", + "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", + "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^5.0.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", + "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", + "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", "dev": true, "requires": { - "@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" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + } } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", "dev": true }, "@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==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-wrap-function": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", "dev": true, "requires": { - "@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" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", + "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@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==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.0" } }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-validator-identifier": { @@ -214,32 +376,32 @@ "dev": true }, "@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==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true }, "@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==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" } }, "@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", + "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", "dev": true, "requires": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.0", + "@babel/types": "^7.17.0" } }, "@babel/highlight": { @@ -254,54 +416,719 @@ } }, "@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==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", + "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", "dev": true }, - "@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==", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", + "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", "dev": true, "requires": { - "@babel/compat-data": "^7.13.8", - "@babel/helper-compilation-targets": "^7.13.8", - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", + "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.7" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", + "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", + "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", + "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.17.6", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", + "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", + "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", + "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", + "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", + "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.0", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.13.0" + "@babel/plugin-transform-parameters": "^7.16.7" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", + "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.16.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", + "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.16.10", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", + "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", + "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", + "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", + "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", + "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", + "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", + "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", + "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", + "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", + "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", + "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", + "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", + "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", + "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + } + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", + "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", + "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", + "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", + "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", + "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", + "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", + "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", + "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", + "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/preset-env": { + "version": "7.16.11", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", + "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-async-generator-functions": "^7.16.8", + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-proposal-class-static-block": "^7.16.7", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.16.7", + "@babel/plugin-proposal-json-strings": "^7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.16.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-private-methods": "^7.16.11", + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.16.7", + "@babel/plugin-transform-async-to-generator": "^7.16.8", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.16.7", + "@babel/plugin-transform-classes": "^7.16.7", + "@babel/plugin-transform-computed-properties": "^7.16.7", + "@babel/plugin-transform-destructuring": "^7.16.7", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.16.7", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.16.7", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.16.7", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.16.7", + "@babel/plugin-transform-modules-commonjs": "^7.16.8", + "@babel/plugin-transform-modules-systemjs": "^7.16.7", + "@babel/plugin-transform-modules-umd": "^7.16.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", + "@babel/plugin-transform-new-target": "^7.16.7", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.16.7", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.16.7", + "@babel/plugin-transform-reserved-words": "^7.16.7", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.16.7", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.16.7", + "@babel/plugin-transform-typeof-symbol": "^7.16.7", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.16.8", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.20.2", + "semver": "^6.3.0" }, "dependencies": { - "@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, - "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -310,558 +1137,129 @@ } } }, - "@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==", + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", "dev": true, "requires": { - "@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" - }, - "dependencies": { - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.15.4", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@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, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@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, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - } - } - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@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" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@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, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - } - } - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@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, - "requires": { - "@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" - } - }, - "@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, - "requires": { - "@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" - } - }, - "@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, - "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5" - }, - "dependencies": { - "@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, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - } - } - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.15.4" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@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, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - } - } - } - } + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" } }, "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + } } }, "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "dev": true, "requires": { - "@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", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + } } }, "@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==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + } } }, "@gulp-sourcemaps/identity-map": { @@ -921,6 +1319,28 @@ } } }, + "@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", + "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@microsoft/signalr": { "version": "3.1.20", "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-3.1.20.tgz", @@ -964,11 +1384,35 @@ "dev": true, "optional": true }, + "@socket.io/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", + "dev": true + }, "@types/angular": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.8.3.tgz", "integrity": "sha512-vgc5Z+TD07DT7NEUjFm6XMp0kEbGXIa95XmOL5IiHXR9LdrJpcdDh3jl1nCuZbWyzFn5/1OqtMfomcnA1sUFXQ==" }, + "@types/component-emitter": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", + "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", + "dev": true + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, "@types/glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz", @@ -1004,13 +1448,30 @@ "dev": true }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "dependencies": { + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "dev": true + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dev": true, + "requires": { + "mime-db": "1.51.0" + } + } } }, "accord": { @@ -1068,12 +1529,6 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1485,12 +1940,6 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, - "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 - }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -1609,6 +2058,44 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.1", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + } + }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -1626,12 +2113,6 @@ "now-and-later": "^2.0.0" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1664,12 +2145,6 @@ } } }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true - }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1678,9 +2153,9 @@ "optional": true }, "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true }, "bcrypt-pbkdf": { @@ -1697,15 +2172,6 @@ "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", "dev": true }, - "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, - "requires": { - "callsite": "1.0.0" - } - }, "bin-build": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", @@ -2014,12 +2480,6 @@ "safe-buffer": "^5.1.1" } }, - "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 - }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -2027,21 +2487,21 @@ "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==", + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", "dev": true, "requires": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "1.7.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" }, "dependencies": { "debug": { @@ -2144,6 +2604,7 @@ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, + "optional": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", "buffer-fill": "^1.0.0" @@ -2153,7 +2614,8 @@ "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 + "dev": true, + "optional": true }, "buffer-crc32": { "version": "0.2.13", @@ -2172,7 +2634,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true + "dev": true, + "optional": true }, "buffer-from": { "version": "1.1.2", @@ -2216,9 +2679,9 @@ } }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true }, "cache-base": { @@ -2327,12 +2790,6 @@ "caller-callsite": "^2.0.0" } }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true - }, "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", @@ -2370,9 +2827,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001267", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001267.tgz", - "integrity": "sha512-r1mjTzAuJ9W8cPBGbbus8E0SKcUP7gn03R14Wk8FlAlqhH9hroy9nLqmpuXlfKEw/oILW+FGz47ipXV2O7x8lg==", + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", "dev": true }, "caseless": { @@ -2797,24 +3254,12 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, "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 }, - "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 - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2932,9 +3377,9 @@ } }, "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true }, "copy-descriptor": { @@ -2954,15 +3399,46 @@ } }, "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==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", + "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", "dev": true, "requires": { - "browserslist": "^4.17.3", + "browserslist": "^4.19.1", "semver": "7.0.0" }, "dependencies": { + "browserslist": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", + "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001312", + "electron-to-chromium": "^1.4.71", + "escalade": "^3.1.1", + "node-releases": "^2.0.2", + "picocolors": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz", + "integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw==", + "dev": true + }, + "node-releases": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "dev": true + }, "semver": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", @@ -2977,6 +3453,16 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", @@ -3265,9 +3751,9 @@ } }, "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", + "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==", "dev": true }, "dateformat": { @@ -3866,111 +4352,38 @@ } }, "engine.io": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", - "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", + "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", "dev": true, "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", "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" + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - } - } - }, - "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, - "requires": { - "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" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } } } }, "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==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", + "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", "dev": true, "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" + "@socket.io/base64-arraybuffer": "~1.0.2" } }, "ent": { @@ -5036,9 +5449,9 @@ } }, "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", "dev": true }, "font-awesome": { @@ -5110,14 +5523,14 @@ "optional": true }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, "fs-mkdirp-stream": { @@ -5168,6 +5581,12 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -6372,29 +6791,6 @@ "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", "dev": true }, - "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, - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } - } - }, - "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 - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -6538,24 +6934,16 @@ "optional": true }, "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==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, "requires": { "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } + "toidentifier": "1.0.1" } }, "http-proxy": { @@ -6734,12 +7122,6 @@ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", "dev": true }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, "indx": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/indx/-/indx-0.2.3.tgz", @@ -6877,15 +7259,6 @@ "p-is-promise": "^1.1.0" } }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -7370,13 +7743,10 @@ "dev": true }, "isbinaryfile": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", - "dev": true, - "requires": { - "buffer-alloc": "^1.2.0" - } + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true }, "isexe": { "version": "2.0.0", @@ -7427,12 +7797,6 @@ "resolved": "https://registry.npmjs.org/jquery-ui-touch-punch/-/jquery-ui-touch-punch-0.2.3.tgz", "integrity": "sha1-7tgiQnM7okP0az6HwYQbMIGR2mg=" }, - "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 - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7554,12 +7918,13 @@ } }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } }, "jsprim": { @@ -7586,39 +7951,52 @@ "dev": true }, "karma": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-4.4.1.tgz", - "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", + "version": "6.3.16", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz", + "integrity": "sha512-nEU50jLvDe5yvXqkEJRf8IuvddUkOY2x5Xc4WXHz6dxINgGDrgD2uqQWeVrJs4hbfNaotn+HQ1LZJ4yOXrL7xQ==", "dev": true, "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", + "body-parser": "^1.19.0", "braces": "^3.0.2", - "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.0", + "chokidar": "^3.5.1", + "colors": "1.4.0", + "connect": "^3.7.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", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.2.0", "source-map": "^0.6.1", - "tmp": "0.0.33", - "useragent": "2.3.0" + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" }, "dependencies": { + "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 + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -7645,9 +8023,9 @@ } }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -7660,6 +8038,26 @@ "readdirp": "~3.6.0" } }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -7676,6 +8074,12 @@ "dev": true, "optional": true }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -7709,12 +8113,39 @@ "picomatch": "^2.2.1" } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "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 }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7723,6 +8154,44 @@ "requires": { "is-number": "^7.0.0" } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } }, @@ -7999,6 +8468,12 @@ "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -8099,16 +8574,33 @@ "dev": true }, "log4js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", - "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.1.tgz", + "integrity": "sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg==", "dev": true, "requires": { - "date-format": "^2.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.0", - "rfdc": "^1.1.4", - "streamroller": "^1.0.6" + "date-format": "^4.0.3", + "debug": "^4.3.3", + "flatted": "^3.2.4", + "rfdc": "^1.3.0", + "streamroller": "^3.0.2" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + } } }, "logalot": { @@ -8141,15 +8633,6 @@ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "dev": true }, - "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, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -8186,6 +8669,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, + "optional": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -8414,9 +8898,9 @@ } }, "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true }, "mime-db": { @@ -8627,9 +9111,9 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true }, "next-tick": { @@ -11892,12 +12376,6 @@ "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", "dev": true }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -12110,24 +12588,6 @@ "mimic-fn": "^2.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - } - } - }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -12345,24 +12805,6 @@ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "dev": true }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -13115,7 +13557,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "dev": true, + "optional": true }, "psl": { "version": "1.8.0", @@ -13174,9 +13617,9 @@ "dev": true }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", "dev": true }, "query-string": { @@ -13234,13 +13677,13 @@ "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==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", "dev": true, "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -13338,9 +13781,9 @@ "dev": true }, "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==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", "dev": true, "requires": { "regenerate": "^1.4.2" @@ -13417,29 +13860,29 @@ "dev": true }, "regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", + "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", "dev": true, "requires": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.0.0" } }, "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", "dev": true }, "regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -13952,9 +14395,9 @@ } }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, "shebang-command": { @@ -14173,125 +14616,34 @@ } }, "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==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", + "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", "dev": true, "requires": { - "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" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.1.0", + "socket.io-adapter": "~2.3.3", + "socket.io-parser": "~4.0.4" } }, "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==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", + "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", "dev": true }, - "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, - "requires": { - "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" - }, - "dependencies": { - "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 - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "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==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", "dev": true, "requires": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "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 - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" } }, "sort-keys": { @@ -14622,27 +14974,14 @@ "dev": true }, "streamroller": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", - "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz", + "integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==", "dev": true, "requires": { - "async": "^2.6.2", - "date-format": "^2.0.0", - "debug": "^3.2.6", - "fs-extra": "^7.0.1", - "lodash": "^4.17.14" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "date-format": "^4.0.3", + "debug": "^4.1.1", + "fs-extra": "^10.0.0" } }, "strict-uri-encode": { @@ -15073,12 +15412,6 @@ "is-negated-glob": "^1.0.0" } }, - "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 - }, "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", @@ -15174,9 +15507,9 @@ } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, "tough-cookie": { @@ -15286,6 +15619,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "ua-parser-js": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "dev": true + }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", @@ -15341,12 +15680,6 @@ "dev": true, "optional": true }, - "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 - }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -15476,9 +15809,9 @@ } }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, "unpipe": { @@ -15554,9 +15887,9 @@ "dev": true }, "url-parse": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz", - "integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -15585,16 +15918,6 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, - "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, - "requires": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -15655,6 +15978,12 @@ "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", "dev": true }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, "vendors": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", @@ -15919,12 +16248,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -16007,12 +16330,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "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 - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -16029,7 +16346,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "dev": true, + "optional": true }, "yargs": { "version": "7.1.2", @@ -16117,12 +16435,6 @@ "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } - }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true } } } diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index ef8135487a..fbbfd95981 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -54,11 +54,10 @@ "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", + "@babel/core": "7.17.5", + "@babel/preset-env": "7.16.11", "autoprefixer": "9.6.5", - "caniuse-lite": "^1.0.30001237", + "caniuse-lite": "^1.0.30001312", "cssnano": "4.1.10", "fs": "0.0.2", "gulp": "4.0.2", @@ -81,7 +80,7 @@ "gulp-wrap-js": "0.4.1", "jasmine-core": "3.5.0", "jsdom": "16.4.0", - "karma": "4.4.1", + "karma": "6.3.16", "karma-jasmine": "2.0.1", "karma-jsdom-launcher": "^8.0.2", "karma-junit-reporter": "2.0.1", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 83292251da..501ea9f81a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -12,6 +12,7 @@ scope.publishStatus = []; scope.currentVariant = null; scope.currentUrls = []; + scope.loadingReferences = false; scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; scope.allowChangeDocumentType = false; @@ -229,6 +230,10 @@ }); } + + function loadReferences(){ + scope.loadingReferences = true; + } function loadRedirectUrls() { scope.loadingRedirectUrls = true; //check if Redirect URL Management is enabled @@ -335,6 +340,7 @@ loadRedirectUrls(); setNodePublishStatus(); formatDatesToLocal(); + loadReferences(); } else { isInfoTab = false; } @@ -352,6 +358,7 @@ loadRedirectUrls(); setNodePublishStatus(); formatDatesToLocal(); + loadReferences(); } updateCurrentUrls(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypegroup.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypegroup.component.js index 82112012c0..3445b4039c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypegroup.component.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypegroup.component.js @@ -45,9 +45,9 @@ vm.onChangeSortOrderValue( {group: vm.group}); } } - function clickComposition (documentTypeId) { + function clickComposition (contentTypeId) { if (vm.onClickComposition) { - vm.onClickComposition({documentTypeId: documentTypeId}); + vm.onClickComposition({contentTypeId: contentTypeId}); } } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypeproperty.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypeproperty.component.js index 8f27332ec2..c96944dbd5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypeproperty.component.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypeproperty.component.js @@ -12,6 +12,7 @@ vm.edit = edit; vm.remove = remove; vm.changeSortOrderValue = changeSortOrderValue; + vm.clickComposition = clickComposition; function edit () { if (vm.onEdit) { @@ -31,6 +32,12 @@ } } + function clickComposition(contentTypeId) { + if (vm.onClickComposition) { + vm.onClickComposition({ contentTypeId: contentTypeId }); + } + } + } const umbContentTypePropertyComponent = { @@ -41,6 +48,7 @@ onEdit: '&', onRemove: '&', onChangeSortOrderValue: '&', + onClickComposition: '&?', valServerFieldAlias: '@', valServerFieldLabel: '@', valTabAlias: '@' diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypetab.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypetab.component.js index 6c0e5c8baf..5fb8297847 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypetab.component.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/contenttype/umbcontenttypetab.component.js @@ -59,9 +59,9 @@ } } - function clickComposition (documentTypeId) { + function clickComposition(contentTypeId) { if (vm.onClickComposition) { - vm.onClickComposition({documentTypeId: documentTypeId}); + vm.onClickComposition({ contentTypeId: contentTypeId}); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js index c07777ca60..2a65c67a8d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js @@ -8,22 +8,6 @@ var evts = []; scope.allowChangeMediaType = false; - scope.loading = true; - - scope.changeContentPageNumber = changeContentPageNumber; - scope.contentOptions = {}; - scope.contentOptions.entityType = "DOCUMENT"; - scope.hasContentReferences = false; - - scope.changeMediaPageNumber = changeMediaPageNumber; - scope.mediaOptions = {}; - scope.mediaOptions.entityType = "MEDIA"; - scope.hasMediaReferences = false; - - scope.changeMemberPageNumber = changeMemberPageNumber; - scope.memberOptions = {}; - scope.memberOptions.entityType = "MEMBER"; - scope.hasMemberReferences = false; function onInit() { @@ -110,45 +94,6 @@ setMediaExtension(); }); - function changeContentPageNumber(pageNumber) { - scope.contentOptions.pageNumber = pageNumber; - loadContentRelations(); - } - - function changeMediaPageNumber(pageNumber) { - scope.mediaOptions.pageNumber = pageNumber; - loadMediaRelations(); - } - - function changeMemberPageNumber(pageNumber) { - scope.memberOptions.pageNumber = pageNumber; - loadMemberRelations(); - } - - function loadContentRelations() { - return mediaResource.getPagedReferences(scope.node.id, scope.contentOptions) - .then(function (data) { - scope.contentReferences = data; - scope.hasContentReferences = data.items.length > 0; - }); - } - - function loadMediaRelations() { - return mediaResource.getPagedReferences(scope.node.id, scope.mediaOptions) - .then(data => { - scope.mediaReferences = data; - scope.hasMediaReferences = data.items.length > 0; - }); - } - - function loadMemberRelations() { - return mediaResource.getPagedReferences(scope.node.id, scope.memberOptions) - .then(data => { - scope.memberReferences = data; - scope.hasMemberReferences = data.items.length > 0; - }); - } - //ensure to unregister from all events! scope.$on('$destroy', function () { for (var e in evts) { @@ -157,18 +102,6 @@ }); onInit(); - - // load media type references when the 'info' tab is first activated/switched to - evts.push(eventsService.on("app.tabChange", function (event, args) { - $timeout(function () { - if (args.alias === "umbInfo") { - - $q.all([loadContentRelations(), loadMediaRelations(), loadMemberRelations()]).then(function () { - scope.loading = false; - }); - } - }); - })); } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js new file mode 100644 index 0000000000..15c1b4aedf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js @@ -0,0 +1,124 @@ +(function () { + 'use strict'; + + /** + * A component to render the tracked references of an item + */ + + function umbTrackedReferencesController($q, trackedReferencesResource, localizationService) { + + var vm = this; + + vm.changeReferencesPageNumber = changeReferencesPageNumber; + vm.changeDescendantsPageNumber = changeDescendantsPageNumber; + + vm.$onInit = onInit; + + function onInit() { + + vm.referencesTitle = this.hideNoneDependencies ? "The following items depend on this" : "Referenced by the following items"; + vm.referencedDescendantsTitle = this.hideNoneDependencies ? "The following descending items have dependencies" : "The following descendant items have dependencies"; + localizationService.localize(this.hideNoneDependencies ? "references_labelDependsOnThis" : "references_labelUsedByItems").then(function (value) { + vm.referencesTitle = value; + }); + + localizationService.localize(this.hideNoneDependencies ? "references_labelDependentDescendants" : "references_labelUsedDescendants").then(function (value) { + vm.referencedDescendantsTitle = value; + }); + + vm.descendantsOptions = {}; + vm.descendantsOptions.filterMustBeIsDependency = this.hideNoneDependencies; + vm.hasReferencesInDescendants = false; + + vm.referencesOptions = {}; + vm.referencesOptions.filterMustBeIsDependency = this.hideNoneDependencies; + vm.hasReferences = false; + + this.loading = true; + this.hideNoResult = this.hideNoResult || false; + + // when vm.id == 0 it means that this is a new item, so it has no references yet + if (vm.id === 0) { + vm.loading = false; + if(vm.onLoadingComplete) { + vm.onLoadingComplete(); + } + return; + } + + // Make array of promises to load: + var promises = [loadReferencesRelations()]; + + // only load descendants if we want to show them. + if (vm.showDescendants) { + promises.push(loadDescendantsUsage()); + } + + $q.all(promises).then(function () { + vm.loading = false; + if(vm.onLoadingComplete) { + vm.onLoadingComplete(); + } + }); + } + + function changeReferencesPageNumber(pageNumber) { + vm.referencesOptions.pageNumber = pageNumber; + loadReferencesRelations(); + } + + function changeDescendantsPageNumber(pageNumber) { + vm.descendantsOptions.pageNumber = pageNumber; + loadDescendantsUsage(); + } + + function loadReferencesRelations() { + return trackedReferencesResource.getPagedReferences(vm.id, vm.referencesOptions) + .then(function (data) { + vm.references = data; + + if (data.items.length > 0) { + vm.hasReferences = data.items.length > 0; + activateWarning(); + } + }); + } + + function loadDescendantsUsage() { + return trackedReferencesResource.getPagedDescendantsInReferences(vm.id, vm.descendantsOptions) + .then(function (data) { + vm.referencedDescendants = data; + + if (data.items.length > 0) { + vm.hasReferencesInDescendants = data.items.length > 0; + activateWarning(); + } + }); + } + + function activateWarning() { + if (vm.onWarning) { + vm.onWarning(); + } + } + } + + var umbTrackedReferencesComponent = { + templateUrl: 'views/components/references/umb-tracked-references.html', + transclude: true, + bindings: { + id: "<", + hideNoResult: " s.id); + + return trackedReferencesResource.getPagedReferencedItems(ids, vm.referencesOptions) + .then(function (data) { + vm.referencedItems = data; + + if (data.items.length > 0) { + vm.hasReferences = data.items.length > 0; + activateWarning(); + } + }); + } + + function activateWarning() { + if (vm.onWarning) { + vm.onWarning(); + } + } + } + + var umbTrackedReferencesBulkActionComponent = { + templateUrl: 'views/components/references/umb-tracked-references-bulk-action.html', + transclude: true, + bindings: { + selection: "<", + hideNoResult: " { + scope.openContentType = (contentTypeId) => { const editor = { - id: documentTypeId, + id: contentTypeId, submit: () => { const args = { node: scope.model }; eventsService.emit("editors.documentType.reload", args); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbimagelazyload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbimagelazyload.directive.js index 988f8fab24..81c2cf2533 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbimagelazyload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbimagelazyload.directive.js @@ -36,19 +36,23 @@ Use this directive to lazy-load an image only when it is scrolled into view. function ImageLazyLoadDirective() { - const placeholder = "assets/img/transparent.png"; + const placeholder = "assets/img/transparent.png"; - function link(scope, element, attrs) { + function link(scope, element, attrs) { const observer = new IntersectionObserver(loadImg); const img = element[0]; img.src = placeholder; + img.classList.add("lazy"); observer.observe(img); - function loadImg(changes) { - changes.forEach(change => { - if (change.intersectionRatio > 0 && change.target.src.indexOf(placeholder) > 0) { - change.target.src = attrs.umbImageLazyLoad; + function loadImg(entries) { + entries.forEach(entry => { + if (entry.intersectionRatio > 0 && entry.target.src.indexOf(placeholder) > 0) { + let lazyImage = entry.target; + lazyImage.src = attrs.umbImageLazyLoad; + lazyImage.classList.add("loaded"); + observer.unobserve(lazyImage); } }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsBlockCard.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsBlockCard.filter.js new file mode 100644 index 0000000000..b461315fc7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsBlockCard.filter.js @@ -0,0 +1,49 @@ +/** + * @ngdoc filter + * @name umbraco.filters.filter:umbCmsBlockCard + * @namespace umbCmsBlockCard + * + * @description + * Filter block cards based on specific properties. + * + */ +angular.module("umbraco.filters").filter('umbCmsBlockCard', function () { + return function (array, searchTerm) { + // If no array is given, exit. + if (!array) { + return; + } + // If no search term exists, return the array unfiltered. + else if (!searchTerm) { + return array; + } + // Otherwise, continue. + else { + // Convert filter text to lower case. + const term = searchTerm.toLowerCase(); + + // Return the filtered array + return array.filter((block, i) => { + const props = ['id', 'key', 'udi', 'alias', 'name', 'description']; + + let found = false; + + for (let i = 0; i < props.length; i++) { + + if (!block.elementTypeModel.hasOwnProperty(props[i])) { + continue; + } + + if (block.elementTypeModel[props[i]] != null && + block.elementTypeModel[props[i]] !== '' && + block.elementTypeModel[props[i]].toString().toLowerCase().includes(term)) { + found = true; + } + } + + return found; + + }) + } + } +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 87ba054675..d426dcd516 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -3,7 +3,7 @@ * @name umbraco.resources.mediaResource * @description Loads in data for media **/ -function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { +function mediaResource($q, $http, umbDataFormatter, umbRequestHelper, trackedReferencesResource) { /** internal method process the saving of data and post processing the result */ function saveMediaItem(content, action, files) { @@ -552,36 +552,41 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to retrieve media items for search: ' + query); }, + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getPagedReferences + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a page list of tracked references for the current item, so you can see where an item is being used + * + * @deprecated + * Use umbraco.resources.trackedReferencesResource#getPagedReferences instead + * + * ##usage + *
+         * var options = {
+         *      pageSize : 25,
+         *      pageNumber : 1,
+         *      entityType : 'DOCUMENT'
+         *  };
+         * trackedReferencesResource.getPagedReferences(1, options)
+         *    .then(function(data) {
+         *        console.log(data);
+         *    });
+         * 
+ * + * @param {int} id Id of the item to query for tracked references + * @param {Object} args optional args object + * @param {Int} args.pageSize the pagesize of the returned list (default 25) + * @param {Int} args.pageNumber the current page index (default 1) + * @param {Int} args.entityType the type of tracked entity (default : DOCUMENT). Possible values DOCUMENT, MEDIA + * @returns {Promise} resourcePromise object. + * + */ getPagedReferences: function (id, options) { - - var defaults = { - pageSize: 25, - pageNumber: 1, - entityType: "DOCUMENT" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - Utilities.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetPagedReferences", - { - id: id, - entityType: options.entityType, - pageNumber: options.pageNumber, - pageSize: options.pageSize - } - )), - "Failed to retrieve usages for media of id " + id); + return trackedReferencesResource.getPagedReferences(id, options); } - }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js new file mode 100644 index 0000000000..d64951a6d0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js @@ -0,0 +1,178 @@ +/** + * @ngdoc service + * @name umbraco.resources.trackedReferencesResource + * @description Loads in data for tracked references + **/ +function trackedReferencesResource($q, $http, umbRequestHelper) { + + return { + + /** + * @ngdoc method + * @name umbraco.resources.trackedReferencesResource#getPagedReferences + * @methodOf umbraco.resources.trackedReferencesResource + * + * @description + * Gets a page list of tracked references for the current item, so you can see where an item is being used + * + * ##usage + *
+         * var options = {
+         *      pageSize : 25,
+         *      pageNumber : 1,
+         *      entityType : 'DOCUMENT'
+         *  };
+         * trackedReferencesResource.getPagedReferences(1, options)
+         *    .then(function(data) {
+         *        console.log(data);
+         *    });
+         * 
+ * + * @param {int} id Id of the item to query for tracked references + * @param {Object} args optional args object + * @param {Int} args.pageSize the pagesize of the returned list (default 25) + * @param {Int} args.pageNumber the current page index (default 1) + * @param {String} args.entityType the type of tracked entity (default : DOCUMENT). Possible values DOCUMENT, MEDIA + * @returns {Promise} resourcePromise object. + * + */ + getPagedReferences: function (id, args) { + + var defaults = { + pageSize: 10, + pageNumber: 1, + entityType: "DOCUMENT" + }; + if (args === undefined) { + args = {}; + } + + //overwrite the defaults if there are any specified + var options = Utilities.extend(defaults, args); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "trackedReferencesApiBaseUrl", + "GetPagedReferences", + { + id: id, + entityType: options.entityType, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + filterMustBeIsDependency: options.filterMustBeIsDependency + } + )), + "Failed to retrieve usages for entity of id " + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.trackedReferencesResource#getPagedDescendantsInReferences + * @methodOf umbraco.resources.trackedReferencesResource + * + * @description + * Gets a page list of the child nodes of the current item used in any kind of relation + * + * ##usage + *
+         * var options = {
+         *      pageSize : 25,
+         *      pageNumber : 1,
+         *      entityType : 'DOCUMENT'
+         *  };
+         * trackedReferencesResource.getPagedDescendantsInReferences(1, options)
+         *    .then(function(data) {
+         *        console.log(data);
+         *    });
+         * 
+ * + * @param {int} id Id of the item to query for child nodes used in relation + * @param {Object} args optional args object + * @param {Int} args.pageSize the pagesize of the returned list (default 25) + * @param {Int} args.pageNumber the current page index (default 1) + * @param {String} args.entityType the type of tracked entity (default : DOCUMENT). Possible values DOCUMENT, MEDIA + * @returns {Promise} resourcePromise object. + * + */ + getPagedDescendantsInReferences: function (id, args) { + + var defaults = { + pageSize: 10, + pageNumber: 1, + entityType: "DOCUMENT" + }; + if (args === undefined) { + args = {}; + } + + //overwrite the defaults if there are any specified + var options = Utilities.extend(defaults, args); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "trackedReferencesApiBaseUrl", + "GetPagedDescendantsInReferences", + { + parentId: id, + entityType: options.entityType, + pageNumber: options.pageNumber, + pageSize: options.pageSize + } + )), + "Failed to retrieve usages for descendants of parent with id " + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.trackedReferencesResource#getPagedReferencedItems + * @methodOf umbraco.resources.trackedReferencesResource + * + * @description + * Checks if any of the items are used in a relation and returns a page list, so you can see which items are being used + * + * ##usage + *
+         * var ids = [123,3453,2334,2343];
+         * var options = {
+         *      pageSize : 25,
+         *      pageNumber : 1,
+         *      entityType : 'DOCUMENT'
+         *  };
+         *
+         * trackedReferencesResource.getPagedReferencedItems(ids, options)
+         *    .then(function(data) {
+         *        console.log(data);
+         *    });
+         * 
+ * + * @param {Array} ids array of the selected items ids to query for references + * @param {Object} options optional options object + * @param {Int} options.pageSize the pagesize of the returned list (default 25) + * @param {Int} options.pageNumber the current page index (default 1) + * @param {String} options.entityType the type of tracked entity (default : DOCUMENT). Possible values DOCUMENT, MEDIA + * @returns {Promise} resourcePromise object. + * + */ + getPagedReferencedItems: function (ids, options) { + var query = `entityType=${options.entityType}&pageNumber=${options.pageNumber}&pageSize=${options.pageSize}`; + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "trackedReferencesApiBaseUrl", + "GetPagedReferencedItems", + query), + { + ids: ids, + entityType: options.entityType, + pageNumber: options.pageNumber, + pageSize: options.pageSize + }), + "Failed to check for references of nodes with ids " + ids); + } + } +} + +angular.module('umbraco.resources').factory('trackedReferencesResource', trackedReferencesResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediapreview.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediapreview.service.js index b922e07c9c..5e5aeed872 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediapreview.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediapreview.service.js @@ -1,4 +1,4 @@ -/** +/** * @ngdoc service * @name umbraco.services.mediaPreview * @description A service providing views used for dealing with previewing files. @@ -56,12 +56,15 @@ function mediaPreview() { */ getMediaPreview: function (fileExtension) { - fileExtension = fileExtension.toLowerCase(); + if (fileExtension) + { + fileExtension = fileExtension.toLowerCase(); - var previewObject = _mediaPreviews.find((preview) => preview.fileExtensions.indexOf(fileExtension) !== -1); + var previewObject = _mediaPreviews.find(preview => preview.fileExtensions.indexOf(fileExtension) !== -1); - if(previewObject !== undefined) { + if (previewObject !== undefined) { return previewObject.view; + } } return DEFAULT_FILE_PREVIEW; 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 e6eb430201..98543a7c68 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 @@ -11,7 +11,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce - var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style|lang]"; + var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style|lang],figure,figcaption"; var fallbackStyles = [{ title: "Page header", block: "h2" }, { title: "Section header", block: "h3" }, { title: "Paragraph header", block: "h4" }, { title: "Normal", block: "p" }, { title: "Quote", block: "blockquote" }, { title: "Code", block: "code" }]; // these languages are available for localization var availableLanguages = [ @@ -292,7 +292,6 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } function sizeImageInEditor(editor, imageDomElement, imgUrl) { - var size = editor.dom.getSize(imageDomElement); if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { @@ -656,21 +655,19 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s stateSelector: 'img[data-udi]', onclick: function () { - - var selectedElm = editor.selection.getNode(), - currentTarget, - imgDomElement; + var selectedElm = editor.selection.getNode(), + currentTarget; if (selectedElm.nodeName === 'IMG') { var img = $(selectedElm); - imgDomElement = selectedElm; var hasUdi = img.attr("data-udi") ? true : false; var hasDataTmpImg = img.attr("data-tmpimg") ? true : false; currentTarget = { altText: img.attr("alt"), - url: img.attr("src") + url: img.attr("src"), + caption: img.attr('data-caption') }; if (hasUdi) { @@ -687,85 +684,80 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s userService.getCurrentUser().then(function (userData) { if (callback) { angularHelper.safeApply($rootScope, function() { - callback(currentTarget, userData, imgDomElement); + callback(currentTarget, userData); }); } }); } }); }, - - insertMediaInEditor: function (editor, img, imgDomElement) { + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#insetMediaInEditor + * @methodOf umbraco.services.tinyMceService + * + * @description + * Inserts the image element in tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + */ + insertMediaInEditor: function (editor, img) { if (img) { - // imgElement is only definied if updating an image - // if null/undefinied then its a BRAND new image - if(imgDomElement){ - // Check if the img src has changed - // If it has we will need to do some resizing/recalc again - var hasImageSrcChanged = false; - - if(img.url !== editor.dom.getAttrib(imgDomElement, "src")){ - hasImageSrcChanged = true; + // We need to create a NEW DOM element to insert + // setting an attribute of ID to __mcenew, so we can gather a reference to the node, to be able to update its size accordingly to the size of the image. + var data = { + alt: img.altText || "", + src: (img.url) ? img.url : "nothing.jpg", + id: "__mcenew", + "data-udi": img.udi, + "data-caption": img.caption + }; + var newImage = editor.dom.createHTML('img', data); + var parentElement = editor.selection.getNode().parentElement; + + if (img.caption) { + var figCaption = editor.dom.createHTML('figcaption', {}, img.caption); + var combined = newImage + figCaption; + + if (parentElement.nodeName !== 'FIGURE') { + var fragment = editor.dom.createHTML('figure', {}, combined); + editor.selection.setContent(fragment); } - - // If null/undefinied it will remove the attribute - editor.dom.setAttrib(imgDomElement, "alt", img.altText); - - // It's possible to pick a NEW image - so need to ensure this gets updated - if(img.udi){ - editor.dom.setAttrib(imgDomElement, "data-udi", img.udi); + else { + parentElement.innerHTML = combined; } - - // It's possible to pick a NEW image - so need to ensure this gets updated - if(img.url){ - editor.dom.setAttrib(imgDomElement, "src", img.url); - } - - // Remove width & height attributes (ONLY if imgSrc changed) - // So native image size is used as this needed to re-calc width & height - // For the function sizeImageInEditor() & apply the image resizing querystrings etc.. - if(hasImageSrcChanged){ - editor.dom.setAttrib(imgDomElement, "width", null); - editor.dom.setAttrib(imgDomElement, "height", null); - - //Re-calc the image dimensions - sizeImageInEditor(editor, imgDomElement, img.url); - } - - } else{ - // We need to create a NEW DOM element to insert - // setting an attribute of ID to __mcenew, so we can gather a reference to the node, to be able to update its size accordingly to the size of the image. - var data = { - alt: img.altText || "", - src: (img.url) ? img.url : "nothing.jpg", - id: "__mcenew", - "data-udi": img.udi - }; - - editor.selection.setContent(editor.dom.createHTML('img', data)); - - // Using settimeout to wait for a DoM-render, so we can find the new element by ID. - $timeout(function () { - - var imgElm = editor.dom.get("__mcenew"); - editor.dom.setAttrib(imgElm, "id", null); - - // When image is loaded we are ready to call sizeImageInEditor. - var onImageLoaded = function() { - sizeImageInEditor(editor, imgElm, img.url); - editor.fire("Change"); - } - - // Check if image already is loaded. - if(imgElm.complete === true) { - onImageLoaded(); - } else { - imgElm.onload = onImageLoaded; - } - - }); - } + else { + //if caption is removed, remove the figure element + if (parentElement.nodeName === 'FIGURE') { + parentElement.parentElement.innerHTML = newImage; + } + else { + editor.selection.setContent(newImage); + } + } + + // Using settimeout to wait for a DoM-render, so we can find the new element by ID. + $timeout(function () { + + var imgElm = editor.dom.get("__mcenew"); + editor.dom.setAttrib(imgElm, "id", null); + + // When image is loaded we are ready to call sizeImageInEditor. + var onImageLoaded = function() { + sizeImageInEditor(editor, imgElm, img.url); + editor.fire("Change"); + } + + // Check if image already is loaded. + if(imgElm.complete === true) { + onImageLoaded(); + } else { + imgElm.onload = onImageLoaded; + } + + }); + } }, 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 7e4d7eaa4a..38b56b7661 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 @@ -154,7 +154,7 @@ formatUserPostData: function (displayModel) { //create the save model from the display model - var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups', 'message'); + var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups', 'message', 'key'); //make sure the userGroups are just a string array var currGroups = saveModel.userGroups; @@ -461,6 +461,7 @@ alias: relationType.alias, key: relationType.key, isBidirectional: relationType.isBidirectional, + isDependency: relationType.isDependency, parentObjectType: relationType.parentObjectType, childObjectType: relationType.childObjectType }; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less index 274f74bbac..85dbb05a1b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less @@ -69,7 +69,8 @@ // and also use a loop to build editor sizes - easily extended with new sizes by adding to the map @editorSizes: small 500px, - medium 800px; + medium 800px, + large 1600px; .create-editor-sizes(@iterator:1) when(@iterator <= length(@editorSizes)) { .umb-editor { @@ -92,6 +93,11 @@ .create-editor-sizes(); +.umb-editor--large { + max-width: 1600px; + width: calc(100% - 50px); +} + .umb-editor__overlay { .absolute(); background: rgba(0,0,0,0.4); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 12cce286d6..60020066c0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -10,6 +10,10 @@ .scoped-view{ display: none; } + + .abstract { + margin-bottom : 20px; + } } .umb-overlay__form { @@ -51,7 +55,7 @@ } .umb-overlay__title { - font-size: 16px; + font-size: 20px; color: @black; line-height: 16px; font-weight: bold; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index a39a38fbde..1f61b7cfc2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -252,6 +252,7 @@ body.touch .umb-tree { // Tree item states // ------------------------- .not-published { + > .umb-tree-item__inner > .umb-icon, > .umb-tree-item__inner > i.icon, > .umb-tree-item__inner > a { opacity: 0.6; @@ -259,6 +260,7 @@ body.touch .umb-tree { } .not-allowed { + > .umb-tree-item__inner > .umb-icon, > .umb-tree-item__inner > i.icon, > .umb-tree-item__inner > a { cursor: not-allowed; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index cae8a1c253..486c0f1336 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -714,7 +714,7 @@ input.umb-group-builder__group-title-input:disabled:hover { } .umb-group-builder__property-tags.-right { - right: 0; + right: 4px; left: auto; } 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 6c1e5058d2..73ef47133c 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 @@ -162,6 +162,7 @@ input.umb-table__input { font-size: 14px; font-weight: bold; text-decoration: none; + cursor: pointer; &:hover { color: @ui-option-type-hover; diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 43911fccb1..d46a1751b2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -14,6 +14,20 @@ box-shadow: 3px 0px 7px rgba(0,0,0,0.16); } +img:not([src]):not([srcset]) { + visibility: hidden; +} + +img.lazy { + -webkit-transition: opacity 1.2s cubic-bezier(0.16, 1.08, 0.38, 0.98); + transition: opacity 1.2s cubic-bezier(0.16, 1.08, 0.38, 0.98); + opacity: 0; +} + +img.lazy.loaded { + opacity: 1; +} + .umb-scrollable, .umb-auto-overflow { overflow: auto; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html index 6f32e89988..c14110437d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html @@ -128,13 +128,13 @@
- +
- Visit umbraco.tv + Watch our free tutorial videos
- The best Umbraco video tutorials + on the Umbraco Learning Base
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html index 273599343d..8ffb0f42a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html @@ -27,17 +27,23 @@
-
+
+ + + Sorry, we can not find what you are looking for. + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.controller.js index eea8e87034..732127ffa0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.controller.js @@ -3,25 +3,35 @@ angular.module("umbraco") function ($scope, localizationService, entityResource, editorService, overlayService, eventsService, mediaHelper) { var unsubscribe = []; - var vm = this; - + + const vm = this; + vm.loading = true; vm.model = $scope.model; vm.mediaEntry = vm.model.mediaEntry; vm.currentCrop = null; - - localizationService.localizeMany([ - vm.model.createFlow ? "general_cancel" : "general_close", - vm.model.createFlow ? "general_create" : "buttons_submitChanges" - ]).then(function (data) { - vm.closeLabel = data[0]; - vm.submitLabel = data[1]; - }); - vm.title = ""; + + vm.focalPointChanged = focalPointChanged; + vm.onImageLoaded = onImageLoaded; + vm.openMedia = openMedia; + vm.repickMedia = repickMedia; + vm.selectCrop = selectCrop; + vm.deselectCrop = deselectCrop; + vm.resetCrop = resetCrop; + vm.submitAndClose = submitAndClose; + vm.close = close; function init() { + localizationService.localizeMany([ + vm.model.createFlow ? "general_cancel" : "general_close", + vm.model.createFlow ? "general_create" : "buttons_submitChanges" + ]).then(data => { + vm.closeLabel = data[0]; + vm.submitLabel = data[1]; + }); + updateMedia(); unsubscribe.push(eventsService.on("editors.media.saved", function(name, args) { @@ -35,6 +45,7 @@ angular.module("umbraco") function updateMedia() { vm.loading = true; + entityResource.getById(vm.mediaEntry.mediaKey, "Media").then(function (mediaEntity) { vm.media = mediaEntity; vm.imageSrc = mediaHelper.resolveFileFromEntity(mediaEntity, true); @@ -44,11 +55,11 @@ angular.module("umbraco") vm.hasDimensions = false; vm.isCroppable = false; - localizationService.localize("mediaPicker_editMediaEntryLabel", [vm.media.name, vm.model.documentName]).then(function (data) { + localizationService.localize("mediaPicker_editMediaEntryLabel", [vm.media.name, vm.model.documentName]).then(data => { vm.title = data; }); }, function () { - localizationService.localize("mediaPicker_deletedItem").then(function (localized) { + localizationService.localize("mediaPicker_deletedItem").then(localized => { vm.media = { name: localized, icon: "icon-picture", @@ -60,15 +71,12 @@ angular.module("umbraco") }); }); } - - vm.onImageLoaded = onImageLoaded; + function onImageLoaded(isCroppable, hasDimensions) { vm.isCroppable = isCroppable; vm.hasDimensions = hasDimensions; - }; - - - vm.repickMedia = repickMedia; + } + function repickMedia() { vm.model.propertyEditor.changeMediaFor(vm.model.mediaEntry, onMediaReplaced); } @@ -84,24 +92,23 @@ angular.module("umbraco") // updateMedia(); } - - vm.openMedia = openMedia; + function openMedia() { - var mediaEditor = { + const mediaEditor = { id: vm.mediaEntry.mediaKey, - submit: function () { + submit: () => { editorService.close(); }, - close: function () { + close: () => { editorService.close(); } }; + editorService.mediaEditor(mediaEditor); } - - vm.focalPointChanged = function(left, top) { + function focalPointChanged(left, top) { //update the model focalpoint value vm.mediaEntry.focalPoint = { left: left, @@ -111,22 +118,17 @@ angular.module("umbraco") //set form to dirty to track changes setDirty(); } - - - - vm.selectCrop = selectCrop; + function selectCrop(targetCrop) { vm.currentCrop = targetCrop; setDirty(); // TODO: start watchin values of crop, first when changed set to dirty. - }; - - vm.deselectCrop = deselectCrop; + } + function deselectCrop() { vm.currentCrop = null; - }; - - vm.resetCrop = resetCrop; + } + function resetCrop() { if (vm.currentCrop) { $scope.$evalAsync( () => { @@ -140,18 +142,22 @@ angular.module("umbraco") vm.imageCropperForm.$setDirty(); } - - vm.submitAndClose = function () { + function submitAndClose() { if (vm.model && vm.model.submit) { vm.model.submit(vm.model); } } - - vm.close = function () { - if (vm.model && vm.model.close) { - if (vm.model.createFlow === true || vm.imageCropperForm.$dirty === true) { - var labels = vm.model.createFlow === true ? ["mediaPicker_confirmCancelMediaEntryCreationHeadline", "mediaPicker_confirmCancelMediaEntryCreationMessage"] : ["prompt_discardChanges", "mediaPicker_confirmCancelMediaEntryHasChanges"]; - localizationService.localizeMany(labels).then(function (localizations) { + + function close() { + if (vm.model && vm.model.close) + { + if (vm.model.createFlow === true || vm.imageCropperForm.$dirty === true) + { + const labelKeys = vm.model.createFlow === true + ? ["mediaPicker_confirmCancelMediaEntryCreationHeadline", "mediaPicker_confirmCancelMediaEntryCreationMessage"] + : ["prompt_discardChanges", "mediaPicker_confirmCancelMediaEntryHasChanges"]; + + localizationService.localizeMany(labelKeys).then(localizations => { const confirm = { title: localizations[0], view: "default", @@ -159,11 +165,11 @@ angular.module("umbraco") submitButtonLabelKey: "general_discard", submitButtonStyle: "danger", closeButtonLabelKey: "prompt_stay", - submit: function () { + submit: () => { overlayService.close(); vm.model.close(vm.model); }, - close: function () { + close: () => { overlayService.close(); } }; @@ -177,6 +183,7 @@ angular.module("umbraco") } init(); + $scope.$on("$destroy", function () { unsubscribe.forEach(x => x()); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 381880da2e..1c7545f9ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -20,6 +20,9 @@ + + + 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 fff779e626..9166f0c8ab 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 @@ -3,147 +3,144 @@
- -
+ +
-
{{ vm.property.alias }}
- - +
{{ vm.property.alias }}
+ + -
- -
-
{{propertyTypeForm.groupName.errorMsg}}
-
Required label
-
- -
- -
- -
+
+
{{propertyTypeForm.groupName.errorMsg}}
+
Required label
- -
- - {{ vm.property.label }} - ({{ vm.property.alias }}) - +
+ +
+ +
+
+ +
+ + {{ vm.property.label }} + ({{ vm.property.alias }}) + +
-
+
- - {{vm.property.dataTypeName}} - Preview - - -
- * - Mandatory -
- -
- - Show on member profile -
- -
- - Member can edit -
- -
- - Is sensitive data -
- -
- - Vary by culture -
- -
- - Vary by segments -
+ + {{vm.property.dataTypeName}} + Preview + +
+ * + Mandatory
-
- -
- - Inherited from - {{vm.property.contentTypeName}} -
- -
- - Locked -
- +
+ + Show on member profile
- - - - +
+ + Member can edit +
- +
+ + Is sensitive data +
+ +
+ + Vary by culture +
+ +
+ + Vary by segments +
+ +
+ +
+
+ + Inherited from + +
+ +
+ + Locked +
+
+ + + + + + +
-
- - -
- -
- - -
- -
+
+ +
+
+ +
+ +
+ +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editors.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editors.html index 4d6ceb2ffc..8443465e25 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editors.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editors.html @@ -6,6 +6,7 @@ ng-attr-inert="{{$last ? undefined : true}}" ng-class="{'umb-editor--small': model.size === 'small', 'umb-editor--medium': model.size === 'medium', + 'umb-editor--large': model.size === 'large', 'umb-editor--animating': model.animating, 'umb-editor--notInFront': model.inFront !== true, 'umb-editor--infiniteMode': model.infiniteMode, 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 1e72950410..010102b920 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 @@ -3,7 +3,7 @@ -
+
@@ -27,131 +27,11 @@ - - - - - - This Media item has no references. - - - - - -
- -
- -
- Used in Documents -
- -
-
-
-
-
Name
-
Alias
-
Open
-
-
-
-
-
-
{{::reference.name}}
-
{{::reference.alias}}
- -
-
-
- - -
- - -
-
- - -
- -
- Used in Members -
- -
-
-
-
-
Name
-
Alias
-
Open
-
-
-
-
-
-
{{::reference.name}}
-
{{::reference.alias}}
- -
-
-
- - -
- - -
-
- - -
- -
- Used in Media -
- -
-
-
-
-
Name
-
Alias
-
Open
-
-
-
-
-
-
{{::reference.name}}
-
{{::reference.alias}}
- -
-
-
- - -
- - -
-
- -
+
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-bulk-action.html b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-bulk-action.html new file mode 100644 index 0000000000..8d3c36f196 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-bulk-action.html @@ -0,0 +1,13 @@ + + +
+ +
+ + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html new file mode 100644 index 0000000000..d09bc23318 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html @@ -0,0 +1,35 @@ +
+
+ {{vm.headline}} +
+ +
+
+
+
+
Node Name
+
Type Name
+
Type
+
Relation
+
+
+
+
+
+ +
{{::reference.contentTypeName}}
+
{{::reference.type}}
+
{{::reference.relationTypeName}}
+
+
+
+ + +
+ + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html new file mode 100644 index 0000000000..9e08c5fbae --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html @@ -0,0 +1,36 @@ + + + + + + + This item is not referenced + + + + +
+ + +
+ + +
+ +
+ + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html index 3575453bd8..c35e1ee45a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html @@ -22,7 +22,7 @@ tab="tab" is-open="tab.alias === openTabAlias" on-click="changeTab(tab)" - on-click-composition="openDocumentType(documentTypeId)" + on-click-composition="openContentType(contentTypeId)" sorting="sortingMode" allow-remove="canRemoveTab(tab) && !sortingMode" on-remove="removeTab(tab, tabIndex)" @@ -105,6 +105,7 @@ sortable="sortingMode" on-edit="editPropertyTypeSettings(property)" on-remove="deleteProperty(tab.properties, property)" + on-click-composition="openContentType(contentTypeId)" val-server-field-alias="{{'Groups[' + tab.serverValidationIndex + '].Properties[' + propertyIndex + '].Alias'}}" val-server-field-label="{{'Groups[' + tab.serverValidationIndex + '].Properties[' + propertyIndex + '].Label'}}" on-change-sort-order-value="onChangePropertySortOrderValue(tab)" @@ -143,7 +144,7 @@ allow-name="true" group="group" allow-remove="canRemoveGroup(group) && !sortingMode" - on-click-composition="openDocumentType(documentTypeId)" + on-click-composition="openContentType(contentTypeId)" on-remove="removeGroup(group)" on-update-name="onChangeGroupName(group)" sorting="sortingMode" @@ -159,6 +160,7 @@ sortable="sortingMode" on-edit="editPropertyTypeSettings(property)" on-remove="deleteProperty(group.properties, property)" + on-click-composition="openContentType(contentTypeId)" on-change-sort-order-value="onChangePropertySortOrderValue(group)" val-server-field-alias="{{'Groups[' + group.serverValidationIndex + '].Properties[' + propertyIndex + '].Alias'}}" val-server-field-label="{{'Groups[' + group.serverValidationIndex + '].Properties[' + propertyIndex + '].Label'}}" diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js index 060c0ec7d9..771d1b9280 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js @@ -6,7 +6,7 @@ * @description * The controller for deleting content */ -function ContentDeleteController($scope, $timeout, contentResource, treeService, navigationService, editorState, $location, overlayService, languageResource) { +function ContentDeleteController($scope, $timeout, contentResource, treeService, navigationService, editorState, $location, overlayService, languageResource, localizationService) { /** * Used to toggle UI elements during delete operations @@ -16,6 +16,10 @@ function ContentDeleteController($scope, $timeout, contentResource, treeService, $scope.currentNode.loading = isDeleting; $scope.busy = isDeleting; } + + $scope.checkingReferences = true; + $scope.warningText = null; + $scope.disableDelete = false; $scope.performDelete = function() { @@ -77,6 +81,27 @@ function ContentDeleteController($scope, $timeout, contentResource, treeService, }; + $scope.checkingReferencesComplete = () => { + $scope.checkingReferences = false; + }; + + $scope.onReferencesWarning = () => { + // check if the deletion of items that have references has been disabled + if (Umbraco.Sys.ServerVariables.umbracoSettings.disableDeleteWhenReferenced) { + // this will only be set to true if we have a warning, indicating that this item or its descendants have reference + $scope.disableDelete = true; + + localizationService.localize("references_deleteDisabledWarning").then((value) => { + $scope.warningText = value; + }); + } + else { + localizationService.localize("references_deleteWarning").then((value) => { + $scope.warningText = value; + }); + } + }; + $scope.cancel = function() { toggleDeleting(false); $scope.close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/delete.html b/src/Umbraco.Web.UI.Client/src/views/content/delete.html index 316d0669c4..765dbda531 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/delete.html @@ -1,7 +1,7 @@ -
-
+
+
-
+
{{currentNode.name}} was deleted @@ -9,20 +9,28 @@
-
-

+

+

Are you sure you want to delete {{currentNode.name}}?

-
+
This will delete the node and all its languages. If you only want to delete one language go and unpublish it instead.
+ + +
+ {{warningText}} +
+
When items are deleted from the recycle bin, they will be gone forever.
- + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js index 936ab3b104..f6c0ea67f9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js @@ -6,10 +6,13 @@ var vm = this; var autoSelectedVariants = []; + vm.id = $scope.content.id; + vm.warningText = null; vm.changeSelection = changeSelection; function onInit() { + $scope.model.hideSubmitButton = true; vm.variants = $scope.model.variants; vm.unpublishableVariants = vm.variants.filter(publishedVariantFilter) @@ -108,6 +111,24 @@ }); }); + vm.checkingReferencesComplete = () => { + $scope.model.hideSubmitButton = false; + }; + + vm.onReferencesWarning = () => { + $scope.model.submitButtonStyle = "danger"; + + // check if the unpublishing of items that have references has been disabled + if (Umbraco.Sys.ServerVariables.umbracoSettings.disableUnpublishWhenReferenced) { + // this will only be disabled if we have a warning, indicating that this item or its descendants have reference + $scope.model.disableSubmitButton = true; + } + + localizationService.localize("references_unpublishWarning").then((value) => { + vm.warningText = value; + }); + }; + onInit(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html index a994fe38ed..b938065bc2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html @@ -49,7 +49,14 @@
-
+ +
+ +
+ +
+ {{vm.warningText}} +
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index 4216d0a57a..b33444177a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -17,7 +17,7 @@ Ask a question in the Community Forum
  • - Watch our tutorial videos (some are free, some require a subscription) + Watch our free tutorial videos on the Umbraco Learning Base
  • Find out about our productivity boosting tools and commercial support diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js index 416d2c3a65..2f6fb2d3a8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js @@ -1,4 +1,4 @@ -/** +/** * @ngdoc controller * @name Umbraco.Editors.Dictionary.EditController * @function @@ -7,128 +7,131 @@ * The controller for editing dictionary items */ function DictionaryEditController($scope, $routeParams, $location, dictionaryResource, navigationService, appState, editorState, contentEditingHelper, formHelper, notificationsService, localizationService) { - - var vm = this; - //setup scope vars - vm.nameDirty = false; - vm.header = {}; - vm.header.editorfor = "template_insertDictionaryItem"; - vm.header.setPageTitle = true; + var vm = this; - vm.page = {}; - vm.page.loading = false; - vm.page.nameLocked = false; - vm.page.menu = {}; - vm.page.menu.currentSection = appState.getSectionState("currentSection"); - vm.page.menu.currentNode = null; - vm.description = ""; - vm.showBackButton = true; - vm.maxlength = 1000; - - vm.save = saveDictionary; - vm.back = back; - vm.change = change; - - function loadDictionary() { + //setup scope vars + vm.nameDirty = false; + vm.header = {}; + vm.header.editorfor = "template_insertDictionaryItem"; + vm.header.setPageTitle = true; - vm.page.loading = true; + vm.page = {}; + vm.page.navigation = {}; + vm.page.loading = false; + vm.page.nameLocked = false; + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState("currentSection"); + vm.page.menu.currentNode = null; + vm.description = ""; + vm.showBackButton = true; + vm.maxlength = 1000; - //we are editing so get the content item from the server - dictionaryResource.getById($routeParams.id) - .then(function (data) { - bindDictionary(data); - vm.page.loading = false; - }); + vm.save = saveDictionary; + vm.back = back; + vm.change = change; + + function loadDictionary() { + + vm.page.loading = true; + + //we are editing so get the content item from the server + dictionaryResource.getById($routeParams.id) + .then(function (data) { + bindDictionary(data); + vm.page.navigation = data.apps; + data.apps[0].active = true; + vm.page.loading = false; + }); + } + + function createTranslationProperty(translation) { + return { + alias: translation.isoCode, + label: translation.displayName, + hideLabel: false } + } - function createTranslationProperty(translation) { - return { - alias: translation.isoCode, - label: translation.displayName, - hideLabel : false - } - } - - function bindDictionary(data) { - localizationService.localize("dictionaryItem_description").then(function (value) { - vm.description = value.replace("%0%", data.name); - }); - - // create data for umb-property displaying - for (var i = 0; i < data.translations.length; i++) { - data.translations[i].property = createTranslationProperty(data.translations[i]); - change(data.translations[i]); - } - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - savedContent: data - }); - - // set content - vm.content = data; - - //share state - editorState.set(vm.content); - - navigationService.syncTree({ tree: "dictionary", path: data.path, forceReload: true }).then(function (syncArgs) { - vm.page.menu.currentNode = syncArgs.node; - }); - } - - function onInit() { - loadDictionary(); - } - - function saveDictionary() { - if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - - vm.page.saveButtonState = "busy"; - - dictionaryResource.save(vm.content, vm.nameDirty) - .then(function (data) { - - formHelper.resetForm({ scope: $scope }); - - bindDictionary(data); - - vm.page.saveButtonState = "success"; - }, - function (err) { - - formHelper.resetForm({ scope: $scope, hasErrors: true }); - - contentEditingHelper.handleSaveError({ - err: err - }); - - notificationsService.error(err.data.message); - - vm.page.saveButtonState = "error"; - }); - } - } - - function back() { - $location.path(vm.page.menu.currentSection + "/dictionary/list"); - } - - function change(translation) { - if (translation.translation) { - var charsCount = translation.translation.length; - translation.nearMaxLimit = charsCount > Math.max(vm.maxlength * .8, vm.maxlength - 50); - } - } - - $scope.$watch("vm.content.name", function (newVal, oldVal) { - //when the value changes, we need to set the name dirty - if (newVal && (newVal !== oldVal) && typeof(oldVal) !== "undefined") { - vm.nameDirty = true; - } + function bindDictionary(data) { + localizationService.localize("dictionaryItem_description").then(function (value) { + vm.description = value.replace("%0%", data.name); }); - onInit(); + // create data for umb-property displaying + for (var i = 0; i < data.translations.length; i++) { + data.translations[i].property = createTranslationProperty(data.translations[i]); + change(data.translations[i]); + } + + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + savedContent: data + }); + + // set content + vm.content = data; + + //share state + editorState.set(vm.content); + + navigationService.syncTree({ tree: "dictionary", path: data.path, forceReload: true }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + + function onInit() { + loadDictionary(); + } + + function saveDictionary() { + if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { + + vm.page.saveButtonState = "busy"; + + dictionaryResource.save(vm.content, vm.nameDirty) + .then(function (data) { + + formHelper.resetForm({ scope: $scope }); + + bindDictionary(data); + + vm.page.saveButtonState = "success"; + }, + function (err) { + + formHelper.resetForm({ scope: $scope, hasErrors: true }); + + contentEditingHelper.handleSaveError({ + err: err + }); + + notificationsService.error(err.data.message); + + vm.page.saveButtonState = "error"; + }); + } + } + + function back() { + $location.path(vm.page.menu.currentSection + "/dictionary/list"); + } + + function change(translation) { + if (translation.translation) { + var charsCount = translation.translation.length; + translation.nearMaxLimit = charsCount > Math.max(vm.maxlength * .8, vm.maxlength - 50); + } + } + + $scope.$watch("vm.content.name", function (newVal, oldVal) { + //when the value changes, we need to set the name dirty + if (newVal && (newVal !== oldVal) && typeof (oldVal) !== "undefined") { + vm.nameDirty = true; + } + }); + + onInit(); } angular.module("umbraco").controller("Umbraco.Editors.Dictionary.EditController", DictionaryEditController); diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html b/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html index f59491c693..01c3a628e9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html @@ -1,63 +1,45 @@ -
    - - -
    +
    + - - - - - - - -

    - + - -
    -

    - {{ translation.displayName }} - %0% characters left. -

    -
    -
    -
    -
    -
    - - + + + - + + + + - - + - + - -
    - + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/views/content/content.html b/src/Umbraco.Web.UI.Client/src/views/dictionary/views/content/content.html new file mode 100644 index 0000000000..4e97626c4f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/views/content/content.html @@ -0,0 +1,20 @@ + + +

    + + + +
    +

    + {{ translation.displayName }} + %0% characters left. +

    +
    +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/media/delete.html b/src/Umbraco.Web.UI.Client/src/views/media/delete.html index 7231ccf2c4..894cf5ef77 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/delete.html @@ -10,15 +10,23 @@
    -

    +

    Are you sure you want to delete {{currentNode.name}}?

    + + +
    + {{warningText}} +
    +
    When items are deleted from the recycle bin, they will be gone forever.
    - + + +
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js index 8792571377..0761504b6b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js @@ -6,7 +6,11 @@ * @description * The controller for deleting content */ -function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location, overlayService) { +function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location, overlayService, localizationService) { + + $scope.checkingReferences = true; + $scope.warningText = null; + $scope.disableDelete = false; $scope.performDelete = function() { @@ -26,7 +30,7 @@ function MediaDeleteController($scope, mediaResource, treeService, navigationSer treeService.removeNode($scope.currentNode); if (rootNode) { - //ensure the recycle bin has child nodes now + //ensure the recycle bin has child nodes now var recycleBin = treeService.getDescendantNode(rootNode, -21); if (recycleBin) { recycleBin.hasChildren = true; @@ -65,6 +69,27 @@ function MediaDeleteController($scope, mediaResource, treeService, navigationSer }); }; + $scope.checkingReferencesComplete = () => { + $scope.checkingReferences = false; + }; + + $scope.onReferencesWarning = () => { + // check if the deletion of items that have references has been disabled + if (Umbraco.Sys.ServerVariables.umbracoSettings.disableDeleteWhenReferenced) { + // this will only be set to true if we have a warning, indicating that this item or its descendants have reference + $scope.disableDelete = true; + + localizationService.localize("references_deleteDisabledWarning").then((value) => { + $scope.warningText = value; + }); + } + else { + localizationService.localize("references_deleteWarning").then((value) => { + $scope.warningText = value; + }); + } + }; + $scope.close = function() { navigationService.hideDialog(); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js index 9331e4227b..0fdc251949 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js @@ -174,13 +174,13 @@ filterCssClass: "not-allowed", select: node => { const filepath = decodeURIComponent(node.id.replace(/\+/g, " ")); - block.view = "~/" + filepath; + block.view = "~/" + filepath.replace("wwwroot/", ""); editorService.close(); }, close: () => editorService.close() }; - editorService.filePicker(filePicker); + editorService.staticFilePicker(filePicker); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 21f6354c62..4e2bdf6ce4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -667,6 +667,7 @@ angular.module("umbraco") $scope.removeControl = function (cell, $index) { $scope.currentControl = null; cell.controls.splice($index, 1); + currentForm.$setDirty(); }; $scope.percentage = function (spans) { 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 c66ff1a461..c3c5dff69b 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 @@ -146,6 +146,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } var listParamsForCurrent = $routeParams.id == $routeParams.list; + $scope.options = { useInfiniteEditor: $scope.model.config.useInfiniteEditor === true, pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, @@ -169,6 +170,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete, cultureName: $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture }; + _.each($scope.options.includeProperties, function (property) { property.nameExp = !!property.nameTemplate ? $interpolate(property.nameTemplate) @@ -268,12 +270,13 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time $scope.getContent = function (contentId) { $scope.reloadView($scope.contentId, true); - } + }; $scope.reloadView = function (id, reloadActiveNode) { if (!id) { return; } + $scope.viewLoaded = false; $scope.folders = []; @@ -327,7 +330,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time $scope.options.pageNumber = 1; $scope.reloadView($scope.contentId); } - } + }; $scope.onSearchStartTyping = function() { $scope.viewLoaded = false; @@ -386,6 +389,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time view: "views/propertyeditors/listview/overlays/delete.html", deletesVariants: selectionHasVariants(), isTrashed: $scope.isTrashed, + selection: $scope.selection, submitButtonLabelKey: "contentTypeEditor_yesDelete", submitButtonStyle: "danger", submit: function (model) { @@ -495,8 +499,8 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time view: "views/propertyeditors/listview/overlays/listviewunpublish.html", submitButtonLabelKey: "actions_unpublish", submitButtonStyle: "warning", + selection: $scope.selection, submit: function (model) { - // create a comma separated array of selected cultures let selectedCultures = []; if (model.languages && model.languages.length > 0) { @@ -506,7 +510,6 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } }); } - performUnpublish(selectedCultures); overlayService.close(); }, @@ -708,13 +711,15 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } function initView() { + var id = $routeParams.id; if (id === undefined) { // no ID found in route params - don't list anything as we don't know for sure where we are return; } - - $scope.contentId = id; + + // Get current id for node to load it's children + $scope.contentId = editorState.current ? editorState.current.id : id; $scope.isTrashed = editorState.current ? editorState.current.trashed : id === "-20" || id === "-21"; $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/delete.controller.js new file mode 100644 index 0000000000..85c8b82620 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/delete.controller.js @@ -0,0 +1,40 @@ +(function () { + "use strict"; + + function ListViewDeleteController($scope, localizationService) { + + var vm = this; + vm.loading = true; + vm.disableDelete = false; + + function onInit() { + $scope.model.hideSubmitButton = true; + } + + vm.checkingReferencesComplete = () => { + $scope.model.hideSubmitButton = false; + }; + + vm.onReferencesWarning = () => { + $scope.model.submitButtonStyle = "danger"; + + // check if the deletion of items that have references has been disabled + if (Umbraco.Sys.ServerVariables.umbracoSettings.disableDeleteWhenReferenced) { + // this will only be set to true if we have a warning, indicating that this item or its descendants have reference + vm.disableDelete = true; + $scope.model.disableSubmitButton = true; + } + + localizationService.localize("general_delete").then(function (action) { + localizationService.localize("references_listViewDialogWarning", [action.toLowerCase()]).then((value) => { + vm.warningText = value; + }); + }); + }; + + onInit(); + } + + angular.module("umbraco").controller("Umbraco.Overlays.ListViewDeleteController", ListViewDeleteController); + +})(); 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 f8b2b008d3..b137c74260 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,9 +1,15 @@ -
    +
    -

    +

    Are you sure you want to delete?

    + + +
    + {{vm.warningText}} +
    +
    This will delete the node and all its languages. If you only want to delete one language go and unpublish it instead.
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.controller.js index 650b1b8438..cf432c0318 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/overlays/listviewunpublish.controller.js @@ -5,6 +5,7 @@ var vm = this; vm.loading = true; + vm.warningText = null; vm.changeSelection = changeSelection; @@ -29,7 +30,9 @@ function onInit() { + vm.selection = $scope.model.selection; vm.languages = $scope.model.languages; + $scope.model.hideSubmitButton = true; if (!$scope.model.title) { localizationService.localize("content_unpublish").then(function (value) { @@ -65,6 +68,26 @@ vm.loading = false; } + vm.checkingReferencesComplete = () => { + $scope.model.hideSubmitButton = false; + }; + + vm.onReferencesWarning = () => { + $scope.model.submitButtonStyle = "danger"; + + // check if the unpublishing of items that have references has been disabled + if (Umbraco.Sys.ServerVariables.umbracoSettings.disableUnpublishWhenReferenced) { + // this will only be disabled if we have a warning, indicating that this item or its descendants have reference + $scope.model.disableSubmitButton = true; + } + + localizationService.localize("content_unpublish").then(function (action) { + localizationService.localize("references_listViewDialogWarning", [action.toLowerCase()]).then((value) => { + vm.warningText = value; + }); + }); + }; + onInit(); //when this dialog is closed, reset all 'publish' flags 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 9a6af50f3e..2d255cbb25 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 @@ -5,6 +5,14 @@

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

    +
    + +
    + +
    + {{vm.warningText}} +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html index e644fac29d..029596a8f6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html @@ -2,7 +2,7 @@
    {{objectType.name}} + + + + +

    + +

    +
      +
    • + +
    • +
    • + +
    • +
    +
    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/relationTypes/views/relationType.settings.controller.js b/src/Umbraco.Web.UI.Client/src/views/relationTypes/views/relationType.settings.controller.js index c8e0af0aa9..01b5367eab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationTypes/views/relationType.settings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/relationTypes/views/relationType.settings.controller.js @@ -16,12 +16,17 @@ function RelationTypeSettingsController($scope, localizationService) { var labelKeys = [ "relationType_parentToChild", - "relationType_bidirectional" + "relationType_bidirectional", + "relationType_dependency", + "relationType_noDependency" + ]; localizationService.localizeMany(labelKeys).then(function (data) { vm.labels.parentToChild = data[0]; vm.labels.bidirectional = data[1]; + vm.labels.dependency = data[2]; + vm.labels.noDependency = data[3]; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index 856886a870..99f11f9913 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -558,24 +558,32 @@ } } }); + + const editor = { + filterCssClass: 'not-allowed', + filter: item => !availableMasterTemplates.some(template => template.id == item.id), + submit: model => { + const template = model.selection[0]; + if (template && template.alias) { + vm.template.masterTemplateAlias = template.alias; + setLayout(template.alias + ".cshtml"); + } else { + vm.template.masterTemplateAlias = null; + setLayout(null); + } + editorService.close(); + }, + close: () => editorService.close() + } localizationService.localize("template_mastertemplate").then(title => { - const editor = { - title, - filterCssClass: 'not-allowed', - filter: item => !availableMasterTemplates.some(template => template.id == item.id), - submit: model => { - var template = model.selection[0]; - if (template && template.alias) { - vm.template.masterTemplateAlias = template.alias; - setLayout(template.alias + ".cshtml"); - } else { - vm.template.masterTemplateAlias = null; - setLayout(null); - } - editorService.close(); - }, - close: () => editorService.close() + editor.title = title; + + const currentTemplate = vm.templates.find(template => template.alias == vm.template.masterTemplateAlias); + if (currentTemplate) { + editor.currentNode = { + path: currentTemplate.path + }; } editorService.templatePicker(editor); diff --git a/src/Umbraco.Web.UI/Views/Partials/blocklist/default.cshtml b/src/Umbraco.Web.UI/Views/Partials/blocklist/default.cshtml index d5944b93c3..accca2ef37 100644 --- a/src/Umbraco.Web.UI/Views/Partials/blocklist/default.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/blocklist/default.cshtml @@ -1,6 +1,6 @@ @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @{ - if (!Model.Any()) { return; } + if (Model?.Any() != true) { return; } }
    @foreach (var block in Model) diff --git a/src/Umbraco.Web.UI/Views/Partials/grid/bootstrap3-fluid.cshtml b/src/Umbraco.Web.UI/Views/Partials/grid/bootstrap3-fluid.cshtml index 84004929bd..b92734e761 100644 --- a/src/Umbraco.Web.UI/Views/Partials/grid/bootstrap3-fluid.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/grid/bootstrap3-fluid.cshtml @@ -7,7 +7,7 @@ Razor helpers located at the bottom of this file *@ -@if (Model != null && Model.GetType() == typeof(JObject) && Model.sections != null) +@if (Model is JObject && Model?.sections is not null) { var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1; @@ -55,7 +55,7 @@
    @foreach (var control in area.controls) { - if (control != null && control.editor != null && control.editor.view != null) + if (control?.editor?.view != null) { @await Html.PartialAsync("grid/editors/base", (object)control) } diff --git a/src/Umbraco.Web.UI/Views/Partials/grid/bootstrap3.cshtml b/src/Umbraco.Web.UI/Views/Partials/grid/bootstrap3.cshtml index ebe1cf725f..886378848b 100644 --- a/src/Umbraco.Web.UI/Views/Partials/grid/bootstrap3.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/grid/bootstrap3.cshtml @@ -3,9 +3,9 @@ @using Newtonsoft.Json.Linq @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage -@if (Model != null && Model.GetType() == typeof(JObject) && Model.sections != null) +@if (Model is JObject && Model?.sections is not null) { - var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1; + var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1;
    @if (oneColumn) @@ -56,7 +56,7 @@
    @foreach (var control in area.controls) { - if (control != null && control.editor != null && control.editor.view != null) + if (control?.editor?.view != null) { @await Html.PartialAsync("grid/editors/base", (object)control) } diff --git a/src/Umbraco.Web.UI/Views/Partials/grid/editors/base.cshtml b/src/Umbraco.Web.UI/Views/Partials/grid/editors/base.cshtml index eca6381fd0..05e27e18e5 100644 --- a/src/Umbraco.Web.UI/Views/Partials/grid/editors/base.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/grid/editors/base.cshtml @@ -3,7 +3,7 @@ @try { string editor = EditorView(Model); - @await Html.PartialAsync(editor, (object)Model) + @await Html.PartialAsync(editor, Model as object) } catch (Exception ex) { diff --git a/src/Umbraco.Web.UI/Views/Partials/grid/editors/embed.cshtml b/src/Umbraco.Web.UI/Views/Partials/grid/editors/embed.cshtml index a383046420..74c8fe2753 100644 --- a/src/Umbraco.Web.UI/Views/Partials/grid/editors/embed.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/grid/editors/embed.cshtml @@ -1,10 +1,11 @@ -@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage -@{ - string embedValue = Convert.ToString(Model.value); - embedValue = embedValue.DetectIsJson() ? Model.value.preview : Model.value; +@if (Model is not null) +{ + string embedValue = Convert.ToString(Model.value); + embedValue = embedValue.DetectIsJson() ? Model.value.preview : Model.value; + +
    + @Html.Raw(embedValue) +
    } - -
    - @Html.Raw(embedValue) -
    diff --git a/src/Umbraco.Web.UI/Views/Partials/grid/editors/macro.cshtml b/src/Umbraco.Web.UI/Views/Partials/grid/editors/macro.cshtml index 0e9661edd9..a4450d1c03 100644 --- a/src/Umbraco.Web.UI/Views/Partials/grid/editors/macro.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/grid/editors/macro.cshtml @@ -1,6 +1,6 @@ @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage -@if (Model.value != null) +@if (Model?.value is not null) { string macroAlias = Model.value.macroAlias.ToString(); var parameters = new Dictionary(); diff --git a/src/Umbraco.Web.UI/Views/Partials/grid/editors/media.cshtml b/src/Umbraco.Web.UI/Views/Partials/grid/editors/media.cshtml index 4cc31d0754..bc3b111332 100644 --- a/src/Umbraco.Web.UI/Views/Partials/grid/editors/media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/grid/editors/media.cshtml @@ -2,7 +2,8 @@ @using Umbraco.Cms.Core.Media @using Umbraco.Cms.Core.PropertyEditors.ValueConverters @inject IImageUrlGenerator ImageUrlGenerator -@if (Model.value != null) + +@if (Model?.value is not null) { var url = Model.value.image; diff --git a/src/Umbraco.Web.UI/Views/Partials/grid/editors/rte.cshtml b/src/Umbraco.Web.UI/Views/Partials/grid/editors/rte.cshtml index e14c6e1a97..944566688a 100644 --- a/src/Umbraco.Web.UI/Views/Partials/grid/editors/rte.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/grid/editors/rte.cshtml @@ -5,7 +5,7 @@ @inject HtmlImageSourceParser HtmlImageSourceParser; @{ - var value = HtmlLocalLinkParser.EnsureInternalLinks(Model.value.ToString()); + var value = HtmlLocalLinkParser.EnsureInternalLinks(Model?.value.ToString()); value = HtmlUrlParser.EnsureUrls(value); value = HtmlImageSourceParser.EnsureImageSources(value); } diff --git a/src/Umbraco.Web.UI/Views/Partials/grid/editors/textstring.cshtml b/src/Umbraco.Web.UI/Views/Partials/grid/editors/textstring.cshtml index 42972f64d8..d4152a59b1 100644 --- a/src/Umbraco.Web.UI/Views/Partials/grid/editors/textstring.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/grid/editors/textstring.cshtml @@ -1,7 +1,7 @@ -@using System.Web +@using System.Web @model dynamic -@if (Model.editor.config.markup != null) +@if (Model?.editor.config.markup is not null) { string markup = Model.editor.config.markup.ToString(); markup = markup.Replace("#value#", Html.ReplaceLineBreaks((string)Model.value.ToString()).ToString()); @@ -18,6 +18,6 @@ else { -
    @Model.value
    +
    @Model?.value
    } diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml index 30c3feb5c5..6a807e8379 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml @@ -1,4 +1,4 @@ -@using Umbraco.Cms.Core.Routing +@using Umbraco.Cms.Core.Routing @using Umbraco.Extensions @inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage @inject IPublishedUrlProvider PublishedUrlProvider @@ -10,9 +10,9 @@ - Finally it outputs the name of the current page (without a link) *@ -@{ var selection = Model.Content.Ancestors().ToArray(); } +@{ var selection = Model?.Content.Ancestors().ToArray(); } -@if (selection.Length > 0) +@if (selection?.Length > 0) { } diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Gallery.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Gallery.cshtml index fec760b5cb..83ac245500 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Gallery.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Gallery.cshtml @@ -24,7 +24,7 @@ Type: (note: You can use a Single Media Picker if that's more appropriate to your needs) *@ -@{ var mediaIds = Model.MacroParameters["mediaIds"] as string; } +@{ var mediaIds = Model?.MacroParameters["mediaIds"] as string; } @if (mediaIds != null) { diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml index c44965ec85..ead17cfe59 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml @@ -10,9 +10,9 @@ - Finally it outputs the name of the current page (without a link) *@ -@{ var selection = Model.Content.Ancestors().ToArray(); } +@{ var selection = Model?.Content.Ancestors().ToArray(); } -@if (selection.Length > 0) +@if (selection?.Length > 0) {
      @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@ @@ -22,6 +22,6 @@ } @* Display the current page as the last item in the list *@ -
    • @Model.Content.Name
    • +
    • @Model?.Content.Name
    } diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml index 464c05fb78..a53c658364 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml @@ -18,7 +18,7 @@ Alias:startNodeId Name:Select starting page Type:Content Picker *@ -@{ var startNodeId = Model.MacroParameters["startNodeId"]; } +@{ var startNodeId = Model?.MacroParameters["startNodeId"]; } @if (startNodeId != null) { diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml index 491f90238a..7b055e8424 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml @@ -13,9 +13,9 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } +@{ var selection = Model?.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } -@if (selection.Length > 0) +@if (selection?.Length > 0) {
      @foreach (var item in selection) diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml index b998d917a1..4b07c6209f 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml @@ -14,9 +14,9 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).OrderByDescending(x => x.CreateDate).ToArray(); } +@{ var selection = Model?.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).OrderByDescending(x => x.CreateDate).ToArray(); } -@if (selection.Length > 0) +@if (selection?.Length > 0) {
        @foreach (var item in selection) diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml index ac66ece1ea..f46bbcda80 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml @@ -14,9 +14,9 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).OrderBy(x => x.Name).ToArray(); } +@{ var selection = Model?.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).OrderBy(x => x.Name).ToArray(); } -@if (selection.Length > 0) +@if (selection?.Length > 0) {
          @foreach (var item in selection) diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml index f20253bc7f..82b34ca55c 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml @@ -17,13 +17,13 @@ Alias:propertyAlias Name:Property Alias Type:Textbox *@ -@{ var propertyAlias = Model.MacroParameters["propertyAlias"]; } +@{ var propertyAlias = Model?.MacroParameters["propertyAlias"]; } @if (propertyAlias != null) { - var selection = Model.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).OrderBy(x => x.Value(PublishedValueFallback, propertyAlias.ToString())).ToArray(); + var selection = Model?.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).OrderBy(x => x.Value(PublishedValueFallback, propertyAlias.ToString())).ToArray(); - if (selection.Length > 0) + if (selection?.Length > 0) {
            @foreach (var item in selection) diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml index 1f78ae43a0..466aeacaa3 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml @@ -13,9 +13,9 @@ (You can find the alias of your Document Type by editing it in the Settings section) *@ -@{ var selection = Model.Content.Children(VariationContextAccessor).Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } +@{ var selection = Model?.Content.Children(VariationContextAccessor).Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } -@if (selection.Length > 0) +@if (selection?.Length > 0) {
              @foreach (var item in selection) diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml index 92caf9906d..339c0bc42d 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml @@ -10,10 +10,10 @@ the page currently being viewed by the website visitor, displayed as nested unordered HTML lists. *@ -@{ var selection = Model.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } +@{ var selection = Model?.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } @* Ensure that the Current Page has children *@ -@if (selection.Length > 0) +@if (selection?.Length > 0) { @* Get the first page in the children, where the property umbracoNaviHide is not True *@ var naviLevel = selection[0].Level; diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml index 2ef595992d..8148612409 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml @@ -16,7 +16,7 @@ Alias:mediaId Name:Select folder with images Type:Single Media Picker *@ -@{ var mediaId = Model.MacroParameters["mediaId"]; } +@{ var mediaId = Model?.MacroParameters["mediaId"]; } @if (mediaId != null) { diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml index bca0a23c43..8f5477bca4 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml @@ -15,7 +15,7 @@ { - @if (Model.Languages != null && Model.Languages.Count() > 1) + @if (Model?.Languages.Any() == true) {
              diff --git a/src/Umbraco.Web.UI/umbraco/UmbracoWebsite/NoNodes.cshtml b/src/Umbraco.Web.UI/umbraco/UmbracoWebsite/NoNodes.cshtml index a19390f8ec..69d8318a1f 100644 --- a/src/Umbraco.Web.UI/umbraco/UmbracoWebsite/NoNodes.cshtml +++ b/src/Umbraco.Web.UI/umbraco/UmbracoWebsite/NoNodes.cshtml @@ -1,4 +1,4 @@ -@using Microsoft.Extensions.Options +@using Microsoft.Extensions.Options @using Umbraco.Cms.Core.Configuration.Models @using Umbraco.Cms.Core.Hosting @using Umbraco.Cms.Core.Routing @@ -31,7 +31,7 @@

              You're seeing this wonderful page because your website doesn't contain any published content yet.

              diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index dbcf771e4c..5ec919874f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1323,6 +1323,8 @@ Mange hilsner fra Umbraco robotten %0% dokumenter udgivet og synlige på hjemmesiden %0% udgivet og synligt på hjemmesiden %0% dokumenter udgivet for sprogene %1% og synlige på hjemmesiden + Indholdsskabelon gemt + Rettelser er blevet gemt Indhold gemt Husk at publicere for at gøre det synligt for besøgende En planlægning for udgivelse er blevet opdateret diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 11e6c1f2f3..bf2de30f2e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -332,6 +332,7 @@ Click to upload or click here to choose files Cannot upload this file, it does not have an approved file type + Cannot upload this file, the media type with alias '%0%' is not allowed here Cannot upload this file, it does not have a valid file name Max file size is Media root @@ -787,6 +788,7 @@ New Next No + Node Name of Off OK @@ -828,6 +830,7 @@ Submit Success Type + Type Name Type to search... under Up @@ -1468,6 +1471,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont The best Umbraco video tutorials Visit our.umbraco.com Visit umbraco.tv + Watch our free tutorial videos + on the Umbraco Learning Base Default template @@ -1507,6 +1512,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Insufficient user permissions, could not complete the operation Cancelled Operation was cancelled by a 3rd party add-in + This file is being uploaded as part of a folder, but creating a new folder is not allowed here + Creating a new folder is not allowed here Publishing was cancelled by a 3rd party add-in Property type already exists Property type created @@ -1524,6 +1531,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Publishing failed because the parent page isn't published Content published and visible on the website + Content Template saved + Changes have been successfully saved Content saved Remember to publish to make changes visible Sent For Approval @@ -2383,6 +2392,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Parent Child Count + Relation Relations Created Comment @@ -2390,6 +2400,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont No relations for this Relation Type Relation Type Relations + Is Dependency + Yes + No Getting Started @@ -2460,13 +2473,17 @@ To manage your website, simply open the Umbraco backoffice and start adding cont References This Data Type has no references. + This item has no references. Used in Document Types Used in Media Types Used in Member Types Used by - Used in Documents - Used in Members - Used in Media + Items in use + Descendants in use + This item or its descendants is being used. Deletion can lead to broken links on your website. + This item or its descendants is being used. Unpublishing can lead to broken links on your website. Please take the appropriate actions. + This item or its descendants is being used. Therefore, deletion has been disabled. + The following items you are trying to %0% are used by other content. Delete Saved Search @@ -2620,9 +2637,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Ask a question in the Community Forum ]]> - + tutorial videos (some are free, some require a subscription) + Watch our free tutorial videos on the Umbraco Learning Base ]]> diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 6b1b607c90..873361169a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -335,6 +335,7 @@ Click to upload or click here to choose files Cannot upload this file, it does not have an approved file type + Cannot upload this file, the media type with alias '%0%' is not allowed here Cannot upload this file, it does not have a valid file name Max file size is Media root @@ -808,6 +809,7 @@ New Next No + Node Name of Off OK @@ -848,6 +850,7 @@ Submit Success Type + Type Name Type to search... under Up @@ -1491,6 +1494,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont The best Umbraco video tutorials Visit our.umbraco.com Visit umbraco.tv + Watch our free tutorial videos + on the Umbraco Learning Base Default template @@ -1532,6 +1537,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Insufficient user permissions, could not complete the operation Cancelled Operation was cancelled by a 3rd party add-in + This file is being uploaded as part of a folder, but creating a new folder is not allowed here + Creating a new folder is not allowed here Property type already exists Property type created DataType: %1%]]> @@ -1549,8 +1556,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont and is visible on the website %0% documents published and visible on the website %0% published and visible on the website - %0% documents published for languages %1% and visible on the website - + %0% documents published for languages %1% and visible on the website + Content Template saved + Changes have been successfully saved Content saved Remember to publish to make changes visible A schedule for publishing has been updated @@ -2466,6 +2474,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Parent Child Count + Relation Relations Created Comment @@ -2473,6 +2482,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont No relations for this Relation Type Relation Type Relations + Is Dependency + Yes + No Getting Started @@ -2543,13 +2555,20 @@ To manage your website, simply open the Umbraco backoffice and start adding cont References This Data Type has no references. - Used in Document Types - Used in Media Types - Used in Member Types - Used by - Used in Documents - Used in Members - Used in Media + This item has no references. + Referenced by the following Document Types + Referenced by the following Media Types + Referenced by the following Member Types + Referenced by + Referenced by the following items + The following items depend on this + The following items are referenced + The following descendant items have dependencies + The following descending items have dependencies + This item or its descendants is being referenced. Deletion can lead to broken links on your website. + This item or its descendants is being referenced. Unpublishing can lead to broken links on your website. Please take the appropriate actions. + This item or its descendants is being referenced. Therefore, deletion has been disabled. + The following items you are trying to %0% are referenced by other content. Delete Saved Search @@ -2703,9 +2722,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Ask a question in the Community Forum ]]> - + tutorial videos (some are free, some require a subscription) + Watch our free tutorial videos on the Umbraco Learning Base ]]> diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 9106c3ed09..60384de752 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -201,23 +201,23 @@ namespace Umbraco.Cms.Web.Website.Routing throw new ArgumentNullException(nameof(httpContext)); } - // if it is a POST/GET then a value must be in the request - if ((!httpContext.Request.HasFormContentType || !httpContext.Request.Form.TryGetValue("ufprt", out StringValues encodedVal)) - && !httpContext.Request.Query.TryGetValue("ufprt", out encodedVal)) + // if it is a POST/GET then a `ufprt` value must be in the request + var ufprt = httpContext.Request.GetUfprt(); + if (string.IsNullOrWhiteSpace(ufprt)) { return null; } if (!EncryptionHelper.DecryptAndValidateEncryptedRouteString( _dataProtectionProvider, - encodedVal, - out IDictionary decodedParts)) + ufprt, + out IDictionary decodedUfprt)) { return null; } // Get all route values that are not the default ones and add them separately so they eventually get to action parameters - foreach (KeyValuePair item in decodedParts.Where(x => ReservedAdditionalKeys.AllKeys.Contains(x.Key) == false)) + foreach (KeyValuePair item in decodedUfprt.Where(x => ReservedAdditionalKeys.AllKeys.Contains(x.Key) == false)) { values[item.Key] = item.Value; } @@ -225,9 +225,9 @@ namespace Umbraco.Cms.Web.Website.Routing // return the proxy info without the surface id... could be a local controller. return new PostedDataProxyInfo { - ControllerName = WebUtility.UrlDecode(decodedParts.First(x => x.Key == ReservedAdditionalKeys.Controller).Value), - ActionName = WebUtility.UrlDecode(decodedParts.First(x => x.Key == ReservedAdditionalKeys.Action).Value), - Area = WebUtility.UrlDecode(decodedParts.First(x => x.Key == ReservedAdditionalKeys.Area).Value), + ControllerName = WebUtility.UrlDecode(decodedUfprt.First(x => x.Key == ReservedAdditionalKeys.Controller).Value), + ActionName = WebUtility.UrlDecode(decodedUfprt.First(x => x.Key == ReservedAdditionalKeys.Action).Value), + Area = WebUtility.UrlDecode(decodedUfprt.First(x => x.Key == ReservedAdditionalKeys.Area).Value), }; } diff --git a/src/Umbraco.Web.Website/Security/MemberAuthenticationBuilder.cs b/src/Umbraco.Web.Website/Security/MemberAuthenticationBuilder.cs index d58abfc871..8308abafff 100644 --- a/src/Umbraco.Web.Website/Security/MemberAuthenticationBuilder.cs +++ b/src/Umbraco.Web.Website/Security/MemberAuthenticationBuilder.cs @@ -41,7 +41,7 @@ namespace Umbraco.Cms.Web.Website.Security // Validate that the prefix is set if (!authenticationScheme.StartsWith(Constants.Security.MemberExternalAuthenticationTypePrefix)) { - throw new InvalidOperationException($"The {nameof(authenticationScheme)} is not prefixed with {Constants.Security.BackOfficeExternalAuthenticationTypePrefix}. The scheme must be created with a call to the method {nameof(SchemeForMembers)}"); + throw new InvalidOperationException($"The {nameof(authenticationScheme)} is not prefixed with {Constants.Security.MemberExternalAuthenticationTypePrefix}. The scheme must be created with a call to the method {nameof(SchemeForMembers)}"); } // add our login provider to the container along with a custom options configuration diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/relationTypes.ts b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/relationTypes.ts index 4773f37c37..1b903d4b76 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/relationTypes.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/relationTypes.ts @@ -25,6 +25,8 @@ context('Relation Types', () => { cy.get('select[name="relationType-parent"]').select('Document'); cy.get('select[name="relationType-child"]').select('Media'); + + cy.get('[name="relationType-isdependency"]').last().click({force: true}) cy.get(".btn-primary").click(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.js b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.js deleted file mode 100644 index ce2e366f2c..0000000000 --- a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.js +++ /dev/null @@ -1,35 +0,0 @@ -context('User Groups', () => { - - beforeEach(() => { - cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); - }); - - it('Create user group', () => { - const name = "Test Group"; - - cy.umbracoEnsureUserGroupNameNotExists(name); - - cy.umbracoSection('users'); - cy.get('[data-element="sub-view-userGroups"]').click(); - - cy.umbracoButtonByLabelKey("actions_createGroup").click(); - - //Type name - cy.umbracoEditorHeaderName(name); - - // Assign sections - cy.get('.umb-box:nth-child(1) .umb-property:nth-child(1) localize').click(); - cy.get('.umb-tree-item__inner').click({multiple:true, timeout: 10000}); - cy.get('.btn-success').last().click(); - - // Save - cy.get('.btn-success').click(); - - //Assert - cy.umbracoSuccessNotification().should('be.visible'); - - //Clean up - cy.umbracoEnsureUserGroupNameNotExists(name); - }); - -}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.ts b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.ts new file mode 100644 index 0000000000..cd4b022544 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.ts @@ -0,0 +1,85 @@ +/// +import { UserGroupBuilder } from 'umbraco-cypress-testhelpers'; + +context('User Groups', () => { + + function navigateToUserGroups() { + cy.umbracoSection('users'); + cy.get('[data-element="sub-view-userGroups"]').click(); + } + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create user group', () => { + const name = "Test Group"; + + cy.umbracoEnsureUserGroupNameNotExists(name); + + navigateToUserGroups(); + cy.umbracoButtonByLabelKey("actions_createGroup").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + // Assign sections + cy.get('.umb-box:nth-child(1) .umb-property:nth-child(1) localize').click(); + cy.get('.umb-tree-item__inner').click({multiple:true, timeout: 10000}); + cy.get('.btn-success').last().click(); + + // Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureUserGroupNameNotExists(name); + }); + + it('Can delete user group', () => { + // Create user group + const groupName = "Delete user group test" + cy.umbracoEnsureUserGroupNameNotExists(groupName); + + const userGroup = new UserGroupBuilder() + .withName(groupName) + .build(); + + cy.saveUserGroup(userGroup); + navigateToUserGroups(); + + // Delete the user group + cy.get('.umb-table-body > :nth-child(2)').click(); + cy.umbracoButtonByLabelKey("general_delete").click(); + cy.get('umb-button[alias="overlaySubmit"]').click(); + + cy.umbracoSuccessNotification().should('be.visible'); + cy.get('.umb-table-body').contains(groupName).should('not.exist'); + + cy.umbracoEnsureUserGroupNameNotExists(groupName); + }); + + it('Cannot delete required groups', () => { + navigateToUserGroups(); + + // There's not really a good way to be 100% sure we'll get the admin group, it should be first, but who knows + // so double check that we actually got the correct one + const administrators = cy.get('.umb-table-body > :nth-child(1)'); + administrators.should('contain', 'Administrators'); + administrators.click({force: true}); + + const sensitive = cy.get('.umb-table-body > :nth-child(3)'); + sensitive.should('contain', 'Sensitive data'); + sensitive.click({force: true}); + + const translators = cy.get('.umb-table-body > :nth-child(4)'); + translators.should('contain', 'Translators'); + translators.click({force: true}); + + // Now that we've clicked all that we shouldn't be able to delete, ensure that the delete button does not show up + cy.get('.umb-editor-sub-header').should('not.contain', 'Delete'); + }); +}); + diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 7ada1d9fb7..fb622bbffb 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -1628,9 +1628,9 @@ "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==" }, "umbraco-cypress-testhelpers": { - "version": "1.0.0-beta-63", - "resolved": "https://registry.npmjs.org/umbraco-cypress-testhelpers/-/umbraco-cypress-testhelpers-1.0.0-beta-63.tgz", - "integrity": "sha512-X+DHWktfB+WBb7YrxvpneVfS1PATx2zPYMdkeZTmtoQEeyGxXA9fW6P712/AUbyGAhRhH+46t4cAINdWJxItug==", + "version": "1.0.0-beta-66", + "resolved": "https://registry.npmjs.org/umbraco-cypress-testhelpers/-/umbraco-cypress-testhelpers-1.0.0-beta-66.tgz", + "integrity": "sha512-/Iq0P7rN9LfODO9snoLNqvbd8b432JIYtCVjYOdYZFceMAUIv0v2/6t7+N55Z7h8OpAQzcTLU3VCxfPzZp8wQw==", "dev": true, "requires": { "camelize": "^1.0.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index a95a71020f..92141f4b1f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -14,7 +14,7 @@ "del": "^6.0.0", "ncp": "^2.0.0", "prompt": "^1.2.0", - "umbraco-cypress-testhelpers": "^1.0.0-beta-63" + "umbraco-cypress-testhelpers": "^1.0.0-beta-65" }, "dependencies": { "typescript": "^3.9.2" diff --git a/tests/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs b/tests/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs index 2bd9dc124d..bb16cadd78 100644 --- a/tests/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs +++ b/tests/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs @@ -8,7 +8,7 @@ using Umbraco.Cms.Tests.Common.Builders.Interfaces; namespace Umbraco.Cms.Tests.Common.Builders { public class RelationTypeBuilder - : ChildBuilderBase, + : ChildBuilderBase, IWithIdBuilder, IWithAliasBuilder, IWithNameBuilder, @@ -23,6 +23,7 @@ namespace Umbraco.Cms.Tests.Common.Builders private DateTime? _deleteDate; private int? _id; private bool? _isBidirectional; + private bool? _isDependency; private Guid? _key; private string _name; private Guid? _parentObjectType; @@ -42,6 +43,12 @@ namespace Umbraco.Cms.Tests.Common.Builders { _isBidirectional = isBidirectional; return this; + } + + public RelationTypeBuilder WithIsDependency(bool isDependency) + { + _isDependency = isDependency; + return this; } public RelationTypeBuilder WithChildObjectType(Guid childObjectType) @@ -56,7 +63,7 @@ namespace Umbraco.Cms.Tests.Common.Builders return this; } - public override IRelationType Build() + public override IRelationTypeWithIsDependency Build() { var alias = _alias ?? Guid.NewGuid().ToString(); var name = _name ?? Guid.NewGuid().ToString(); @@ -65,11 +72,12 @@ namespace Umbraco.Cms.Tests.Common.Builders var id = _id ?? 0; Guid key = _key ?? Guid.NewGuid(); var isBidirectional = _isBidirectional ?? false; + var isDependency = _isDependency ?? false; DateTime createDate = _createDate ?? DateTime.Now; DateTime updateDate = _updateDate ?? DateTime.Now; DateTime? deleteDate = _deleteDate ?? null; - return new RelationType(name, alias, isBidirectional, parentObjectType, childObjectType) + return new RelationType(name, alias, isBidirectional, parentObjectType, childObjectType, isDependency) { Id = id, Key = key, diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs index ee01816f09..bdd5257b45 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs @@ -189,23 +189,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos // Get parent entities for child id var parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 11, out long totalRecords).ToList(); - Assert.AreEqual(6, totalRecords); - Assert.AreEqual(6, parents.Count); + Assert.AreEqual(9, totalRecords); + Assert.AreEqual(9, parents.Count); - // add the next page + // Add the next page parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 1, 11, out totalRecords)); - Assert.AreEqual(6, totalRecords); - Assert.AreEqual(6, parents.Count); + Assert.AreEqual(9, totalRecords); + Assert.AreEqual(9, parents.Count); var contentEntities = parents.OfType().ToList(); var mediaEntities = parents.OfType().ToList(); var memberEntities = parents.OfType().ToList(); Assert.AreEqual(3, contentEntities.Count); - Assert.AreEqual(0, mediaEntities.Count); + Assert.AreEqual(3, mediaEntities.Count); Assert.AreEqual(3, memberEntities.Count); - // only of a certain type + // Only of a certain type parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Document.GetGuid())); Assert.AreEqual(3, totalRecords); @@ -213,7 +213,32 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual(3, totalRecords); parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid())); - Assert.AreEqual(0, totalRecords); + Assert.AreEqual(3, totalRecords); + + // Test relations on content + var contentParents = repository.GetPagedParentEntitiesByChildId(createdContent[0].Id, 0, int.MaxValue, out totalRecords).ToList(); + Assert.AreEqual(6, totalRecords); + Assert.AreEqual(6, contentParents.Count); + + // Test getting relations of specified relation types + var relatedMediaRelType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias); + var relatedContentRelType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedDocumentAlias); + + parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 11, out totalRecords, new int[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList(); + Assert.AreEqual(6, totalRecords); + Assert.AreEqual(6, parents.Count); + + parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 1, 11, out totalRecords, new int[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList(); + Assert.AreEqual(6, totalRecords); + Assert.AreEqual(0, parents.Count); + + parents = repository.GetPagedParentEntitiesByChildId(createdContent[0].Id, 0, 6, out totalRecords, new int[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList(); + Assert.AreEqual(3, totalRecords); + Assert.AreEqual(3, parents.Count); + + parents = repository.GetPagedParentEntitiesByChildId(createdContent[0].Id, 1, 6, out totalRecords, new int[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList(); + Assert.AreEqual(3, totalRecords); + Assert.AreEqual(0, parents.Count); } } @@ -255,19 +280,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos // Get parent entities for child id var parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 6, out long totalRecords).ToList(); - Assert.AreEqual(3, totalRecords); - Assert.AreEqual(3, parents.Count); + Assert.AreEqual(6, totalRecords); + Assert.AreEqual(6, parents.Count); - // add the next page + // Add the next page parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 1, 6, out totalRecords)); - Assert.AreEqual(3, totalRecords); - Assert.AreEqual(3, parents.Count); + Assert.AreEqual(6, totalRecords); + Assert.AreEqual(6, parents.Count); var contentEntities = parents.OfType().ToList(); var mediaEntities = parents.OfType().ToList(); var memberEntities = parents.OfType().ToList(); - Assert.AreEqual(0, contentEntities.Count); + Assert.AreEqual(3, contentEntities.Count); Assert.AreEqual(3, mediaEntities.Count); Assert.AreEqual(0, memberEntities.Count); @@ -280,6 +305,18 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Member.GetGuid())); Assert.AreEqual(0, totalRecords); + + // Test getting relations of specified relation types + IRelationType relatedMediaRelType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias); + IRelationType relatedContentRelType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedDocumentAlias); + + parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 6, out totalRecords, new int[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList(); + Assert.AreEqual(3, totalRecords); + Assert.AreEqual(3, parents.Count); + + parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 1, 6, out totalRecords, new int[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList(); + Assert.AreEqual(3, totalRecords); + Assert.AreEqual(0, parents.Count); } } @@ -296,6 +333,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos createdContent.Add(c1); } + // Create related content + var relatedContent = new List(); + for (int i = 0; i < 3; i++) + { + Content c1 = ContentBuilder.CreateBasicContent(contentType); + ContentService.Save(c1); + relatedContent.Add(c1); + } + // Create media createdMedia = new List(); MediaType imageType = MediaTypeBuilder.CreateImageMediaType("myImage"); @@ -313,14 +359,24 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos createdMembers = MemberBuilder.CreateSimpleMembers(memberType, 3).ToList(); GetMemberService().Save(createdMembers); - IRelationType relType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias); + IRelationType relatedMediaRelType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias); + IRelationType relatedContentRelType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedDocumentAlias); // Relate content to media foreach (IContent content in createdContent) { foreach (IMedia media in createdMedia) { - RelationService.Relate(content.Id, media.Id, relType); + RelationService.Relate(content.Id, media.Id, relatedMediaRelType); + } + } + + // Relate content to content + foreach (IContent relContent in relatedContent) + { + foreach (IContent content in createdContent) + { + RelationService.Relate(relContent.Id, content.Id, relatedContentRelType); } } @@ -329,7 +385,67 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { foreach (IMedia media in createdMedia) { - RelationService.Relate(member.Id, media.Id, relType); + RelationService.Relate(member.Id, media.Id, relatedMediaRelType); + } + } + + // Create copied content + var copiedContent = new List(); + for (int i = 0; i < 3; i++) + { + Content c1 = ContentBuilder.CreateBasicContent(contentType); + ContentService.Save(c1); + copiedContent.Add(c1); + } + + IRelationType copiedContentRelType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); + + // Relate content to content (mimics copy) + foreach (IContent content in createdContent) + { + foreach (IContent cpContent in copiedContent) + { + RelationService.Relate(content.Id, cpContent.Id, copiedContentRelType); + } + } + + // Create trashed content + var trashedContent = new List(); + for (int i = 0; i < 3; i++) + { + Content c1 = ContentBuilder.CreateBasicContent(contentType); + ContentService.Save(c1); + trashedContent.Add(c1); + } + + IRelationType trashedRelType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias); + + // Relate to trashed content + foreach (IContent trContent in trashedContent) + { + foreach (IContent content in createdContent) + { + RelationService.Relate(trContent.Id, content.Id, trashedRelType); + } + } + + // Create trashed media + var trashedMedia = new List(); + for (int i = 0; i < 3; i++) + { + Media m1 = MediaBuilder.CreateMediaImage(imageType, -1); + MediaService.Save(m1); + trashedMedia.Add(m1); + } + + IRelationType trashedMediaRelType = RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias); + + // Relate to trashed media + foreach (IMedia trMedia in trashedMedia) + { + foreach (IMedia media in createdMedia) + { + RelationService.Relate(trMedia.Id, media.Id, trashedMediaRelType); } } } @@ -417,14 +533,16 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos "relateContentOnCopy", true, Constants.ObjectTypes.Document, - new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972")); + new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), + false); _relateContentType = new RelationType( "Relate ContentType on Copy", "relateContentTypeOnCopy", true, Constants.ObjectTypes.DocumentType, - new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB")); + new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"), + false); using (IScope scope = ScopeProvider.CreateScope()) { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs index 4b0a23464f..16c88e7cf9 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -36,7 +36,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos RelationTypeRepository repository = CreateRepository(provider); // Act - var relateMemberToContent = new RelationType("Relate Member to Content", "relateMemberToContent", true, Constants.ObjectTypes.Member, Constants.ObjectTypes.Document); + var relateMemberToContent = new RelationType("Relate Member to Content", "relateMemberToContent", true, Constants.ObjectTypes.Member, Constants.ObjectTypes.Document, true); repository.Save(relateMemberToContent); @@ -101,12 +101,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos RelationTypeRepository repository = CreateRepository(provider); // Act - IRelationType relationType = repository.Get(8); + var relationType = repository.Get(8) as IRelationTypeWithIsDependency; // Assert Assert.That(relationType, Is.Not.Null); Assert.That(relationType.HasIdentity, Is.True); Assert.That(relationType.IsBidirectional, Is.True); + Assert.That(relationType.IsDependency, Is.True); Assert.That(relationType.Alias, Is.EqualTo("relateContentToMedia")); Assert.That(relationType.Name, Is.EqualTo("Relate Content to Media")); Assert.That(relationType.ChildObjectType, Is.EqualTo(Constants.ObjectTypes.Media)); @@ -215,9 +216,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos public void CreateTestData() { - var relateContent = new RelationType("Relate Content on Copy", "relateContentOnCopy", true, Constants.ObjectTypes.Document, Constants.ObjectTypes.Document); - var relateContentType = new RelationType("Relate ContentType on Copy", "relateContentTypeOnCopy", true, Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentType); - var relateContentMedia = new RelationType("Relate Content to Media", "relateContentToMedia", true, Constants.ObjectTypes.Document, Constants.ObjectTypes.Media); + var relateContent = new RelationType("Relate Content on Copy", "relateContentOnCopy", true, Constants.ObjectTypes.Document, Constants.ObjectTypes.Document, false); + var relateContentType = new RelationType("Relate ContentType on Copy", "relateContentTypeOnCopy", true, Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentType, false); + var relateContentMedia = new RelationType("Relate Content to Media", "relateContentToMedia", true, Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, true); IScopeProvider provider = ScopeProvider; using (IScope scope = provider.CreateScope()) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs new file mode 100644 index 0000000000..ebb9b8a6a7 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs @@ -0,0 +1,111 @@ +using System.IO; +using System.Threading; +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.Hosting; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Infrastructure.Runtime; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Runtime +{ + [TestFixture] + internal class FileSystemMainDomLockTests : UmbracoIntegrationTest + { + private IMainDomKeyGenerator MainDomKeyGenerator { get; set; } + + private IHostingEnvironment HostingEnvironment { get; set; } + + private FileSystemMainDomLock FileSystemMainDomLock { get; set; } + + private string LockFilePath { get; set; } + private string LockReleaseFilePath { get; set; } + + [SetUp] + public void SetUp() + { + MainDomKeyGenerator = GetRequiredService(); + HostingEnvironment = GetRequiredService(); + + var lockFileName = $"MainDom_{MainDomKeyGenerator.GenerateKey()}.lock"; + LockFilePath = Path.Combine(HostingEnvironment.LocalTempPath, lockFileName); + LockReleaseFilePath = LockFilePath + "_release"; + + var globalSettings = Mock.Of>(); + Mock.Get(globalSettings).Setup(x => x.CurrentValue).Returns(new GlobalSettings()); + + var log = GetRequiredService>(); + FileSystemMainDomLock = new FileSystemMainDomLock(log, MainDomKeyGenerator, HostingEnvironment, globalSettings); + } + + [TearDown] + public void TearDown() + { + CleanupTestFile(LockFilePath); + CleanupTestFile(LockReleaseFilePath); + } + + private static void CleanupTestFile(string path) + { + for (var i = 0; i < 3; i++) + { + try + { + File.Delete(path); + return; + } + catch + { + Thread.Sleep(500 * (i + 1)); + } + } + } + + [Test] + public async Task AcquireLockAsync_WhenNoOtherHoldsLockFileHandle_ReturnsTrue() + { + using var sut = FileSystemMainDomLock; + + var result = await sut.AcquireLockAsync(1000); + + Assert.True(result); + } + + [Test] + public async Task AcquireLockAsync_WhenTimeoutExceeded_ReturnsFalse() + { + await using var lockFile = File.Open(LockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + + using var sut = FileSystemMainDomLock; + + var result = await sut.AcquireLockAsync(1000); + + Assert.False(result); + } + + [Test] + public async Task ListenAsync_WhenLockReleaseSignalFileFound_DropsLockFileHandle() + { + using var sut = FileSystemMainDomLock; + + await sut.AcquireLockAsync(1000); + + var before = await sut.AcquireLockAsync(1000); + + sut.CreateLockReleaseSignalFile(); + await sut.ListenAsync(); + + var after = await sut.AcquireLockAsync(1000); + + Assert.Multiple(() => + { + Assert.False(before); + Assert.True(after); + }); + } + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs index 5b033fcd42..4f0c489516 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs @@ -1970,7 +1970,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services UserService.Save(admin); RelationService.Save(new RelationType("test", "test", false, Constants.ObjectTypes.Document, - Constants.ObjectTypes.Document)); + Constants.ObjectTypes.Document, false)); Assert.IsNotNull(RelationService.Relate(content1, content2, "test")); PublicAccessService.Save(new PublicAccessEntry(content1, content2, content2, diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs index 75dae7515b..621762917d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; @@ -24,7 +23,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class MacroServiceTests : UmbracoIntegrationTest { - private IMacroService MacroService => GetRequiredService(); + [Obsolete("After merging IMacroWithAliasService interface with IMacroService in Umbraco 11, this should go back to just being GetRequiredService()")] + private IMacroWithAliasService MacroService => GetRequiredService() as IMacroWithAliasService; [SetUp] public void SetupTest() @@ -52,6 +52,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreEqual("Test1", macro.Name); } + [Test] + public void Can_Get_By_Aliases() + { + // Act + IEnumerable macros = MacroService.GetAll("test1", "test2"); + + // Assert + Assert.IsNotNull(macros); + Assert.AreEqual(2, macros.Count()); + Assert.AreEqual("Test1", macros.ToArray()[0].Name); + Assert.AreEqual("Test2", macros.ToArray()[1].Name); + } + [Test] public void Can_Get_All() { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs index 3edba86760..0156100f0a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs @@ -266,4 +266,4 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // TODO: Create a relation for entities of the wrong Entity Type (GUID) based on the Relation Type's defined parent/child object types } -} +} \ No newline at end of file diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs index 9228182a8a..a08554d669 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; using Microsoft.Extensions.Options; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; @@ -15,7 +16,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validati public void Returns_Success_ForValid_Configuration() { var validator = new GlobalSettingsValidator(); - GlobalSettings options = BuildGlobalSettings(); + var options = new GlobalSettings(); ValidateOptionsResult result = validator.Validate("settings", options); Assert.True(result.Succeeded); } @@ -24,18 +25,55 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validati public void Returns_Fail_For_Configuration_With_Invalid_SmtpFrom_Field() { var validator = new GlobalSettingsValidator(); - GlobalSettings options = BuildGlobalSettings(smtpEmail: "invalid"); + var options = new GlobalSettings + { + Smtp = new SmtpSettings + { + From = "invalid", + } + }; + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } - private static GlobalSettings BuildGlobalSettings(string smtpEmail = "test@test.com") => - new GlobalSettings + [Test] + public void Returns_Fail_For_Configuration_With_Insufficient_SqlWriteLockTimeOut() + { + var validator = new GlobalSettingsValidator(); + var options = new GlobalSettings { - Smtp = new SmtpSettings - { - From = smtpEmail, - } + SqlWriteLockTimeOut = TimeSpan.Parse("00:00:00.099") }; + + ValidateOptionsResult result = validator.Validate("settings", options); + Assert.False(result.Succeeded); + } + + [Test] + public void Returns_Fail_For_Configuration_With_Excessive_SqlWriteLockTimeOut() + { + var validator = new GlobalSettingsValidator(); + var options = new GlobalSettings + { + SqlWriteLockTimeOut = TimeSpan.Parse("00:00:21") + }; + + ValidateOptionsResult result = validator.Validate("settings", options); + Assert.False(result.Succeeded); + } + + [Test] + public void Returns_Success_For_Configuration_With_Valid_SqlWriteLockTimeOut() + { + var validator = new GlobalSettingsValidator(); + var options = new GlobalSettings + { + SqlWriteLockTimeOut = TimeSpan.Parse("00:00:20") + }; + + ValidateOptionsResult result = validator.Validate("settings", options); + Assert.True(result.Succeeded); + } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTypeHistoryCleanupTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTypeHistoryCleanupTests.cs new file mode 100644 index 0000000000..1a9d293527 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTypeHistoryCleanupTests.cs @@ -0,0 +1,105 @@ +using System.Linq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Tests.Common.Builders; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models +{ + [TestFixture] + public class ContentTypeHistoryCleanupTests + { + [Test] + public void Changing_Keep_all_Makes_ContentType_Dirty() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + + Assert.IsFalse(contentType.IsDirty()); + + var newValue = 2; + contentType.HistoryCleanup.KeepAllVersionsNewerThanDays = newValue; + Assert.IsTrue(contentType.IsDirty()); + Assert.AreEqual(newValue, contentType.HistoryCleanup.KeepAllVersionsNewerThanDays); + } + + [Test] + public void Changing_Keep_latest_Makes_ContentType_Dirty() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + + Assert.IsFalse(contentType.IsDirty()); + + var newValue = 2; + contentType.HistoryCleanup.KeepLatestVersionPerDayForDays = newValue; + Assert.IsTrue(contentType.IsDirty()); + Assert.AreEqual(newValue, contentType.HistoryCleanup.KeepLatestVersionPerDayForDays); + } + + [Test] + public void Changing_Prevent_Cleanup_Makes_ContentType_Dirty() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + + Assert.IsFalse(contentType.IsDirty()); + + var newValue = true; + contentType.HistoryCleanup.PreventCleanup = newValue; + Assert.IsTrue(contentType.IsDirty()); + Assert.AreEqual(newValue, contentType.HistoryCleanup.PreventCleanup); + } + + [Test] + public void Replacing_History_Cleanup_Registers_As_Dirty() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + Assert.IsFalse(contentType.IsDirty()); + + contentType.HistoryCleanup = new HistoryCleanup(); + + Assert.IsTrue(contentType.IsDirty()); + Assert.IsTrue(contentType.IsPropertyDirty(nameof(contentType.HistoryCleanup))); + } + + [Test] + public void Replacing_History_Cleanup_Removes_Old_Dirty_History_Properties() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + + contentType.Alias = "NewValue"; + contentType.HistoryCleanup.KeepAllVersionsNewerThanDays = 2; + + contentType.PropertyChanged += (sender, args) => + { + // Ensure that property changed is only invoked for history cleanup + Assert.AreEqual(nameof(contentType.HistoryCleanup), args.PropertyName); + }; + + // Since we're replacing the entire HistoryCleanup the changed property is no longer dirty, the entire HistoryCleanup is + contentType.HistoryCleanup = new HistoryCleanup(); + + Assert.Multiple(() => + { + Assert.IsTrue(contentType.IsDirty()); + Assert.IsFalse(contentType.WasDirty()); + Assert.AreEqual(2, contentType.GetDirtyProperties().Count()); + Assert.IsTrue(contentType.IsPropertyDirty(nameof(contentType.HistoryCleanup))); + Assert.IsTrue(contentType.IsPropertyDirty(nameof(contentType.Alias))); + }); + } + + [Test] + public void Old_History_Cleanup_Reference_Doesnt_Make_Content_Type_Dirty() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + var oldHistoryCleanup = contentType.HistoryCleanup; + + contentType.HistoryCleanup = new HistoryCleanup(); + contentType.ResetDirtyProperties(); + contentType.ResetWereDirtyProperties(); + + oldHistoryCleanup.KeepAllVersionsNewerThanDays = 2; + + Assert.IsFalse(contentType.IsDirty()); + Assert.IsFalse(contentType.WasDirty()); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs index 273823eec3..12c5f50d30 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs @@ -1,10 +1,14 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; +using System.Security.Cryptography; +using System.Text; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Security; +using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security @@ -15,7 +19,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security [Test] public void Check_Password_Hashed_Non_KeyedHashAlgorithm() { - IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == "SHA256"); + IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == "SHA1"); var passwordSecurity = new LegacyPasswordSecurity(); var pass = "ThisIsAHashedPassword"; @@ -45,14 +49,12 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security [Test] public void Check_Password_Legacy_v4_SHA1() { - IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName); - var passwordSecurity = new LegacyPasswordSecurity(); + const string clearText = "ThisIsAHashedPassword"; + var clearTextUnicodeBytes = Encoding.Unicode.GetBytes(clearText); + using var algorithm = new HMACSHA1(clearTextUnicodeBytes); + var dbPassword = Convert.ToBase64String(algorithm.ComputeHash(clearTextUnicodeBytes)); - var pass = "ThisIsAHashedPassword"; - var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out string salt); - var storedPassword = passwordSecurity.FormatPasswordForStorage(passwordConfiguration.HashAlgorithmType, hashed, salt); - - var result = passwordSecurity.VerifyPassword(passwordConfiguration.HashAlgorithmType, "ThisIsAHashedPassword", storedPassword); + var result = new LegacyPasswordSecurity().VerifyLegacyHashedPassword(clearText, dbPassword); Assert.IsTrue(result); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SiteIdentifierServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SiteIdentifierServiceTests.cs new file mode 100644 index 0000000000..81934cc1be --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SiteIdentifierServiceTests.cs @@ -0,0 +1,77 @@ +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Telemetry; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry +{ + [TestFixture] + public class SiteIdentifierServiceTests + { + [TestCase("0F1785C5-7BA0-4C52-AB62-863BD2C8F3FE", true)] + [TestCase("This is not a guid", false)] + [TestCase("", false)] + [TestCase("00000000-0000-0000-0000-000000000000", false)] // Don't count empty GUID as valid + public void TryGetOnlyPassesIfValidId(string guidString, bool shouldSucceed) + { + var globalSettings = CreateGlobalSettings(guidString); + var sut = new SiteIdentifierService( + globalSettings, + Mock.Of(), + Mock.Of>()); + + var result = sut.TryGetSiteIdentifier(out var siteIdentifier); + + Assert.AreEqual(shouldSucceed, result); + if (shouldSucceed) + { + // When toString is called on a GUID it will to lower, so do the same to our guidString + Assert.AreEqual(guidString.ToLower(), siteIdentifier.ToString()); + } + else + { + Assert.AreEqual(Guid.Empty, siteIdentifier); + } + } + + [TestCase("0F1785C5-7BA0-4C52-AB62-863BD2C8F3FE", false)] + [TestCase("This is not a guid", true)] + [TestCase("", true)] + [TestCase("00000000-0000-0000-0000-000000000000", true)] // Don't count empty GUID as valid + public void TryGetOrCreateOnlyCreatesNewGuidIfCurrentIsMissingOrInvalid(string guidString, bool shouldCreate) + { + var globalSettings = CreateGlobalSettings(guidString); + var configManipulatorMock = new Mock(); + + var sut = new SiteIdentifierService( + globalSettings, + configManipulatorMock.Object, + Mock.Of>()); + + var result = sut.TryGetOrCreateSiteIdentifier(out var identifier); + + if (shouldCreate) + { + configManipulatorMock.Verify(x => x.SetGlobalId(It.IsAny()), Times.Once); + Assert.AreNotEqual(Guid.Empty, identifier); + Assert.IsTrue(result); + } + else + { + configManipulatorMock.Verify(x => x.SetGlobalId(It.IsAny()), Times.Never()); + Assert.AreEqual(guidString.ToLower(), identifier.ToString()); + Assert.IsTrue(result); + } + } + + private IOptionsMonitor CreateGlobalSettings(string guidString) + { + var globalSettings = new GlobalSettings { Id = guidString }; + return Mock.Of>(x => x.CurrentValue == globalSettings); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs index 1c92569695..910ca7c792 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/TelemetryServiceTests.cs @@ -15,36 +15,36 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry [TestFixture] public class TelemetryServiceTests { - [TestCase("0F1785C5-7BA0-4C52-AB62-863BD2C8F3FE", true)] - [TestCase("This is not a guid", false)] - [TestCase("", false)] - public void OnlyParsesIfValidId(string guidString, bool shouldSucceed) + [Test] + public void UsesGetOrCreateSiteId() { - var globalSettings = CreateGlobalSettings(guidString); - var version = CreateUmbracoVersion(9, 1, 1); - var sut = new TelemetryService(globalSettings, Mock.Of(), version); + var version = CreateUmbracoVersion(9, 3, 1); + var siteIdentifierServiceMock = new Mock(); + var sut = new TelemetryService(Mock.Of(), version, siteIdentifierServiceMock.Object); + Guid guid; + + var result = sut.TryGetTelemetryReportData(out var telemetryReportData); + siteIdentifierServiceMock.Verify(x => x.TryGetOrCreateSiteIdentifier(out guid), Times.Once); + } + + [Test] + public void SkipsIfCantGetOrCreateId() + { + var version = CreateUmbracoVersion(9, 3, 1); + var sut = new TelemetryService(Mock.Of(), version, createSiteIdentifierService(false)); var result = sut.TryGetTelemetryReportData(out var telemetry); - Assert.AreEqual(shouldSucceed, result); - if (shouldSucceed) - { - // When toString is called on a GUID it will to lower, so do the same to our guidString - Assert.AreEqual(guidString.ToLower(), telemetry.Id.ToString()); - } - else - { - Assert.IsNull(telemetry); - } + Assert.IsFalse(result); + Assert.IsNull(telemetry); } [Test] public void ReturnsSemanticVersionWithoutBuild() { - var globalSettings = CreateGlobalSettings(); var version = CreateUmbracoVersion(9, 1, 1, "-rc", "-ad2f4k2d"); - var sut = new TelemetryService(globalSettings, Mock.Of(), version); + var sut = new TelemetryService(Mock.Of(), version, createSiteIdentifierService()); var result = sut.TryGetTelemetryReportData(out var telemetry); @@ -55,7 +55,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry [Test] public void CanGatherPackageTelemetry() { - var globalSettings = CreateGlobalSettings(); var version = CreateUmbracoVersion(9, 1, 1); var versionPackageName = "VersionPackage"; var packageVersion = "1.0.0"; @@ -66,7 +65,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry new () { PackageName = noVersionPackageName } }; var manifestParser = CreateManifestParser(manifests); - var sut = new TelemetryService(globalSettings, manifestParser, version); + var sut = new TelemetryService(manifestParser, version, createSiteIdentifierService()); var success = sut.TryGetTelemetryReportData(out var telemetry); @@ -87,15 +86,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry [Test] public void RespectsAllowPackageTelemetry() { - var globalSettings = CreateGlobalSettings(); var version = CreateUmbracoVersion(9, 1, 1); PackageManifest[] manifests = { new () { PackageName = "DoNotTrack", AllowPackageTelemetry = false }, - new () { PackageName = "TrackingAllowed", AllowPackageTelemetry = true } + new () { PackageName = "TrackingAllowed", AllowPackageTelemetry = true }, }; var manifestParser = CreateManifestParser(manifests); - var sut = new TelemetryService(globalSettings, manifestParser, version); + var sut = new TelemetryService(manifestParser, version, createSiteIdentifierService()); var success = sut.TryGetTelemetryReportData(out var telemetry); @@ -121,15 +119,12 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry return Mock.Of(x => x.SemanticVersion == version); } - private IOptionsMonitor CreateGlobalSettings(string guidString = null) + private ISiteIdentifierService createSiteIdentifierService(bool shouldSucceed = true) { - if (guidString is null) - { - guidString = Guid.NewGuid().ToString(); - } - - var globalSettings = new GlobalSettings { Id = guidString }; - return Mock.Of>(x => x.CurrentValue == globalSettings); + var mock = new Mock(); + var siteIdentifier = shouldSucceed ? Guid.NewGuid() : Guid.Empty; + mock.Setup(x => x.TryGetOrCreateSiteIdentifier(out siteIdentifier)).Returns(shouldSucceed); + return mock.Object; } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs index 2a8f006ab6..08999affe2 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs @@ -2,12 +2,15 @@ // See LICENSE for more details. using System; +using System.Data; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; @@ -108,6 +111,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockServerMessenger = new Mock(); + var mockScopeProvider = new Mock(); + mockScopeProvider + .Setup(x => x.CreateScope(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Mock.Of()); + return new ScheduledPublishing( mockRunTimeState.Object, mockMainDom.Object, @@ -115,7 +123,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices _mockContentService.Object, mockUmbracoContextFactory.Object, _mockLogger.Object, - mockServerMessenger.Object); + mockServerMessenger.Object, + mockScopeProvider.Object); } private void VerifyScheduledPublishingNotPerformed() => VerifyScheduledPublishingPerformed(Times.Never()); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Runtime/DefaultMainDomKeyGeneratorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Runtime/DefaultMainDomKeyGeneratorTests.cs new file mode 100644 index 0000000000..9b013aa38f --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Runtime/DefaultMainDomKeyGeneratorTests.cs @@ -0,0 +1,47 @@ +using AutoFixture.NUnit3; +using Microsoft.Extensions.Options; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Infrastructure.Runtime; +using Umbraco.Cms.Tests.UnitTests.AutoFixture; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Runtime +{ + [TestFixture] + internal class DefaultMainDomKeyGeneratorTests + { + [Test] + [AutoMoqData] + public void GenerateKey_WithConfiguredDiscriminatorValue_AltersHash( + [Frozen] IHostingEnvironment hostingEnvironment, + [Frozen] GlobalSettings globalSettings, + [Frozen] IOptionsMonitor globalSettingsMonitor, + DefaultMainDomKeyGenerator sut, + string aDiscriminator) + { + var withoutDiscriminator = sut.GenerateKey(); + globalSettings.MainDomKeyDiscriminator = aDiscriminator; + var withDiscriminator = sut.GenerateKey(); + + Assert.AreNotEqual(withoutDiscriminator, withDiscriminator); + } + + [Test] + [AutoMoqData] + public void GenerateKey_WithUnchangedDiscriminatorValue_ReturnsSameValue( + [Frozen] IHostingEnvironment hostingEnvironment, + [Frozen] GlobalSettings globalSettings, + [Frozen] IOptionsMonitor globalSettingsMonitor, + DefaultMainDomKeyGenerator sut, + string aDiscriminator) + { + globalSettings.MainDomKeyDiscriminator = aDiscriminator; + + var a = sut.GenerateKey(); + var b = sut.GenerateKey(); + + Assert.AreEqual(a, b); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs new file mode 100644 index 0000000000..db4ec3392f --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs @@ -0,0 +1,127 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using AutoFixture.NUnit3; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Tests.UnitTests.AutoFixture; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security +{ + [TestFixture] + public class UmbracoPasswordHasherTests + { + // Technically MD5, HMACSHA384 & HMACSHA512 were also possible but opt in as opposed to historic defaults. + [Test] + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "uB/pLEhhe1W7EtWMv/pSgg==1y8+aso9+h3AKRtJXlVYeg2TZKJUr64hccj82ZZ7Ksk=")] // Actually HMACSHA256 + [InlineAutoMoqData("SHA1", "Umbraco9Rocks!", "6tZGfG9NTxJJYp19Fac9og==zzRggqANxhb+CbD/VabEt8cIde8=")] // When SHA1 is set on machine key. + public void VerifyHashedPassword_ValidHashWithoutLegacyEncoding_ReturnsSuccessRehashNeeded( + string algorithm, + string providedPassword, + string hashedPassword, + [Frozen] IJsonSerializer jsonSerializer, + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + Mock.Get(jsonSerializer) + .Setup(x => x.Deserialize(It.IsAny())) + .Returns(new PersistedPasswordSettings{ HashAlgorithm = algorithm }); + + var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); + + Assert.AreEqual(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + [Test] + [InlineAutoMoqData("HMACSHA1", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + [InlineAutoMoqData("FOOBARBAZQUX", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + [InlineAutoMoqData("", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + [InlineAutoMoqData(null, "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + public void VerifyHashedPassword_ValidHashWithLegacyEncoding_ReturnsSuccessRehashNeeded( + string algorithm, + string providedPassword, + string hashedPassword, + [Frozen] IJsonSerializer jsonSerializer, + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + Mock.Get(jsonSerializer) + .Setup(x => x.Deserialize(It.IsAny())) + .Returns(new PersistedPasswordSettings { HashAlgorithm = algorithm }); + + var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); + + Assert.AreEqual(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + [Test] + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "aB/cDeFaBcDefAbcD/EfaB==1y8+aso9+h3AKRtJXlVYeg2TZKJUr64hccj82ZZ7Ksk=")] + public void VerifyHashedPassword_WithIncorrectPassword_ReturnsFailed( + string algorithm, + string providedPassword, + string hashedPassword, + [Frozen] IJsonSerializer jsonSerializer, + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + Mock.Get(jsonSerializer) + .Setup(x => x.Deserialize(It.IsAny())) + .Returns(new PersistedPasswordSettings { HashAlgorithm = algorithm }); + + var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); + + Assert.AreEqual(PasswordVerificationResult.Failed, result); + } + + [Test] + [AutoMoqData] + public void VerifyHashedPassword_WithIdentityV1OrV2StyleHash_ReturnsSuccessRehashNeeded( + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + var options = Options.Create(new PasswordHasherOptions + { + CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2 + }); + + var upstreamHasher = new PasswordHasher(options); + + const string password = "Umbraco9Rocks!"; + var identityV1Or2StyleHash = upstreamHasher.HashPassword(aUser, password); + var result = sut.VerifyHashedPassword(aUser, identityV1Or2StyleHash, password); + + Assert.AreEqual(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + [Test] + [AutoMoqData] + public void VerifyHashedPassword_WithIdentityV3StyleHash_ReturnsSuccess( + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + var options = Options.Create(new PasswordHasherOptions + { + CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV3 + }); + + var upstreamHasher = new PasswordHasher(options); + + const string password = "Umbraco9Rocks!"; + var identityV1Or2StyleHash = upstreamHasher.HashPassword(aUser, password); + var result = sut.VerifyHashedPassword(aUser, identityV1Or2StyleHash, password); + + Assert.AreEqual(PasswordVerificationResult.Success, result); + } + + public class TestUserStub : UmbracoIdentityUser + { + public TestUserStub() => PasswordConfig = "not null or empty"; + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs index c25b2fde1e..fdb29f88e6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs @@ -323,6 +323,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.PublishedCache.NuCache } [Test] + [Retry(5)] // TODO make this test non-flaky. public async Task EventuallyCollectNulls() { var d = new SnapDictionary(); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationBuilderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationBuilderTests.cs index 67f887d123..bde8b8f092 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationBuilderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationBuilderTests.cs @@ -44,6 +44,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Tests.Common.Builders .WithAlias(relationTypeAlias) .WithName(relationTypeName) .WithIsBidirectional(false) + .WithIsDependency(true) .WithParentObjectType(parentObjectType) .WithChildObjectType(childObjectType) .Done() @@ -61,6 +62,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Tests.Common.Builders Assert.AreEqual(relationTypeAlias, relation.RelationType.Alias); Assert.AreEqual(relationTypeName, relation.RelationType.Name); Assert.IsFalse(relation.RelationType.IsBidirectional); + + Assert.IsTrue((relation.RelationType as IRelationTypeWithIsDependency).IsDependency); Assert.AreEqual(parentObjectType, relation.RelationType.ParentObjectType); Assert.AreEqual(childObjectType, relation.RelationType.ChildObjectType); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationTypeBuilderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationTypeBuilderTests.cs index 877023f82f..e99f5ae273 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationTypeBuilderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationTypeBuilderTests.cs @@ -26,11 +26,12 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Tests.Common.Builders var parentObjectType = Guid.NewGuid(); var childObjectType = Guid.NewGuid(); const bool isBidirectional = true; + const bool isDependency = true; var builder = new RelationTypeBuilder(); // Act - IRelationType relationType = builder + var relationType = builder .WithId(id) .WithAlias(alias) .WithName(name) @@ -41,6 +42,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Tests.Common.Builders .WithParentObjectType(parentObjectType) .WithChildObjectType(childObjectType) .WithIsBidirectional(isBidirectional) + .WithIsDependency(isDependency) .Build(); // Assert @@ -54,6 +56,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Tests.Common.Builders Assert.AreEqual(parentObjectType, relationType.ParentObjectType); Assert.AreEqual(childObjectType, relationType.ChildObjectType); Assert.AreEqual(isBidirectional, relationType.IsBidirectional); + Assert.AreEqual(isDependency, relationType.IsDependency); } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilderTests.cs new file mode 100644 index 0000000000..06cf2fad43 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilderTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authentication; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Web.BackOffice.Security; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security +{ + [TestFixture] + public class BackOfficeAuthenticationBuilderTests + { + [Test] + public void EnsureBackOfficeScheme_When_Backoffice_Auth_Scheme_Expect_Updated_SignInScheme() + { + var scheme = $"{Constants.Security.BackOfficeExternalAuthenticationTypePrefix}test"; + var options = new RemoteAuthenticationOptions + { + SignInScheme = "my_cookie" + }; + + var sut = new BackOfficeAuthenticationBuilder.EnsureBackOfficeScheme(); + sut.PostConfigure(scheme, options); + + Assert.AreEqual(options.SignInScheme, Constants.Security.BackOfficeExternalAuthenticationType); + } + + [Test] + public void EnsureBackOfficeScheme_When_Not_Backoffice_Auth_Scheme_Expect_No_Change() + { + var scheme = "test"; + var options = new RemoteAuthenticationOptions + { + SignInScheme = "my_cookie" + }; + + var sut = new BackOfficeAuthenticationBuilder.EnsureBackOfficeScheme(); + sut.PostConfigure(scheme, options); + + Assert.AreNotEqual(options.SignInScheme, Constants.Security.BackOfficeExternalAuthenticationType); + } + } +} \ No newline at end of file