]]>
[%0%] Notificering om %1% udført på %2%
- Notificeringer
+ NotifikationerHandlinger
@@ -2104,25 +2105,25 @@ Mange hilsner fra Umbraco robotten
Test beståetTest fejletÅben backoffice søgning
- Åben/Luk backoffice hjælp
- Ă…ben/Luk dine profil indstillinger
+ Åben/luk backoffice hjælp
+ Åben/luk dine profilindstillingerTilføj domæne på %0%Opret ny node under %0%Opsæt offentlig adgang på %0%Opsæt rettigheder på %0%
- Juster soterings rækkefølgen for %0%
- Opret indholds skabelon baseret pĂĄ %0%
- Ă…ben kontext menu for
+ Juster sorteringsrækkefølgen for %0%
+ Opret indholdsskabelon baseret pĂĄ %0%
+ Ă…ben kontekstmenu forAktivt sprogSkift sprog tilOpret ny mappe
- Delvist View
- Delvist View Macro
+ Partial View
+ Partial View MacroMedlemData typeSøg i viderestillings dashboardet
- Søg i brugergruppe sektionen
- Søg i bruger sektionen
+ Søg i brugergruppesektionen
+ Søg i brugersektionenOpret elementOpretRediger
@@ -2146,16 +2147,16 @@ Mange hilsner fra Umbraco robotten
Referencer
- Denne Data Type har ingen referencer.
- Brugt i Medie Typer
- Brugt i Medlems Typer
- Brugt af
- Brugt i Dokumenter
- Brugt i Medlemmer
- Brugt i Medier
+ Denne Datatype har ingen referencer.
+ Bruges i Medietyper
+ Bruges i Medlemstyper
+ Bruges af
+ Bruges i Dokumenter
+ Bruges i Medlemmer
+ Bruges i Medier
- Slet gemte søgning
+ Slet gemt søgningLog typeVælg alleFravælg alle
@@ -2187,7 +2188,7 @@ Mange hilsner fra Umbraco robotten
Slet denne søgningFind logs med request IdFind logs med Namespace
- Find logs med maskin navn
+ Find logs med maskinnavnĂ…benHenterHver 2 sekunder
@@ -2224,19 +2225,19 @@ Mange hilsner fra Umbraco robotten
Redigerings udseendeData modellerkatalog udseende
- Baggrunds farve
+ BaggrundsfarveIkon farveIndholds modelLabelSpeciel visningVis speciel visning beskrivelsen
- Overskrift hvordan denne block præsenteres i backoffice interfacet. Vælg en
- .html fil der indeholder din præsensation.
+ Overskriv hvordan denne blok præsenteres i backoffice. Vælg en
+ .html fil der indeholder din visning.
Indstillings model
- Rederings lagets størrelse
+ Redigeringsvinduets størrelseTilføj speciel visning
- Tilføj instillinger
+ Tilføj indstillinger
%0%?]]>
@@ -2261,7 +2262,7 @@ Mange hilsner fra Umbraco robotten
Skjul indholdseditorenSkjul indholds redigerings knappen samt indholdseditoren i Blok Redigerings vinduetDirekte redigering
- Tilføjer direkte redigering a det første felt. Yderligere felter optræder kun i redigerings vinduet.
+ Tilføjer direkte redigering af det første felt. Yderligere felter optræder kun i redigerings vinduet.Du har lavet ændringer til dette indhold. Er du sikker på at du vil kassere dem?Annuller oprettelse?
@@ -2284,11 +2285,11 @@ Mange hilsner fra Umbraco robotten
Alle blokke, der er oprettet i dette område, vil blive slettet.Layout-opsætningStruktur
- Størrelses opsætning
+ StørrelsesopsætningTilgængelige kolonne-størrelser
- Vælg de forskellige antal kolonner denne blok må optage i layoutet. Dette forhindre ikke blokken i at optræde i et mindre område.
- TIlgængelige række-størrelser
- Vælg hvor mange rækker denne blok på optage i layoutet.
+ Vælg de forskellige antal kolonner denne blok må optage i layoutet. Dette forhindrer ikke blokken i at optræde i et mindre område.
+ Tilgængelige række-størrelser
+ Vælg hvor mange rækker denne blok må optage i layoutet.Tillad på rodniveauGør denne blok tilgængelig i layoutets rodniveau. Hvis dette ikke er valgt, kan denne blok kun bruges inden for andre blokkes definerede områder.Blok-områder
@@ -2302,11 +2303,11 @@ Mange hilsner fra Umbraco robotten
-
+ Træk for at skalereTilføj indhold label
- Overskriv labellen for tilføj indholds knappen i dette område.
- Tilføj skalerings muligheder
+ Overskriv labelen for tilføj indholds knappen i dette område.
+ Tilføj skaleringsmulighederTilføj BlokTilføj gruppeTilføj gruppe eller Blok
@@ -2319,14 +2320,14 @@ Mange hilsner fra Umbraco robotten
AvanceretTilladelserInstaller demo konfiguration
- Dette indeholder Blokke for Overskrift, Beriget-Tekst, Billede og To-Koloners-Layout.]]>
+ Dette indeholder Blokke for Overskrift, Formateret Tekst, Billede og To-Kolonners-Layout.]]>Installer
- Sortings tilstand
- Afslut sortings tilstand
+ Sortingstilstand
+ Afslut sortingstilstandDette område alias skal være unikt sammenlignet med andre områder af denne Blok.Konfigurer områdeSlet område
- Tilføj mulighed for %0% koloner
+ Tilføj mulighed for %0% kolonnerIndsæt BlokVis på linje med tekst
@@ -2358,12 +2359,12 @@ Mange hilsner fra Umbraco robotten
Vis i nyt vindueĂ…ben forhĂĄndsvisning i nyt vindueForhĂĄndsvisning af indholdet?
- Du har afslutet forhĂĄndsvisning, vil du starte forhĂĄndsvisning igen for at
+ Du har afsluttet forhĂĄndsvisning, vil du starte forhĂĄndsvisning igen for at
se seneste gemte version af indholdet?
Se udgivet indholdSe udgivet indhold?
- Du er i forhĂĄndsvisning, vil du afslutte for at se den udgivet
+ Du er i forhĂĄndsvisning, vil du afslutte for at se den udgivne
version?
Se udgivet version
@@ -2373,7 +2374,7 @@ Mange hilsner fra Umbraco robotten
MappeoprettelseFilskrivning for pakkerFilskrivning
- Medie mappeoprettelse
+ Mediemappeoprettelseresultat
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
index 572fe05dd4..4421e7d2f5 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
@@ -56,6 +56,8 @@
Create Content TemplateResend InvitationHide unavailable options
+ Change Data Type
+ Edit contentContent
@@ -158,6 +160,7 @@
ConfirmMore publishing optionsSubmit
+ Generate models and closeMedia deleted
@@ -198,6 +201,8 @@
SaveSaveHistory (all variants)
+ Content unpublished for languages: %0%
+ UnpublishThe folder name cannot contain illegal characters.
@@ -325,6 +330,12 @@
Create newPaste from clipboardThis item is in the Recycle Bin
+ No content can be added for this item
+ Save is not allowed
+ Publish is not allowed
+ Send for approval is not allowed
+ Schedule is not allowed
+ Unpublish is not allowed%0%]]>
@@ -349,12 +360,19 @@
Failed to rename the folder with id %0%Drag and drop your file(s) into the areaOne or more file security validations have failed
+ Parent and destination folders cannot be the same
+ Upload is not allowed in this location.Create a new memberAll MembersMember groups have no additional properties for editing.Two-Factor Authentication
+ A member with this login already exists
+ The member is already in group '%0%'
+ The member already has a password set
+ Lockout is not enabled for this member
+ The member is not in group '%0%'Failed to copy content type
@@ -476,7 +494,6 @@
LinkAnchor / querystringName
- Manage hostnamesClose this windowAre you sure you want to delete%0% of %1% items]]>
@@ -581,6 +598,7 @@
Yes, removeYou are deleting the layoutModifying layout will result in loss of data for any existing content that is based on this configuration.
+ Select configuration
@@ -689,6 +707,9 @@
Select the folder to moveto in the tree structure belowwas moved underneath
+ Changing a property editor on a data type with stored values is disabled. To allow this you can change the Umbraco:CMS:DataTypes:CanBeChanged setting in appsettings.json.
+ %0% will delete the properties and their data from the following items]]>
+ I understand this action will delete the properties and data based on this Data TypeYour data has been saved, but before you can publish this page there are some
@@ -729,6 +750,8 @@
Please place cursor at the left of the two cells you wish to mergeYou cannot split a cell that hasn't been merged.This property is invalid
+ An unknown failure has occurred
+ Optimistic concurrency failure, object has been modifiedAbout
@@ -893,6 +916,13 @@
Last UpdatedSkip to menuSkip to content
+ Primary
+ Change
+ Crop section
+ Generic
+ Media
+ Revert
+ ValidateBlue
@@ -919,6 +949,7 @@
GeneralEditorToggle allow culture variants
+ Add tabBackground colour
@@ -1368,6 +1399,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
so uninstall with caution. If in doubt, contact the package author.]]>Package versionVerified to work on Umbraco Cloud
+ Package migrations have successfully completed.
+ All package migrations have successfully completed.Paste with full formatting (Not recommended)
@@ -1425,6 +1458,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Publish to publish %0% and thereby making its content publicly available.
You can publish this page and all its subpages by checking Include unpublished subpages below.
]]>
+ Insufficient user permissions to publish all descendant documents
+ %0% could not be published because the item is in the recycle bin.
+ Validation failed for required language '%0%'. This language was saved but not published.You have not configured any approved colours
@@ -1517,25 +1553,22 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Rollback toSelect versionView
+
+ Versions
+ Current draft version
+ Current published versionEdit script file
- ConciergeContent
- Courier
- DeveloperForms
- Help
- Umbraco Configuration WizardMediaMembers
- NewslettersPackagesMarketplaceSettings
- StatisticsTranslationUsers
@@ -1576,6 +1609,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
column headers to sort the entire collection of items
+ This node has no child nodes to sortValidation
@@ -1587,7 +1621,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Operation was cancelled by a 3rd party add-inThis file is being uploaded as part of a folder, but creating a new folder is not allowed hereCreating a new folder is not allowed here
- Publishing was cancelled by a 3rd party add-inProperty type already existsProperty type created DataType: %1%]]>
@@ -1601,7 +1634,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Stylesheet saved without any errorsDatatype savedDictionary item saved
- Publishing failed because the parent page isn't publishedContent publishedand visible on the websiteContent Template saved
@@ -1669,6 +1701,24 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Your system information has successfully been copied to the clipboardCould not copy your system information to the clipboardWebhook saved
+ Saved. To view the changes please reload your browser
+ %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
+ A schedule for publishing has been updated
+ %0% saved
+ %0% changes have been sent for approval
+ Content variation %0% unpublished
+ The mandatory language '%0%' was unpublished. All languages for this content item are now unpublished.
+ Cannot publish the document since the required '%0%' is not published
+ Validation failed for language '%0%'
+ The release date cannot be in the past
+ Cannot schedule the document for publishing since the required '%0%' is not published
+ Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language
+ The expire date cannot be in the past
+ The expire date cannot be before the release date
+ An error occurred while enabling version cleanup for %0%
+ An error occurred while disabling version cleanup for %0%Add style
@@ -1932,6 +1982,18 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Prevent cleanupEnable cleanupNOTE! The cleanup of historically content versions are disabled globally. These settings will not take effect before it is enabled.]]>
+ You can't move the group %0% to this tab because the group will get the same alias as a tab: "%1%". Rename the group to continue.
+ Available configurations
+ Create a new configuration
+ %0%?]]>
+ %0%?]]>
+ %0%?]]>
+ This will also delete all items below this tab.
+ This will also delete all items below this group.
+ Add tab
+ Convert to tab
+ Drag properties here to place directly on the tab
+ Changing a data type with stored values is disabled. To allow this you can change the Umbraco:CMS:DataTypes:CanBeChanged setting in appsettings.json.Create webhook
@@ -1939,8 +2001,16 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Add Document TypeAdd Media TypeCreate header
- Logs
+ DeliveriesNo webhook headers have been added
+ No events were found.
+ Enabled
+ Events
+ Event
+ Url
+ Types
+ Webhook key
+ Retry countAdd language
@@ -1959,6 +2029,11 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Fall back languagenone
+ %0% is shared across languages and segments.]]>
+ %0% is shared across all languages.]]>
+ %0% is shared across all segments.]]>
+ Shared: Languages
+ Shared: SegmentsAdd parameter
@@ -2312,10 +2387,22 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
This two-factor provider is now disabledSomething went wrong with trying to disable this two-factor providerDo you want to disable this two-factor provider for this user?
+ A user with this login already exists
+ The password must have at least one digit ('0'-'9')
+ The password must have at least one lowercase ('a'-'z')
+ The password must have at least one non alphanumeric character
+ The password must use at least %0% different characters
+ The password must have at least one uppercase ('A'-'Z')
+ The password must be at least %0% characters long
+ Allow access to all languages
+ The user already has a password set
+ The user is already in group '%0%'
+ Lockout is not enabled for this user
+ The user is not in group '%0%'
+ Configure Two-FactorValidation
- No validationValidate as an email addressValidate as a numberValidate as a URL
@@ -2342,6 +2429,14 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
%1% more.]]>%1% too many.]]>The content amount requirements are not met for one or more areas.
+ Invalid member group name
+ Invalid user group name
+ Invalid token
+ Invalid username
+ Email '%0%' is already taken
+ User group name '%0%' is already taken
+ Member group name '%0%' is already taken
+ Username '%0%' is already taken
+
diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs
index ba78af33b4..ad4486c698 100644
--- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs
+++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs
@@ -1,14 +1,18 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.HealthChecks;
using Umbraco.Cms.Core.HealthChecks.NotificationMethods;
using Umbraco.Cms.Core.Logging;
+using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Runtime;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
@@ -22,7 +26,7 @@ namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
///
public class HealthCheckNotifierJob : IRecurringBackgroundJob
{
-
+
public TimeSpan Period { get; private set; }
public TimeSpan Delay { get; private set; }
@@ -38,9 +42,30 @@ public class HealthCheckNotifierJob : IRecurringBackgroundJob
private readonly ILogger _logger;
private readonly HealthCheckNotificationMethodCollection _notifications;
private readonly IProfilingLogger _profilingLogger;
+ private readonly IEventAggregator _eventAggregator;
private readonly ICoreScopeProvider _scopeProvider;
private HealthChecksSettings _healthChecksSettings;
+ [Obsolete("Use constructor that accepts IEventAggregator as a parameter, scheduled for removal in V14")]
+ public HealthCheckNotifierJob(
+ IOptionsMonitor healthChecksSettings,
+ HealthCheckCollection healthChecks,
+ HealthCheckNotificationMethodCollection notifications,
+ ICoreScopeProvider scopeProvider,
+ ILogger logger,
+ IProfilingLogger profilingLogger,
+ ICronTabParser cronTabParser)
+ : this(
+ healthChecksSettings,
+ healthChecks,
+ notifications,
+ scopeProvider,
+ logger,
+ profilingLogger,
+ cronTabParser,
+ StaticServiceProvider.Instance.GetRequiredService())
+ { }
+
///
/// Initializes a new instance of the class.
///
@@ -58,7 +83,8 @@ public class HealthCheckNotifierJob : IRecurringBackgroundJob
ICoreScopeProvider scopeProvider,
ILogger logger,
IProfilingLogger profilingLogger,
- ICronTabParser cronTabParser)
+ ICronTabParser cronTabParser,
+ IEventAggregator eventAggregator)
{
_healthChecksSettings = healthChecksSettings.CurrentValue;
_healthChecks = healthChecks;
@@ -66,6 +92,7 @@ public class HealthCheckNotifierJob : IRecurringBackgroundJob
_scopeProvider = scopeProvider;
_logger = logger;
_profilingLogger = profilingLogger;
+ _eventAggregator = eventAggregator;
Period = healthChecksSettings.CurrentValue.Notification.Period;
Delay = DelayCalculator.GetDelay(healthChecksSettings.CurrentValue.Notification.FirstRunTime, cronTabParser, logger, TimeSpan.FromMinutes(3));
@@ -106,6 +133,8 @@ public class HealthCheckNotifierJob : IRecurringBackgroundJob
HealthCheckResults results = await HealthCheckResults.Create(checks);
results.LogResults();
+ _eventAggregator.Publish(new HealthCheckCompletedNotification(results));
+
// Send using registered notification methods that are enabled.
foreach (IHealthCheckNotificationMethod notificationMethod in _notifications.Where(x => x.Enabled))
{
diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs
index 11eaa3bb8a..e6d25aff0e 100644
--- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs
+++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs
@@ -1,4 +1,4 @@
-using System.Net.Mime;
+using System.Net.Mime;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -6,7 +6,6 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Scoping;
-using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
@@ -15,11 +14,11 @@ public class WebhookFiring : IRecurringBackgroundJob
{
private readonly ILogger _logger;
private readonly IWebhookRequestService _webhookRequestService;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IWebhookLogFactory _webhookLogFactory;
private readonly IWebhookLogService _webhookLogService;
private readonly IWebhookService _webHookService;
private readonly ICoreScopeProvider _coreScopeProvider;
+ private readonly IHttpClientFactory _httpClientFactory;
private WebhookSettings _webhookSettings;
public TimeSpan Period => _webhookSettings.Period;
@@ -32,20 +31,20 @@ public class WebhookFiring : IRecurringBackgroundJob
public WebhookFiring(
ILogger logger,
IWebhookRequestService webhookRequestService,
- IJsonSerializer jsonSerializer,
IWebhookLogFactory webhookLogFactory,
IWebhookLogService webhookLogService,
IWebhookService webHookService,
IOptionsMonitor webhookSettings,
- ICoreScopeProvider coreScopeProvider)
+ ICoreScopeProvider coreScopeProvider,
+ IHttpClientFactory httpClientFactory)
{
_logger = logger;
_webhookRequestService = webhookRequestService;
- _jsonSerializer = jsonSerializer;
_webhookLogFactory = webhookLogFactory;
_webhookLogService = webhookLogService;
_webHookService = webHookService;
_coreScopeProvider = coreScopeProvider;
+ _httpClientFactory = httpClientFactory;
_webhookSettings = webhookSettings.CurrentValue;
webhookSettings.OnChange(x => _webhookSettings = x);
}
@@ -72,8 +71,7 @@ public class WebhookFiring : IRecurringBackgroundJob
return;
}
- HttpResponseMessage? response = await SendRequestAsync(webhook, request.EventAlias, request.RequestObject, request.RetryCount, CancellationToken.None);
-
+ using HttpResponseMessage? response = await SendRequestAsync(webhook, request.EventAlias, request.RequestObject, request.RetryCount, CancellationToken.None);
if ((response?.IsSuccessStatusCode ?? false) || request.RetryCount >= _webhookSettings.MaximumRetries)
{
await _webhookRequestService.DeleteAsync(request);
@@ -90,33 +88,40 @@ public class WebhookFiring : IRecurringBackgroundJob
private async Task SendRequestAsync(IWebhook webhook, string eventName, string? serializedObject, int retryCount, CancellationToken cancellationToken)
{
- using var httpClient = new HttpClient();
+ using HttpClient httpClient = _httpClientFactory.CreateClient(Constants.HttpClients.WebhookFiring);
- var stringContent = new StringContent(serializedObject ?? string.Empty, Encoding.UTF8, MediaTypeNames.Application.Json);
- stringContent.Headers.TryAddWithoutValidation("Umb-Webhook-Event", eventName);
-
- foreach (KeyValuePair header in webhook.Headers)
+ using var request = new HttpRequestMessage(HttpMethod.Post, webhook.Url)
{
- stringContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
- }
+ Version = httpClient.DefaultRequestVersion,
+ VersionPolicy = httpClient.DefaultVersionPolicy,
+ };
HttpResponseMessage? response = null;
+ Exception? exception = null;
try
{
- response = await httpClient.PostAsync(webhook.Url, stringContent, cancellationToken);
+ // Add headers
+ request.Headers.Add(Constants.WebhookEvents.HeaderNames.EventName, eventName);
+ request.Headers.Add(Constants.WebhookEvents.HeaderNames.RetryCount, retryCount.ToString());
+
+ foreach (KeyValuePair header in webhook.Headers)
+ {
+ request.Headers.Add(header.Key, header.Value);
+ }
+
+ // Set content
+ request.Content = new StringContent(serializedObject ?? string.Empty, Encoding.UTF8, MediaTypeNames.Application.Json);
+
+ // Send request
+ response = await httpClient.SendAsync(request, cancellationToken);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error while sending webhook request for webhook {WebhookKey}.", webhook);
+ exception = ex;
+ _logger.LogError(ex, "Error while sending webhook request for webhook {WebhookKey}.", webhook.Key);
}
- var webhookResponseModel = new WebhookResponseModel
- {
- HttpResponseMessage = response,
- RetryCount = retryCount,
- };
-
- WebhookLog log = await _webhookLogFactory.CreateAsync(eventName, webhookResponseModel, webhook, cancellationToken);
+ WebhookLog log = await _webhookLogFactory.CreateAsync(eventName, request, response, retryCount, exception, webhook, cancellationToken);
await _webhookLogService.CreateAsync(log);
return response;
diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs b/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs
index 0e56bfa2b1..e86b8c64bd 100644
--- a/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs
+++ b/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs
@@ -1,12 +1,5 @@
-using System.Linq;
-using System.Threading;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-using Umbraco.Cms.Core.Runtime;
-using Umbraco.Cms.Core.Services;
-using Umbraco.Cms.Core.Sync;
-using Umbraco.Cms.Infrastructure.ModelsBuilder;
namespace Umbraco.Cms.Infrastructure.BackgroundJobs;
@@ -18,7 +11,7 @@ public class RecurringBackgroundJobHostedServiceRunner : IHostedService
private readonly ILogger _logger;
private readonly List _jobs;
private readonly Func _jobFactory;
- private IList _hostedServices = new List();
+ private readonly List _hostedServices = new();
public RecurringBackgroundJobHostedServiceRunner(
@@ -33,49 +26,61 @@ public class RecurringBackgroundJobHostedServiceRunner : IHostedService
public async Task StartAsync(CancellationToken cancellationToken)
{
- _logger.LogInformation("Creating recurring background jobs hosted services");
-
- // create hosted services for each background job
- _hostedServices = _jobs.Select(_jobFactory).ToList();
-
_logger.LogInformation("Starting recurring background jobs hosted services");
- foreach (IHostedService hostedService in _hostedServices)
+ foreach (IRecurringBackgroundJob job in _jobs)
{
+ var jobName = job.GetType().Name;
try
{
- _logger.LogInformation($"Starting background hosted service for {hostedService.GetType().Name}");
+
+ _logger.LogDebug("Creating background hosted service for {job}", jobName);
+ IHostedService hostedService = _jobFactory(job);
+
+ _logger.LogInformation("Starting background hosted service for {job}", jobName);
await hostedService.StartAsync(cancellationToken).ConfigureAwait(false);
+
+ _hostedServices.Add(new NamedServiceJob(jobName, hostedService));
}
catch (Exception exception)
{
- _logger.LogError(exception, $"Failed to start background hosted service for {hostedService.GetType().Name}");
+ _logger.LogError(exception, "Failed to start background hosted service for {job}", jobName);
}
}
_logger.LogInformation("Completed starting recurring background jobs hosted services");
-
-
}
public async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Stopping recurring background jobs hosted services");
- foreach (IHostedService hostedService in _hostedServices)
+ foreach (NamedServiceJob namedServiceJob in _hostedServices)
{
try
{
- _logger.LogInformation($"Stopping background hosted service for {hostedService.GetType().Name}");
- await hostedService.StopAsync(stoppingToken).ConfigureAwait(false);
+ _logger.LogInformation("Stopping background hosted service for {job}", namedServiceJob.Name);
+ await namedServiceJob.HostedService.StopAsync(stoppingToken).ConfigureAwait(false);
}
catch (Exception exception)
{
- _logger.LogError(exception, $"Failed to stop background hosted service for {hostedService.GetType().Name}");
+ _logger.LogError(exception, "Failed to stop background hosted service for {job}", namedServiceJob.Name);
}
}
_logger.LogInformation("Completed stopping recurring background jobs hosted services");
+ }
+ private class NamedServiceJob
+ {
+ public NamedServiceJob(string name, IHostedService hostedService)
+ {
+ Name = name;
+ HostedService = hostedService;
+ }
+
+ public string Name { get; }
+
+ public IHostedService HostedService { get; }
}
}
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index 73b657ff59..fd897ad46a 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -168,6 +168,9 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
// both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be
// discovered when CoreBootManager configures the converters. We will remove the basic one defined
@@ -375,7 +378,9 @@ public static partial class UmbracoBuilderExtensions
.AddNotificationHandler()
.AddNotificationHandler()
.AddNotificationHandler()
- .AddNotificationHandler();
+ .AddNotificationHandler()
+ .AddNotificationHandler()
+ .AddNotificationHandler();
// add notification handlers for redirect tracking
builder
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs
index 5139cba48f..19aa103123 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
@@ -57,6 +58,7 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddUnique();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+ builder.Services.AddUnique();
builder.AddNotificationHandler();
builder.AddNotificationHandler();
diff --git a/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs
index 2d59e0ebe3..860c6199f7 100644
--- a/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs
+++ b/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs
@@ -1,5 +1,7 @@
using Examine;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.PropertyEditors;
@@ -8,7 +10,6 @@ using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
-using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope;
namespace Umbraco.Cms.Infrastructure.Examine;
@@ -28,6 +29,7 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal
private readonly IUserService _userService;
private readonly ILocalizationService _localizationService;
private readonly IContentTypeService _contentTypeService;
+ private readonly ILogger _logger;
public ContentValueSetBuilder(
PropertyEditorCollection propertyEditors,
@@ -37,7 +39,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal
ICoreScopeProvider scopeProvider,
bool publishedValuesOnly,
ILocalizationService localizationService,
- IContentTypeService contentTypeService)
+ IContentTypeService contentTypeService,
+ ILogger logger)
: base(propertyEditors, publishedValuesOnly)
{
_urlSegmentProviders = urlSegmentProviders;
@@ -46,6 +49,30 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal
_scopeProvider = scopeProvider;
_localizationService = localizationService;
_contentTypeService = contentTypeService;
+ _logger = logger;
+ }
+
+ [Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
+ public ContentValueSetBuilder(
+ PropertyEditorCollection propertyEditors,
+ UrlSegmentProviderCollection urlSegmentProviders,
+ IUserService userService,
+ IShortStringHelper shortStringHelper,
+ ICoreScopeProvider scopeProvider,
+ bool publishedValuesOnly,
+ ILocalizationService localizationService,
+ IContentTypeService contentTypeService)
+ : this(
+ propertyEditors,
+ urlSegmentProviders,
+ userService,
+ shortStringHelper,
+ scopeProvider,
+ publishedValuesOnly,
+ localizationService,
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService>())
+ {
}
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
@@ -65,9 +92,9 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal
scopeProvider,
publishedValuesOnly,
localizationService,
- StaticServiceProvider.Instance.GetRequiredService())
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService>())
{
-
}
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
@@ -86,7 +113,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal
scopeProvider,
publishedValuesOnly,
StaticServiceProvider.Instance.GetRequiredService(),
- StaticServiceProvider.Instance.GetRequiredService())
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService>())
{
}
@@ -190,13 +218,34 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal
{
if (!property.PropertyType.VariesByCulture())
{
- AddPropertyValue(property, null, null, values, availableCultures, contentTypeDictionary);
+ try
+ {
+ AddPropertyValue(property, null, null, values, availableCultures, contentTypeDictionary);
+ }
+ catch (JsonSerializationException ex)
+ {
+ _logger.LogError(ex, "Failed to add property '{PropertyAlias}' to index for content {ContentId}", property.Alias, c.Id);
+ throw;
+ }
}
else
{
foreach (var culture in c.AvailableCultures)
{
- AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures, contentTypeDictionary);
+ try
+ {
+ AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures, contentTypeDictionary);
+ }
+ catch (JsonSerializationException ex)
+ {
+ _logger.LogError(
+ ex,
+ "Failed to add property '{PropertyAlias}' to index for content {ContentId} in culture {Culture}",
+ property.Alias,
+ c.Id,
+ culture);
+ throw;
+ }
}
}
}
diff --git a/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs b/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs
index 32dc801dd3..0cdfc10db6 100644
--- a/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs
+++ b/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs
@@ -1,6 +1,9 @@
using Examine;
using Examine.Search;
+using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.DeliveryApi;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Changes;
@@ -18,19 +21,33 @@ internal sealed class DeliveryApiContentIndexHandleContentTypeChanges : Delivery
private readonly IDeliveryApiContentIndexValueSetBuilder _deliveryApiContentIndexValueSetBuilder;
private readonly IContentService _contentService;
private readonly IBackgroundTaskQueue _backgroundTaskQueue;
+ private readonly IDeliveryApiCompositeIdHandler _deliveryApiCompositeIdHandler;
+ [Obsolete("Use the constructor that takes an IDeliveryApiCompositeIdHandler instead, scheduled for removal in v15")]
public DeliveryApiContentIndexHandleContentTypeChanges(
IList> changes,
DeliveryApiIndexingHandler deliveryApiIndexingHandler,
IDeliveryApiContentIndexValueSetBuilder deliveryApiContentIndexValueSetBuilder,
IContentService contentService,
IBackgroundTaskQueue backgroundTaskQueue)
+ : this(changes, deliveryApiIndexingHandler, deliveryApiContentIndexValueSetBuilder, contentService, backgroundTaskQueue, StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
+ public DeliveryApiContentIndexHandleContentTypeChanges(
+ IList> changes,
+ DeliveryApiIndexingHandler deliveryApiIndexingHandler,
+ IDeliveryApiContentIndexValueSetBuilder deliveryApiContentIndexValueSetBuilder,
+ IContentService contentService,
+ IBackgroundTaskQueue backgroundTaskQueue,
+ IDeliveryApiCompositeIdHandler deliveryApiCompositeIdHandler)
{
_changes = changes;
_deliveryApiIndexingHandler = deliveryApiIndexingHandler;
_deliveryApiContentIndexValueSetBuilder = deliveryApiContentIndexValueSetBuilder;
_contentService = contentService;
_backgroundTaskQueue = backgroundTaskQueue;
+ _deliveryApiCompositeIdHandler = deliveryApiCompositeIdHandler;
}
public void Execute() => _backgroundTaskQueue.QueueBackgroundWorkItem(_ =>
@@ -79,10 +96,13 @@ internal sealed class DeliveryApiContentIndexHandleContentTypeChanges : Delivery
var indexIdsByContentIds = indexIds
.Select(id =>
{
- var parts = id.Split(Constants.CharArrays.VerticalTab);
- return parts.Length == 2 && int.TryParse(parts[0], out var contentId)
- ? (ContentId: contentId, IndexId: id)
- : throw new InvalidOperationException($"Delivery API identifier should be composite of ID and culture, got: {id}");
+ DeliveryApiIndexCompositeIdModel compositeIdModel = _deliveryApiCompositeIdHandler.Decompose(id);
+ if (compositeIdModel.Id is null)
+ {
+ throw new InvalidOperationException($"Delivery API identifier should be composite of ID and culture, got: {id}");
+ }
+
+ return (ContentId: compositeIdModel.Id.Value, IndexId: compositeIdModel.Culture!);
})
.GroupBy(tuple => tuple.ContentId)
.ToDictionary(
diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexUtilites.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexUtilites.cs
deleted file mode 100644
index 2bfd3d6f80..0000000000
--- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexUtilites.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Umbraco.Cms.Core.Models;
-
-namespace Umbraco.Cms.Infrastructure.Examine;
-
-internal static class DeliveryApiContentIndexUtilites
-{
- public static string IndexId(IContent content, string culture) => $"{content.Id}|{culture}";
-}
diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs
index abf19b6bfe..e8226d994c 100644
--- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs
+++ b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs
@@ -1,9 +1,11 @@
using Examine;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DeliveryApi;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
@@ -18,8 +20,10 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte
private readonly ILogger _logger;
private readonly IDeliveryApiContentIndexFieldDefinitionBuilder _deliveryApiContentIndexFieldDefinitionBuilder;
private readonly IMemberService _memberService;
+ private readonly IDeliveryApiCompositeIdHandler _deliveryApiCompositeIdHandler;
private DeliveryApiSettings _deliveryApiSettings;
+ [Obsolete("Please use ctor that takes an IDeliveryApiCompositeIdHandler. Scheduled for removal in v15")]
public DeliveryApiContentIndexValueSetBuilder(
ContentIndexHandlerCollection contentIndexHandlerCollection,
IContentService contentService,
@@ -28,12 +32,34 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte
IDeliveryApiContentIndexFieldDefinitionBuilder deliveryApiContentIndexFieldDefinitionBuilder,
IOptionsMonitor deliveryApiSettings,
IMemberService memberService)
+ : this(
+ contentIndexHandlerCollection,
+ contentService,
+ publicAccessService,
+ logger,
+ deliveryApiContentIndexFieldDefinitionBuilder,
+ deliveryApiSettings,
+ memberService,
+ StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
+ public DeliveryApiContentIndexValueSetBuilder(
+ ContentIndexHandlerCollection contentIndexHandlerCollection,
+ IContentService contentService,
+ IPublicAccessService publicAccessService,
+ ILogger logger,
+ IDeliveryApiContentIndexFieldDefinitionBuilder deliveryApiContentIndexFieldDefinitionBuilder,
+ IOptionsMonitor deliveryApiSettings,
+ IMemberService memberService,
+ IDeliveryApiCompositeIdHandler deliveryApiCompositeIdHandler)
{
_contentIndexHandlerCollection = contentIndexHandlerCollection;
_publicAccessService = publicAccessService;
_logger = logger;
_deliveryApiContentIndexFieldDefinitionBuilder = deliveryApiContentIndexFieldDefinitionBuilder;
_memberService = memberService;
+ _deliveryApiCompositeIdHandler = deliveryApiCompositeIdHandler;
_contentService = contentService;
_deliveryApiSettings = deliveryApiSettings.CurrentValue;
deliveryApiSettings.OnChange(settings => _deliveryApiSettings = settings);
@@ -73,7 +99,7 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte
AddContentIndexHandlerFields(content, culture, fieldDefinitions, indexValues);
- yield return new ValueSet(DeliveryApiContentIndexUtilites.IndexId(content, indexCulture), IndexTypes.Content, content.ContentType.Alias, indexValues);
+ yield return new ValueSet(_deliveryApiCompositeIdHandler.IndexId(content.Id, indexCulture), IndexTypes.Content, content.ContentType.Alias, indexValues);
}
}
}
diff --git a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs
index 5394cdc275..2a89327a33 100644
--- a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs
+++ b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs
@@ -66,7 +66,17 @@ public class ExamineIndexRebuilder : IIndexRebuilder
_logger.LogInformation("Starting async background thread for rebuilding index {indexName}.", indexName);
_backgroundTaskQueue.QueueBackgroundWorkItem(
- cancellationToken => Task.Run(() => RebuildIndex(indexName, delay.Value, cancellationToken)));
+ cancellationToken =>
+ {
+ // Do not flow AsyncLocal to the child thread
+ using (ExecutionContext.SuppressFlow())
+ {
+ Task.Run(() => RebuildIndex(indexName, delay.Value, cancellationToken));
+
+ // immediately return so the queue isn't waiting.
+ return Task.CompletedTask;
+ }
+ });
}
else
{
@@ -96,12 +106,16 @@ public class ExamineIndexRebuilder : IIndexRebuilder
_backgroundTaskQueue.QueueBackgroundWorkItem(
cancellationToken =>
{
- // This is a fire/forget task spawned by the background thread queue (which means we
- // don't need to worry about ExecutionContext flowing).
- Task.Run(() => RebuildIndexes(onlyEmptyIndexes, delay.Value, cancellationToken));
+ // Do not flow AsyncLocal to the child thread
+ using (ExecutionContext.SuppressFlow())
+ {
+ // This is a fire/forget task spawned by the background thread queue (which means we
+ // don't need to worry about ExecutionContext flowing).
+ Task.Run(() => RebuildIndexes(onlyEmptyIndexes, delay.Value, cancellationToken));
- // immediately return so the queue isn't waiting.
- return Task.CompletedTask;
+ // immediately return so the queue isn't waiting.
+ return Task.CompletedTask;
+ }
});
}
else
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
index d89fe9c0b4..130eb82c87 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
@@ -104,6 +104,8 @@ public class UmbracoPlan : MigrationPlan
To("{23BA95A4-FCCE-49B0-8AA1-45312B103A9B}");
To("{7DDCE198-9CA4-430C-8BBC-A66D80CA209F}");
To("{F74CDA0C-7AAA-48C8-94C6-C6EC3C06F599}");
+ To("{21C42760-5109-4C03-AB4F-7EA53577D1F5}");
+ To("{6158F3A3-4902-4201-835E-1ED7F810B2D8}");
// To 14.0.0
To("{419827A0-4FCE-464B-A8F3-247C6092AF55}");
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs
new file mode 100644
index 0000000000..2ef666867b
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs
@@ -0,0 +1,25 @@
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_13_0_0;
+
+public class AddExceptionOccured : MigrationBase
+{
+ public AddExceptionOccured(IMigrationContext context) : base(context)
+ {
+ }
+
+ protected override void Migrate()
+ {
+ if (ColumnExists(Constants.DatabaseSchema.Tables.WebhookLog, "exceptionOccured") == false)
+ {
+ // Use a custom SQL query to prevent selecting explicit columns (sortOrder doesn't exist yet)
+ List webhookLogDtos = Database.Fetch($"SELECT * FROM {Constants.DatabaseSchema.Tables.WebhookLog}");
+
+ Delete.Table(Constants.DatabaseSchema.Tables.WebhookLog).Do();
+ Create.Table().Do();
+
+ Database.InsertBatch(webhookLogDtos);
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs
new file mode 100644
index 0000000000..1e24cb9497
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs
@@ -0,0 +1,89 @@
+using System.Linq.Expressions;
+using System.Text;
+using NPoco;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create.Column;
+using Umbraco.Cms.Infrastructure.Persistence;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_13_0_0;
+
+public class ChangeWebhookUrlColumnsToNvarcharMax : MigrationBase
+{
+ public ChangeWebhookUrlColumnsToNvarcharMax(IMigrationContext context) : base(context)
+ {
+ }
+
+ protected override void Migrate()
+ {
+ // We don't need to run this migration for SQLite, since ntext is not a thing there, text is just text.
+ if (DatabaseType == DatabaseType.SQLite)
+ {
+ return;
+ }
+
+ MigrateNtextColumn("url", Constants.DatabaseSchema.Tables.Webhook, x => x.Url);
+ MigrateNtextColumn("url", Constants.DatabaseSchema.Tables.WebhookLog, x => x.Url);
+ }
+
+ private void MigrateNtextColumn(string columnName, string tableName, Expression> fieldSelector, bool nullable = true)
+ {
+ var columnType = ColumnType(tableName, columnName);
+ if (columnType is null || columnType.Equals("nvarchar", StringComparison.InvariantCultureIgnoreCase) is false)
+ {
+ return;
+ }
+
+ var oldColumnName = $"Old{columnName}";
+
+ // Rename the column so we can create the new one and copy over the data.
+ Rename
+ .Column(columnName)
+ .OnTable(tableName)
+ .To(oldColumnName)
+ .Do();
+
+ // Create new column with the correct type
+ // This is pretty ugly, but we have to do ti this way because the CacheInstruction.Instruction column doesn't support nullable.
+ // So we have to populate with some temporary placeholder value before we copy over the actual data.
+ ICreateColumnOptionBuilder builder = Create
+ .Column(columnName)
+ .OnTable(tableName)
+ .AsCustom("nvarchar(max)");
+
+ if (nullable is false)
+ {
+ builder
+ .NotNullable()
+ .WithDefaultValue("Placeholder");
+ }
+ else
+ {
+ builder.Nullable();
+ }
+
+ builder.Do();
+
+ // Copy over data NPOCO doesn't support this for some reason, so we'll have to do it like so
+ // While we're add it we'll also set all the old values to be NULL since it's recommended here:
+ // https://learn.microsoft.com/en-us/sql/t-sql/data-types/ntext-text-and-image-transact-sql?view=sql-server-ver16#remarks
+ StringBuilder queryBuilder = new StringBuilder()
+ .AppendLine($"UPDATE {tableName}")
+ .AppendLine("SET")
+ .Append($"\t{SqlSyntax.GetFieldNameForUpdate(fieldSelector)} = {SqlSyntax.GetQuotedTableName(tableName)}.{SqlSyntax.GetQuotedColumnName(oldColumnName)}");
+
+ if (nullable)
+ {
+ queryBuilder.AppendLine($"\n,\t{SqlSyntax.GetQuotedColumnName(oldColumnName)} = NULL");
+ }
+
+ Sql copyDataQuery = Database.SqlContext.Sql(queryBuilder.ToString());
+ Database.Execute(copyDataQuery);
+
+ // Delete old column
+ Delete
+ .Column(oldColumnName)
+ .FromTable(tableName)
+ .Do();
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs
index abcf160b03..2fb0d13555 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs
@@ -19,6 +19,7 @@ internal class WebhookDto
public Guid Key { get; set; }
[Column(Name = "url")]
+ [SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
[NullSetting(NullSetting = NullSettings.NotNull)]
public string Url { get; set; } = string.Empty;
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs
index f98226248e..8e409cd0b3 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs
@@ -30,6 +30,7 @@ internal class WebhookLogDto
public DateTime Date { get; set; }
[Column(Name = "url")]
+ [SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
[NullSetting(NullSetting = NullSettings.NotNull)]
public string Url { get; set; } = string.Empty;
@@ -60,4 +61,7 @@ internal class WebhookLogDto
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
[NullSetting(NullSetting = NullSettings.NotNull)]
public string ResponseBody { get; set; } = string.Empty;
+
+ [Column(Name = "exceptionOccured")]
+ public bool ExceptionOccured { get; set; }
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs
index ff1378ed2d..f060525624 100644
--- a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs
@@ -21,6 +21,7 @@ internal static class WebhookLogFactory
RequestHeaders = log.RequestHeaders,
ResponseHeaders = log.ResponseHeaders,
WebhookKey = log.WebhookKey,
+ ExceptionOccured = log.ExceptionOccured,
};
public static WebhookLog DtoToEntity(WebhookLogDto dto) =>
@@ -38,5 +39,6 @@ internal static class WebhookLogFactory
RequestHeaders = dto.RequestHeaders,
ResponseHeaders = dto.ResponseHeaders,
WebhookKey = dto.WebhookKey,
+ ExceptionOccured = dto.ExceptionOccured,
};
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
index 3d81674e0e..05317b39e1 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
@@ -343,9 +343,15 @@ public class CreatedPackageSchemaRepository : ICreatedPackagesRepository
_hostingEnvironment.MapPathContentRoot(Path.Combine(
_createdPackagesFolderPath,
definition.Name.Replace(' ', '_')));
- Directory.CreateDirectory(directoryName);
- var finalPackagePath = Path.Combine(directoryName, fileName);
+ var expectedRoot = Path.GetFullPath(_hostingEnvironment.MapPathContentRoot(_createdPackagesFolderPath));
+ var finalPackagePath = Path.GetFullPath(Path.Combine(directoryName, fileName));
+ if (finalPackagePath.StartsWith(expectedRoot) == false)
+ {
+ throw new IOException("Invalid path due to the package name");
+ }
+
+ Directory.CreateDirectory(directoryName);
// Clean existing files
foreach (var packagePath in new[] { definition.PackagePath, finalPackagePath })
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs
index d8abbf35e8..99296e788d 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs
@@ -35,16 +35,22 @@ public abstract class BlockListPropertyEditorBase : DataEditor
public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory;
-
#region Value Editor
+ ///
+ /// Instantiates a new for use with the block list editor property value editor.
+ ///
+ /// A new instance of .
+ protected virtual BlockEditorDataConverter CreateBlockEditorDataConverter() => new BlockListEditorDataConverter();
+
protected override IDataValueEditor CreateValueEditor() =>
- DataValueEditorFactory.Create(Attribute!);
+ DataValueEditorFactory.Create(Attribute!, CreateBlockEditorDataConverter());
internal class BlockListEditorPropertyValueEditor : BlockEditorPropertyValueEditor
{
public BlockListEditorPropertyValueEditor(
DataEditorAttribute attribute,
+ BlockEditorDataConverter blockEditorDataConverter,
PropertyEditorCollection propertyEditors,
IDataTypeService dataTypeService,
IContentTypeService contentTypeService,
@@ -56,7 +62,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor
IPropertyValidationService propertyValidationService) :
base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
{
- BlockEditorValues = new BlockEditorValues(new BlockListEditorDataConverter(jsonSerializer), contentTypeService, logger);
+ BlockEditorValues = new BlockEditorValues(blockEditorDataConverter, contentTypeService, logger);
Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService));
Validators.Add(new MinMaxValidator(BlockEditorValues, textService));
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs
index 6eb8996686..146e4b8949 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
///
[DataEditor(
Constants.PropertyEditors.Aliases.MediaPicker3,
- EditorType.PropertyValue | EditorType.MacroParameter,
+ EditorType.PropertyValue,
"Media Picker",
"mediapicker3",
ValueType = ValueTypes.Json,
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs
new file mode 100644
index 0000000000..3af7b4beba
--- /dev/null
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs
@@ -0,0 +1,23 @@
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
+using Umbraco.Cms.Core.Models.Blocks;
+using Umbraco.Cms.Core.Models.PublishedContent;
+
+namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
+
+public abstract class BlockEditorPropertyValueConstructorCacheBase
+ where T : IBlockReference
+{
+ private readonly
+ ConcurrentDictionary<(Guid, Guid?), Func>
+ _constructorCache = new();
+
+ public bool TryGetValue((Guid ContentTypeKey, Guid? SettingsTypeKey) key, [MaybeNullWhen(false)] out Func value)
+ => _constructorCache.TryGetValue(key, out value);
+
+ public void SetValue((Guid ContentTypeKey, Guid? SettingsTypeKey) key, Func value)
+ => _constructorCache[key] = value;
+
+ public void Clear()
+ => _constructorCache.Clear();
+}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConstructorCache.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConstructorCache.cs
new file mode 100644
index 0000000000..6154381013
--- /dev/null
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConstructorCache.cs
@@ -0,0 +1,7 @@
+using Umbraco.Cms.Core.Models.Blocks;
+
+namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
+
+public class BlockGridPropertyValueConstructorCache : BlockEditorPropertyValueConstructorCacheBase
+{
+}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConverter.cs
index d0e1e2ba19..6ca6c30b3c 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConverter.cs
@@ -21,8 +21,9 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
private readonly BlockEditorConverter _blockConverter;
private readonly IJsonSerializer _jsonSerializer;
private readonly IApiElementBuilder _apiElementBuilder;
+ private readonly BlockGridPropertyValueConstructorCache _constructorCache;
- [Obsolete("Please use non-obsolete cconstrutor. This will be removed in Umbraco 14.")]
+ [Obsolete("Please use non-obsolete construtor. This will be removed in Umbraco 14.")]
public BlockGridPropertyValueConverter(
IProfilingLogger proflog,
BlockEditorConverter blockConverter,
@@ -32,16 +33,28 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
}
+ [Obsolete("Please use non-obsolete construtor. This will be removed in Umbraco 15.")]
public BlockGridPropertyValueConverter(
IProfilingLogger proflog,
BlockEditorConverter blockConverter,
IJsonSerializer jsonSerializer,
IApiElementBuilder apiElementBuilder)
+ : this(proflog, blockConverter, jsonSerializer, apiElementBuilder, StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
+ public BlockGridPropertyValueConverter(
+ IProfilingLogger proflog,
+ BlockEditorConverter blockConverter,
+ IJsonSerializer jsonSerializer,
+ IApiElementBuilder apiElementBuilder,
+ BlockGridPropertyValueConstructorCache constructorCache)
{
_proflog = proflog;
_blockConverter = blockConverter;
_jsonSerializer = jsonSerializer;
_apiElementBuilder = apiElementBuilder;
+ _constructorCache = constructorCache;
}
///
@@ -129,7 +142,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
return null;
}
- var creator = new BlockGridPropertyValueCreator(_blockConverter, _jsonSerializer);
+ var creator = new BlockGridPropertyValueCreator(_blockConverter, _jsonSerializer, _constructorCache);
return creator.CreateBlockModel(referenceCacheLevel, intermediateBlockModelValue, preview, configuration.Blocks, configuration.GridColumns);
}
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueCreator.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueCreator.cs
index fbbedf3a44..6b1252f751 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueCreator.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueCreator.cs
@@ -7,10 +7,14 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
internal class BlockGridPropertyValueCreator : BlockPropertyValueCreatorBase
{
private readonly IJsonSerializer _jsonSerializer;
+ private readonly BlockGridPropertyValueConstructorCache _constructorCache;
- public BlockGridPropertyValueCreator(BlockEditorConverter blockEditorConverter, IJsonSerializer jsonSerializer)
+ public BlockGridPropertyValueCreator(BlockEditorConverter blockEditorConverter, IJsonSerializer jsonSerializer, BlockGridPropertyValueConstructorCache constructorCache)
: base(blockEditorConverter)
- => _jsonSerializer = jsonSerializer;
+ {
+ _jsonSerializer = jsonSerializer;
+ _constructorCache = constructorCache;
+ }
public BlockGridModel CreateBlockModel(PropertyCacheLevel referenceCacheLevel, string intermediateBlockModelValue, bool preview, BlockGridConfiguration.BlockGridBlockConfiguration[] blockConfigurations, int? gridColumns)
{
@@ -55,11 +59,12 @@ internal class BlockGridPropertyValueCreator : BlockPropertyValueCreatorBase CreateBlockEditorDataConverter() => new BlockGridEditorDataConverter(_jsonSerializer);
- protected override BlockItemActivator CreateBlockItemActivator() => new BlockGridItemActivator(BlockEditorConverter);
+ protected override BlockItemActivator CreateBlockItemActivator() => new BlockGridItemActivator(BlockEditorConverter, _constructorCache);
private class BlockGridItemActivator : BlockItemActivator
{
- public BlockGridItemActivator(BlockEditorConverter blockConverter) : base(blockConverter)
+ public BlockGridItemActivator(BlockEditorConverter blockConverter, BlockGridPropertyValueConstructorCache constructorCache)
+ : base(blockConverter, constructorCache)
{
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConstructorCache.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConstructorCache.cs
new file mode 100644
index 0000000000..b07185a954
--- /dev/null
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConstructorCache.cs
@@ -0,0 +1,7 @@
+using Umbraco.Cms.Core.Models.Blocks;
+
+namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
+
+public class BlockListPropertyValueConstructorCache : BlockEditorPropertyValueConstructorCacheBase
+{
+}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
index 22177c3a63..add8f3576b 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
@@ -25,6 +25,7 @@ public class BlockListPropertyValueConverter : PropertyValueConverterBase, IDeli
private readonly BlockEditorConverter _blockConverter;
private readonly IApiElementBuilder _apiElementBuilder;
private readonly IJsonSerializer _jsonSerializer;
+ private readonly BlockListPropertyValueConstructorCache _constructorCache;
[Obsolete("Use the constructor that takes all parameters, scheduled for removal in V14")]
public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter)
@@ -40,17 +41,24 @@ public class BlockListPropertyValueConverter : PropertyValueConverterBase, IDeli
[Obsolete("Use the constructor that takes all parameters, scheduled for removal in V15")]
public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IContentTypeService contentTypeService, IApiElementBuilder apiElementBuilder)
- : this(proflog, blockConverter, contentTypeService, apiElementBuilder, StaticServiceProvider.Instance.GetRequiredService())
+ : this(proflog, blockConverter, contentTypeService, apiElementBuilder, StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService())
{
}
- public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IContentTypeService contentTypeService, IApiElementBuilder apiElementBuilder, IJsonSerializer jsonSerializer)
+ [Obsolete("Use the constructor that takes all parameters, scheduled for removal in V15")]
+ public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IContentTypeService contentTypeService, IApiElementBuilder apiElementBuilder, BlockListPropertyValueConstructorCache constructorCache)
+ : this(proflog, blockConverter, contentTypeService, apiElementBuilder, StaticServiceProvider.Instance.GetRequiredService(), constructorCache)
+ {
+ }
+
+ public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IContentTypeService contentTypeService, IApiElementBuilder apiElementBuilder, IJsonSerializer jsonSerializer, BlockListPropertyValueConstructorCache constructorCache)
{
_proflog = proflog;
_blockConverter = blockConverter;
_contentTypeService = contentTypeService;
_apiElementBuilder = apiElementBuilder;
_jsonSerializer = jsonSerializer;
+ _constructorCache = constructorCache;
}
///
@@ -162,7 +170,7 @@ public class BlockListPropertyValueConverter : PropertyValueConverterBase, IDeli
return null;
}
- var creator = new BlockListPropertyValueCreator(_blockConverter, _jsonSerializer);
+ var creator = new BlockListPropertyValueCreator(_blockConverter, _jsonSerializer, _constructorCache);
return creator.CreateBlockModel(referenceCacheLevel, intermediateBlockModelValue, preview, configuration.Blocks);
}
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueCreator.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueCreator.cs
index 4fbd14fd23..853dc1027f 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueCreator.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueCreator.cs
@@ -6,10 +6,17 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
internal class BlockListPropertyValueCreator : BlockPropertyValueCreatorBase
{
private readonly IJsonSerializer _jsonSerializer;
+ private readonly BlockListPropertyValueConstructorCache _constructorCache;
- public BlockListPropertyValueCreator(BlockEditorConverter blockEditorConverter, IJsonSerializer jsonSerializer)
+ public BlockListPropertyValueCreator(
+ BlockEditorConverter blockEditorConverter,
+ IJsonSerializer jsonSerializer,
+ BlockListPropertyValueConstructorCache constructorCache)
: base(blockEditorConverter)
- => _jsonSerializer = jsonSerializer;
+ {
+ _jsonSerializer = jsonSerializer;
+ _constructorCache = constructorCache;
+ }
public BlockListModel CreateBlockModel(PropertyCacheLevel referenceCacheLevel, string intermediateBlockModelValue, bool preview, BlockListConfiguration.BlockConfiguration[] blockConfigurations)
{
@@ -24,11 +31,12 @@ internal class BlockListPropertyValueCreator : BlockPropertyValueCreatorBase CreateBlockEditorDataConverter() => new BlockListEditorDataConverter(_jsonSerializer);
- protected override BlockItemActivator CreateBlockItemActivator() => new BlockListItemActivator(BlockEditorConverter);
+ protected override BlockItemActivator CreateBlockItemActivator() => new BlockListItemActivator(BlockEditorConverter, _constructorCache);
private class BlockListItemActivator : BlockItemActivator
{
- public BlockListItemActivator(BlockEditorConverter blockConverter) : base(blockConverter)
+ public BlockListItemActivator(BlockEditorConverter blockConverter, BlockListPropertyValueConstructorCache constructorCache)
+ : base(blockConverter, constructorCache)
{
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockPropertyValueCreatorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockPropertyValueCreatorBase.cs
index 8c7290a32c..45d4cdff2b 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockPropertyValueCreatorBase.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockPropertyValueCreatorBase.cs
@@ -216,17 +216,19 @@ internal abstract class BlockPropertyValueCreatorBase
+ where T : IBlockReference
{
protected abstract Type GenericItemType { get; }
private readonly BlockEditorConverter _blockConverter;
- private readonly
- Dictionary<(Guid, Guid?), Func>
- _constructorCache = new();
+ private readonly BlockEditorPropertyValueConstructorCacheBase _constructorCache;
- public BlockItemActivator(BlockEditorConverter blockConverter)
- => _blockConverter = blockConverter;
+ public BlockItemActivator(BlockEditorConverter blockConverter, BlockEditorPropertyValueConstructorCacheBase constructorCache)
+ {
+ _blockConverter = blockConverter;
+ _constructorCache = constructorCache;
+ }
public T CreateInstance(Guid contentTypeKey, Guid? settingsTypeKey, Udi contentUdi, IPublishedElement contentData, Udi? settingsUdi, IPublishedElement? settingsData)
{
@@ -234,8 +236,8 @@ internal abstract class BlockPropertyValueCreatorBase? constructor))
{
- constructor = _constructorCache[(contentTypeKey, settingsTypeKey)] =
- EmitConstructor(contentTypeKey, settingsTypeKey);
+ constructor = EmitConstructor(contentTypeKey, settingsTypeKey);
+ _constructorCache.SetValue((contentTypeKey, settingsTypeKey), constructor);
}
return constructor(contentUdi, contentData, settingsUdi, settingsData);
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ConstructorCacheClearNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ConstructorCacheClearNotificationHandler.cs
new file mode 100644
index 0000000000..2abbfe73e0
--- /dev/null
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ConstructorCacheClearNotificationHandler.cs
@@ -0,0 +1,38 @@
+using Umbraco.Cms.Core.Events;
+using Umbraco.Cms.Core.Notifications;
+
+namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
+
+public class ConstructorCacheClearNotificationHandler :
+ INotificationHandler,
+ INotificationHandler
+{
+ private readonly BlockListPropertyValueConstructorCache _blockListPropertyValueConstructorCache;
+ private readonly BlockGridPropertyValueConstructorCache _blockGridPropertyValueConstructorCache;
+ private readonly RichTextBlockPropertyValueConstructorCache _richTextBlockPropertyValueConstructorCache;
+
+ public ConstructorCacheClearNotificationHandler(
+ BlockListPropertyValueConstructorCache blockListPropertyValueConstructorCache,
+ BlockGridPropertyValueConstructorCache blockGridPropertyValueConstructorCache,
+ RichTextBlockPropertyValueConstructorCache richTextBlockPropertyValueConstructorCache)
+ {
+ _blockListPropertyValueConstructorCache = blockListPropertyValueConstructorCache;
+ _blockGridPropertyValueConstructorCache = blockGridPropertyValueConstructorCache;
+ _richTextBlockPropertyValueConstructorCache = richTextBlockPropertyValueConstructorCache;
+ }
+
+ public void Handle(ContentTypeCacheRefresherNotification notification)
+ => ClearCaches();
+
+ public void Handle(DataTypeCacheRefresherNotification notification)
+ => ClearCaches();
+
+ private void ClearCaches()
+ {
+ // must clear the block item constructor caches whenever content types and data types change,
+ // otherwise InMemoryAuto generated models will not work.
+ _blockListPropertyValueConstructorCache.Clear();
+ _blockGridPropertyValueConstructorCache.Clear();
+ _richTextBlockPropertyValueConstructorCache.Clear();
+ }
+}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs
index dfc64bdad5..61cc0e1c7d 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs
@@ -185,6 +185,7 @@ public class MultiUrlPickerValueConverter : PropertyValueConverterBase, IDeliver
? null
: ApiLink.Content(
item.Name.IfNullOrWhiteSpace(_apiContentNameProvider.GetName(content)),
+ item.QueryString,
item.Target,
content.Key,
content.ContentType.Alias,
@@ -195,12 +196,13 @@ public class MultiUrlPickerValueConverter : PropertyValueConverterBase, IDeliver
? null
: ApiLink.Media(
item.Name.IfNullOrWhiteSpace(_apiContentNameProvider.GetName(media)),
- _apiMediaUrlProvider.GetUrl(media),
+ $"{_apiMediaUrlProvider.GetUrl(media)}{item.QueryString}",
+ item.QueryString,
item.Target,
media.Key,
media.ContentType.Alias);
default:
- return ApiLink.External(item.Name, $"{item.Url}{item.QueryString}", item.Target);
+ return ApiLink.External(item.Name, $"{item.Url}{item.QueryString}", item.QueryString, item.Target);
}
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueConstructorCache.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueConstructorCache.cs
new file mode 100644
index 0000000000..f6458df23f
--- /dev/null
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueConstructorCache.cs
@@ -0,0 +1,7 @@
+using Umbraco.Cms.Core.Models.Blocks;
+
+namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
+
+public class RichTextBlockPropertyValueConstructorCache : BlockEditorPropertyValueConstructorCacheBase
+{
+}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueCreator.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueCreator.cs
index 43dca1d436..2c9f36192d 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueCreator.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueCreator.cs
@@ -7,10 +7,11 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
internal class RichTextBlockPropertyValueCreator : BlockPropertyValueCreatorBase
{
- public RichTextBlockPropertyValueCreator(BlockEditorConverter blockEditorConverter)
+ private readonly RichTextBlockPropertyValueConstructorCache _constructorCache;
+
+ public RichTextBlockPropertyValueCreator(BlockEditorConverter blockEditorConverter, RichTextBlockPropertyValueConstructorCache constructorCache)
: base(blockEditorConverter)
- {
- }
+ => _constructorCache = constructorCache;
public RichTextBlockModel CreateBlockModel(PropertyCacheLevel referenceCacheLevel, RichTextBlockValue blockValue, bool preview, RichTextConfiguration.RichTextBlockConfiguration[] blockConfigurations)
{
@@ -25,11 +26,12 @@ internal class RichTextBlockPropertyValueCreator : BlockPropertyValueCreatorBase
protected override BlockEditorDataConverter CreateBlockEditorDataConverter() => new RichTextEditorBlockDataConverter();
- protected override BlockItemActivator CreateBlockItemActivator() => new RichTextBlockItemActivator(BlockEditorConverter);
+ protected override BlockItemActivator CreateBlockItemActivator() => new RichTextBlockItemActivator(BlockEditorConverter, _constructorCache);
private class RichTextBlockItemActivator : BlockItemActivator
{
- public RichTextBlockItemActivator(BlockEditorConverter blockConverter) : base(blockConverter)
+ public RichTextBlockItemActivator(BlockEditorConverter blockConverter, RichTextBlockPropertyValueConstructorCache constructorCache)
+ : base(blockConverter, constructorCache)
{
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs
index 1d9319db68..5fe36f0517 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs
@@ -46,6 +46,7 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel
private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger _logger;
private readonly IApiElementBuilder _apiElementBuilder;
+ private readonly RichTextBlockPropertyValueConstructorCache _constructorCache;
private DeliveryApiSettings _deliveryApiSettings;
[Obsolete("Please use the constructor that takes all arguments. Will be removed in V14.")]
@@ -79,6 +80,7 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel
StaticServiceProvider.Instance.GetRequiredService(),
StaticServiceProvider.Instance.GetRequiredService(),
StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService(),
StaticServiceProvider.Instance.GetRequiredService>(),
deliveryApiSettingsMonitor
)
@@ -89,7 +91,7 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel
HtmlLocalLinkParser linkParser, HtmlUrlParser urlParser, HtmlImageSourceParser imageSourceParser,
IApiRichTextElementParser apiRichTextElementParser, IApiRichTextMarkupParser apiRichTextMarkupParser,
IPartialViewBlockEngine partialViewBlockEngine, BlockEditorConverter blockEditorConverter, IJsonSerializer jsonSerializer,
- IApiElementBuilder apiElementBuilder, ILogger logger,
+ IApiElementBuilder apiElementBuilder, RichTextBlockPropertyValueConstructorCache constructorCache, ILogger logger,
IOptionsMonitor deliveryApiSettingsMonitor)
{
_umbracoContextAccessor = umbracoContextAccessor;
@@ -103,6 +105,7 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel
_blockEditorConverter = blockEditorConverter;
_jsonSerializer = jsonSerializer;
_apiElementBuilder = apiElementBuilder;
+ _constructorCache = constructorCache;
_logger = logger;
_deliveryApiSettings = deliveryApiSettingsMonitor.CurrentValue;
deliveryApiSettingsMonitor.OnChange(settings => _deliveryApiSettings = settings);
@@ -267,7 +270,7 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel
return null;
}
- var creator = new RichTextBlockPropertyValueCreator(_blockEditorConverter);
+ var creator = new RichTextBlockPropertyValueCreator(_blockEditorConverter, _constructorCache);
return creator.CreateBlockModel(referenceCacheLevel, blocks, preview, configuration.Blocks);
}
diff --git a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs
index 3c7e4eabee..10b5974c52 100644
--- a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs
+++ b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs
@@ -131,7 +131,9 @@ public class MemberPasswordHasher : UmbracoPasswordHasher
switch (algorithmName)
{
case "AES":
- algorithm = new AesCryptoServiceProvider { Key = StringToByteArray(decryptionKey), IV = new byte[16] };
+ algorithm = Aes.Create();
+ algorithm.Key = StringToByteArray(decryptionKey);
+ algorithm.IV = new byte[16];
break;
default:
throw new NotSupportedException($"The algorithm ({algorithmName}) is not supported");
diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
index 33b7bd36f4..b6ceb3d2e8 100644
--- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
+++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
@@ -12,35 +12,31 @@
-
-
+
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
diff --git a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs
index e236ef4907..3bb9872901 100644
--- a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs
+++ b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs
@@ -59,6 +59,8 @@ public class BackOfficeWebAssets
BundlingOptions.NotOptimizedAndComposite,
FormatPaths(
"assets/css/umbraco.min.css",
+ "lib/umbraco-ui/uui-css/dist/custom-properties.css",
+ "lib/umbraco-ui/uui-css/dist/uui-text.css",
"lib/bootstrap-social/bootstrap-social.css",
"lib/font-awesome/css/font-awesome.min.css"));
diff --git a/src/Umbraco.Infrastructure/WebAssets/JsInitialize.js b/src/Umbraco.Infrastructure/WebAssets/JsInitialize.js
index 14dab707c1..28ec6ad24f 100644
--- a/src/Umbraco.Infrastructure/WebAssets/JsInitialize.js
+++ b/src/Umbraco.Infrastructure/WebAssets/JsInitialize.js
@@ -35,6 +35,10 @@
'lib/umbraco/NamespaceManager.js',
'lib/umbraco/LegacySpeechBubble.js',
+ 'lib/umbraco-ui/uui/dist/uui.min.js',
+
+ 'login/login.iife.js',
+
'js/utilities.min.js',
'js/app.min.js',
diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
index 6ab806c8df..175cf766c5 100644
--- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
+++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
@@ -58,11 +58,13 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
private long _domainGen;
private SnapDictionary _domainStore = null!;
private IAppCache? _elementsCache;
- private bool _isReadSet;
+ private bool _isReadSet;
private bool _isReady;
private object? _isReadyLock;
+ private bool _mainDomRegistered;
+
private BPlusTree? _localContentDb;
private bool _localContentDbExists;
private BPlusTree? _localMediaDb;
@@ -511,9 +513,20 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
// it will not be able to close the stores until we are done populating (if the store is empty)
lock (_storesLock)
{
+ SyncBootState bootState = _syncBootStateAccessor.GetSyncBootState();
+
if (!_options.IgnoreLocalDb)
{
- _mainDom.Register(MainDomRegister, MainDomRelease);
+ if (!_mainDomRegistered)
+ {
+ _mainDom.Register(MainDomRegister, MainDomRelease);
+ }
+ else
+ {
+ // MainDom is already registered, so we must be retrying to load cache data
+ // We can't trust the localdb state, so always perform a cold boot
+ bootState = SyncBootState.ColdBoot;
+ }
// stores are created with a db so they can write to it, but they do not read from it,
// stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to
@@ -562,8 +575,6 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
var okContent = false;
var okMedia = false;
- SyncBootState bootState = _syncBootStateAccessor.GetSyncBootState();
-
try
{
if (bootState != SyncBootState.ColdBoot && _localContentDbExists)
@@ -635,6 +646,8 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
_localContentDb = BTree.GetTree(localContentDbPath, _localContentDbExists, _config, _contentDataSerializer);
_localMediaDb = BTree.GetTree(localMediaDbPath, _localMediaDbExists, _config, _contentDataSerializer);
+ _mainDomRegistered = true;
+
_logger.LogInformation(
"Registered with MainDom, localContentDbExists? {LocalContentDbExists}, localMediaDbExists? {LocalMediaDbExists}",
_localContentDbExists,
@@ -720,21 +733,10 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
_localContentDb?.Clear();
// IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder
- try
- {
- IEnumerable kits = _publishedContentService.GetAllContentSources();
- return onStartup
- ? _contentStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true)
- : _contentStore.SetAllLocked(kits, _config.KitBatchSize, true);
- }
- catch (ThreadAbortException tae)
- {
- // Caught a ThreadAbortException, most likely from a database timeout.
- // If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment).
- _logger.LogWarning(tae, tae.Message);
- }
-
- return false;
+ IEnumerable kits = _publishedContentService.GetAllContentSources();
+ return onStartup
+ ? _contentStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true)
+ : _contentStore.SetAllLocked(kits, _config.KitBatchSize, true);
}
}
@@ -783,21 +785,10 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
}
// IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder
- try
- {
- IEnumerable kits = _publishedContentService.GetAllMediaSources();
- return onStartup
- ? _mediaStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true)
- : _mediaStore.SetAllLocked(kits, _config.KitBatchSize, true);
- }
- catch (ThreadAbortException tae)
- {
- // Caught a ThreadAbortException, most likely from a database timeout.
- // If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment).
- _logger.LogWarning(tae, tae.Message);
- }
-
- return false;
+ IEnumerable kits = _publishedContentService.GetAllMediaSources();
+ return onStartup
+ ? _mediaStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true)
+ : _mediaStore.SetAllLocked(kits, _config.KitBatchSize, true);
}
}
diff --git a/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs b/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs
index 1820f2b5e0..b9980308b9 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs
@@ -1,9 +1,12 @@
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Controllers;
+[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)]
public class AnalyticsController : UmbracoAuthorizedJsonController
{
private readonly IMetricsConsentService _metricsConsentService;
diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
index 71eb240fd5..45a1746b7e 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
@@ -470,6 +470,9 @@ public class AuthenticationController : UmbracoApiControllerBase
}
BackOfficeIdentityUser? identityUser = await _userManager.FindByEmailAsync(model.Email);
+
+ await Task.Delay(RandomNumberGenerator.GetInt32(400, 2500)); // To randomize response time preventing user enumeration
+
if (identityUser != null)
{
IUser? user = _userService.GetByEmail(model.Email);
@@ -490,14 +493,20 @@ public class AuthenticationController : UmbracoApiControllerBase
var mailMessage = new EmailMessage(from, user.Email, subject, message, true);
- await _emailSender.SendAsync(mailMessage, Constants.Web.EmailTypes.PasswordReset, true);
+ try
+ {
+ await _emailSender.SendAsync(mailMessage, Constants.Web.EmailTypes.PasswordReset, true);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error sending email, please check your SMTP configuration: {ErrorMessage}", ex.Message);
+ return Ok();
+ }
_userManager.NotifyForgotPasswordRequested(User, user.Id.ToString());
}
}
- await Task.Delay(RandomNumberGenerator.GetInt32(400, 2500));
-
return Ok();
}
@@ -592,7 +601,7 @@ public class AuthenticationController : UmbracoApiControllerBase
await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.IsPersistent, model.RememberClient);
if (result.Succeeded)
{
- return GetUserDetail(_userService.GetByUsername(user.UserName));
+ return Ok(GetUserDetail(_userService.GetByUsername(user.UserName)));
}
if (result.IsLockedOut)
diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
index 6a9573f5a3..d3b00d064d 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
@@ -11,13 +11,13 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Grid;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Manifest;
-using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
@@ -137,7 +137,6 @@ public class BackOfficeController : UmbracoController
return await RenderDefaultOrProcessExternalLoginAsync(
result,
- () => defaultView,
() => defaultView);
}
@@ -164,7 +163,6 @@ public class BackOfficeController : UmbracoController
return await RenderDefaultOrProcessExternalLoginAsync(
result,
- () => View(viewPath),
() => View(viewPath));
}
@@ -458,11 +456,9 @@ public class BackOfficeController : UmbracoController
///
private async Task RenderDefaultOrProcessExternalLoginAsync(
AuthenticateResult authenticateResult,
- Func defaultResponse,
- Func externalSignInResponse)
+ Func defaultResponse)
{
ArgumentNullException.ThrowIfNull(defaultResponse);
- ArgumentNullException.ThrowIfNull(externalSignInResponse);
ViewData.SetUmbracoPath(_globalSettings.GetUmbracoMvcArea(_hostingEnvironment));
@@ -479,23 +475,35 @@ public class BackOfficeController : UmbracoController
// First check if there's external login info, if there's not proceed as normal
ExternalLoginInfo? loginInfo = await _signInManager.GetExternalLoginInfoAsync();
- if (loginInfo == null || loginInfo.Principal == null)
+ if (loginInfo != null)
{
- // if the user is not logged in, check if there's any auto login redirects specified
- if (!authenticateResult.Succeeded)
- {
- var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider();
- if (!oauthRedirectAuthProvider.IsNullOrWhiteSpace())
- {
- return ExternalLogin(oauthRedirectAuthProvider!);
- }
- }
+ // we're just logging in with an external source, not linking accounts
+ return await ExternalSignInAsync(loginInfo, defaultResponse);
+ }
+ // If we are authenticated then we can just render the default view
+ if (authenticateResult.Succeeded)
+ {
return defaultResponse();
}
- // we're just logging in with an external source, not linking accounts
- return await ExternalSignInAsync(loginInfo, externalSignInResponse);
+ // If the user is not logged in, check if there's any auto login redirects specified
+ var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider();
+
+ // If there's no auto login provider specified, then we'll render the default view
+ if (oauthRedirectAuthProvider.IsNullOrWhiteSpace())
+ {
+ return defaultResponse();
+ }
+
+ // If the ?logout=true query string is not specified, then we'll redirect to the external login provider
+ // which will then redirect back to the ExternalLoginCallback action
+ if (Request.Query.TryGetValue("logout", out StringValues logout) == false || logout != "true")
+ {
+ return ExternalLogin(oauthRedirectAuthProvider);
+ }
+
+ return defaultResponse();
}
private async Task ExternalSignInAsync(ExternalLoginInfo loginInfo, Func response)
diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs
index c058a39ef4..f11402d892 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs
@@ -18,6 +18,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers;
/// Backoffice controller supporting the dashboard for language administration.
///
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
+[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)]
public class LanguageController : UmbracoAuthorizedJsonController
{
private readonly ILocalizationService _localizationService;
@@ -35,7 +36,7 @@ public class LanguageController : UmbracoAuthorizedJsonController
///
///
[HttpGet]
- public IDictionary GetAllCultures()
+ [Authorize(Policy = AuthorizationPolicies.TreeAccessLanguages)]public IDictionary GetAllCultures()
=> CultureInfo.GetCultures(CultureTypes.AllCultures).DistinctBy(x => x.Name).OrderBy(x => x.EnglishName).ToDictionary(x => x.Name, x => x.EnglishName);
///
@@ -43,6 +44,7 @@ public class LanguageController : UmbracoAuthorizedJsonController
///
///
[HttpGet]
+ [Authorize(Policy = AuthorizationPolicies.SectionAccessContent)]
public IEnumerable? GetAllLanguages()
{
IEnumerable allLanguages = _localizationService.GetAllLanguages();
@@ -51,6 +53,7 @@ public class LanguageController : UmbracoAuthorizedJsonController
}
[HttpGet]
+ [Authorize(Policy = AuthorizationPolicies.TreeAccessLanguages)]
public ActionResult GetLanguage(int id)
{
ILanguage? lang = _localizationService.GetLanguageById(id);
diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs
index 09d129b2d2..9c1aceaa6b 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs
@@ -375,7 +375,10 @@ public class MemberController : ContentControllerBase
}
// map the custom properties - this will already be set for new entities in our member binder
- contentItem.PersistedContent.IsApproved = contentItem.IsApproved;
+ if (_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() ?? false)
+ {
+ contentItem.PersistedContent.IsApproved = contentItem.IsApproved;
+ }
contentItem.PersistedContent.Email = contentItem.Email.Trim();
contentItem.PersistedContent.Username = contentItem.Username;
}
@@ -547,6 +550,13 @@ public class MemberController : ContentControllerBase
}
}
}
+ //thoese properties defaulting to sensitive, change the value of the contentItem model to the persisted value
+ if (contentItem.PersistedContent is not null)
+ {
+ contentItem.IsApproved = contentItem.PersistedContent.IsApproved;
+ contentItem.IsLockedOut = contentItem.PersistedContent.IsLockedOut;
+ }
+ contentItem.IsTwoFactorEnabled = await _twoFactorLoginService.IsTwoFactorEnabledAsync(contentItem.Key);
}
if (contentItem.PersistedContent is not null)
diff --git a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs
index c8c391d990..91cd16a0f6 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs
@@ -1,13 +1,16 @@
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Web.Common.Attributes;
+using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Controllers;
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
+[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)]
public class PublishedSnapshotCacheStatusController : UmbracoAuthorizedApiController
{
private readonly DistributedCache _distributedCache;
diff --git a/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs
index 11e8099a57..9e6f977d72 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs
@@ -22,6 +22,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers;
[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
+[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)]
public class RedirectUrlManagementController : UmbracoAuthorizedApiController
{
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
@@ -48,6 +49,8 @@ public class RedirectUrlManagementController : UmbracoAuthorizedApiController
_configManipulator = configManipulator ?? throw new ArgumentNullException(nameof(configManipulator));
}
+ private bool IsEnabled => _webRoutingSettings.CurrentValue.DisableRedirectUrlTracking == false;
+
///
/// Returns true/false of whether redirect tracking is enabled or not
///
@@ -55,9 +58,8 @@ public class RedirectUrlManagementController : UmbracoAuthorizedApiController
[HttpGet]
public IActionResult GetEnableState()
{
- var enabled = _webRoutingSettings.CurrentValue.DisableRedirectUrlTracking == false;
var userIsAdmin = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.IsAdmin() ?? false;
- return Ok(new { enabled, userIsAdmin });
+ return Ok(new { enabled = IsEnabled, userIsAdmin });
}
//add paging
@@ -107,6 +109,11 @@ public class RedirectUrlManagementController : UmbracoAuthorizedApiController
[HttpPost]
public IActionResult DeleteRedirectUrl(Guid id)
{
+ if (IsEnabled is false)
+ {
+ return BadRequest("Redirect URL tracking is disabled, and therefore no URLs can be deleted.");
+ }
+
_redirectUrlService.Delete(id);
return Ok();
}
diff --git a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs
index 32adfcfdf6..1fc5f641f6 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs
@@ -1,8 +1,10 @@
+using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Attributes;
+using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
using Stylesheet = Umbraco.Cms.Core.Models.ContentEditing.Stylesheet;
@@ -12,6 +14,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers;
/// The API controller used for retrieving available stylesheets
///
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
+[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)]
public class StylesheetController : UmbracoAuthorizedJsonController
{
private readonly IFileService _fileService;
diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
index dc2b8715ce..0476d6ce67 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
@@ -532,7 +532,7 @@ public class UsersController : BackOfficeNotificationsController
{
// first validate the username if we're showing it
ActionResult userResult = CheckUniqueUsername(userSave.Username,
- u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
+ u => u.UserState != UserState.Invited);
if (userResult.Result is not null)
{
return userResult.Result;
@@ -540,7 +540,7 @@ public class UsersController : BackOfficeNotificationsController
}
IUser? user = CheckUniqueEmail(userSave.Email,
- u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
+ u => u.UserState != UserState.Invited);
if (ModelState.IsValid == false)
{
diff --git a/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json b/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json
index 502b1fe47e..72bfee09d2 100644
--- a/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json
+++ b/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json
@@ -126,7 +126,7 @@
"steps": [
{
"title": "Create your first Document Type",
- "content": "
Step 1 of any site is to create a Document Type. A Document Type is a template for content. For each type of content you want to create you'll create a Document Type. This will define where content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.
When you have at least one Document Type in place you can start creating content and this content can then be used in a template.
In this tour you will learn how to set up a basic Document Type with a property to enter a short text.
",
+ "content": "
Step 1 of any site is to create a Document Type. A Document Type is a template for content. For each type of content you want to create, you will need to create a Document Type. This will define where content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.
When you have at least one Document Type in place you can start creating content and this content can then be used in a template.
In this tour you will learn how to set up a basic Document Type with a property to enter a short text.
",
"type": "intro"
},
{
diff --git a/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs b/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs
index 9744ed8c5f..1880b26ced 100644
--- a/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs
+++ b/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs
@@ -3,11 +3,15 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.HealthChecks;
+using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Web.BackOffice.Controllers;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
@@ -24,14 +28,27 @@ public class HealthCheckController : UmbracoAuthorizedJsonController
private readonly HealthCheckCollection _checks;
private readonly IList _disabledCheckIds;
private readonly ILogger _logger;
+ private readonly IEventAggregator _eventAggregator;
+ private readonly HealthChecksSettings _healthChecksSettings;
///
/// Initializes a new instance of the class.
///
+ [Obsolete("Use constructor that accepts IEventAggregator as a parameter, scheduled for removal in V14")]
public HealthCheckController(HealthCheckCollection checks, ILogger logger, IOptions healthChecksSettings)
+ : this(checks, logger, healthChecksSettings, StaticServiceProvider.Instance.GetRequiredService())
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [ActivatorUtilitiesConstructor]
+ public HealthCheckController(HealthCheckCollection checks, ILogger logger, IOptions healthChecksSettings, IEventAggregator eventAggregator)
{
_checks = checks ?? throw new ArgumentNullException(nameof(checks));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _eventAggregator = eventAggregator ?? throw new ArgumentException(nameof(eventAggregator));
+ _healthChecksSettings = healthChecksSettings?.Value ?? throw new ArgumentException(nameof(healthChecksSettings));
HealthChecksSettings healthCheckConfig =
healthChecksSettings.Value ?? throw new ArgumentNullException(nameof(healthChecksSettings));
@@ -80,6 +97,16 @@ public class HealthCheckController : UmbracoAuthorizedJsonController
{
_logger.LogDebug("Running health check: " + check.Name);
}
+
+ if (!_healthChecksSettings.Notification.Enabled)
+ {
+ return await check.GetStatus();
+ }
+
+ HealthCheckResults results = await HealthCheckResults.Create(check);
+ _eventAggregator.Publish(new HealthCheckCompletedNotification(results));
+
+
return await check.GetStatus();
}
catch (Exception ex)
diff --git a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs
index 197a06278c..a7564fb6ac 100644
--- a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs
+++ b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs
@@ -47,5 +47,6 @@ public class WebhookMapDefinition : IMapDefinition
target.RequestHeaders = source.RequestHeaders;
target.ResponseHeaders = source.ResponseHeaders;
target.WebhookKey = source.WebhookKey;
+ target.ExceptionOccured = source.ExceptionOccured;
}
}
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs
index a6e4e96d8b..cc1fc0c4bc 100644
--- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs
@@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
@@ -20,25 +22,35 @@ namespace Umbraco.Cms.Web.BackOffice.Security;
public class BackOfficeAntiforgery : IBackOfficeAntiforgery
{
private readonly IAntiforgery _internalAntiForgery;
- private GlobalSettings _globalSettings;
+ private readonly CookieBuilder _angularCookieBuilder;
+ [Obsolete($"Please use the constructor that accepts {nameof(ILoggerFactory)}. Will be removed in V14.")]
public BackOfficeAntiforgery(IOptionsMonitor globalSettings)
+ : this(globalSettings, NullLoggerFactory.Instance)
+ { }
+
+ public BackOfficeAntiforgery(IOptionsMonitor globalSettings, ILoggerFactory loggerFactory)
{
+ CookieSecurePolicy cookieSecurePolicy = globalSettings.CurrentValue.UseHttps ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
+
// NOTE: This is the only way to create a separate IAntiForgery service :(
// Everything in netcore is internal. I have logged an issue here https://github.com/dotnet/aspnetcore/issues/22217
// but it will not be handled so we have to revert to this.
- var services = new ServiceCollection();
- services.AddLogging();
- services.AddAntiforgery(x =>
- {
- x.HeaderName = Constants.Web.AngularHeadername;
- x.Cookie.Name = Constants.Web.CsrfValidationCookieName;
- x.Cookie.SecurePolicy = globalSettings.CurrentValue.UseHttps ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
- });
- ServiceProvider container = services.BuildServiceProvider();
- _internalAntiForgery = container.GetRequiredService();
- _globalSettings = globalSettings.CurrentValue;
- globalSettings.OnChange(x => _globalSettings = x);
+ _internalAntiForgery = new ServiceCollection()
+ .AddSingleton(loggerFactory)
+ .AddAntiforgery(x =>
+ {
+ x.HeaderName = Constants.Web.AngularHeadername;
+ x.Cookie.Name = Constants.Web.CsrfValidationCookieName;
+ x.Cookie.SecurePolicy = cookieSecurePolicy;
+ })
+ .BuildServiceProvider()
+ .GetRequiredService();
+
+ // Configure cookie builder using defaults from antiforgery options
+ _angularCookieBuilder = new AntiforgeryOptions().Cookie;
+ _angularCookieBuilder.HttpOnly = false; // Needs to be accessed from JavaScript
+ _angularCookieBuilder.SecurePolicy = cookieSecurePolicy;
}
///
@@ -68,15 +80,6 @@ public class BackOfficeAntiforgery : IBackOfficeAntiforgery
// We need to set 2 cookies:
// The cookie value that angular will use to set a header value on each request - we need to manually set this here
// The validation cookie value generated by the anti-forgery helper that we validate the header token against - set above in GetAndStoreTokens
- httpContext.Response.Cookies.Append(
- Constants.Web.AngularCookieName,
- set.RequestToken,
- new CookieOptions
- {
- Path = "/",
- //must be js readable
- HttpOnly = false,
- Secure = _globalSettings.UseHttps
- });
+ httpContext.Response.Cookies.Append(Constants.Web.AngularCookieName, set.RequestToken, _angularCookieBuilder.Build(httpContext));
}
}
diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
index f59c00eebf..66a9c03dab 100644
--- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -1,4 +1,5 @@
using System.Data.Common;
+using System.Net.Http.Headers;
using System.Reflection;
using Dazinator.Extensions.FileProviders.GlobPatternFilter;
using Microsoft.AspNetCore.Builder;
@@ -23,6 +24,7 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Blocks;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Composing;
+using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Diagnostics;
@@ -231,7 +233,6 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddSingleton(RecurringBackgroundJobHostedService.CreateHostedServiceFactory);
builder.Services.AddHostedService();
-
return builder;
}
@@ -260,6 +261,11 @@ public static partial class UmbracoBuilderExtensions
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
});
+ builder.Services.AddHttpClient(Constants.HttpClients.WebhookFiring, (services, client) =>
+ {
+ var productVersion = services.GetRequiredService().SemanticVersion.ToSemanticStringWithoutBuild();
+ client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.HttpClients.Headers.UserAgentProductName, productVersion));
+ });
return builder;
}
diff --git a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs
index f63282b7cb..0b3ff552a7 100644
--- a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs
+++ b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs
@@ -37,4 +37,7 @@ public class WebhookLogViewModel
[DataMember(Name = "responseBody")]
public string ResponseBody { get; set; } = string.Empty;
+
+ [DataMember(Name = "exceptionOccured")]
+ public bool ExceptionOccured { get; set; }
}
diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
index c7e259d283..02a039a6db 100644
--- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
+++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
@@ -20,6 +20,10 @@
+
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js
index 65fd7199d1..41693fa9b4 100644
--- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js
+++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js
@@ -282,6 +282,22 @@ function dependencies() {
],
"base": "./node_modules/wicg-inert"
},
+ {
+ "name": "umbraco-ui",
+ "src": [
+ "./node_modules/@umbraco-ui/uui/dist/uui.min.js",
+ "./node_modules/@umbraco-ui/uui/dist/uui.min.js.map",
+ "./node_modules/@umbraco-ui/uui-css/dist/custom-properties.css",
+ "./node_modules/@umbraco-ui/uui-css/dist/uui-text.css",
+ "./node_modules/@umbraco-ui/uui-css/dist/uui-css.css",
+ "./node_modules/@umbraco-ui/uui-css/assets/fonts/lato/LatoLatin-Black.woff2",
+ "./node_modules/@umbraco-ui/uui-css/assets/fonts/lato/LatoLatin-Light.woff2",
+ "./node_modules/@umbraco-ui/uui-css/assets/fonts/lato/LatoLatin-Regular.woff2",
+ "./node_modules/@umbraco-ui/uui-css/assets/fonts/lato/LatoLatin-Italic.woff2",
+ "./node_modules/@umbraco-ui/uui-css/assets/fonts/lato/LatoLatin-Bold.woff2"
+ ],
+ "base": "./node_modules/@umbraco-ui"
+ },
];
// add streams for node modules
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtogglegroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtogglegroup.directive.js
index 092908cd8d..7e9bbe942f 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtogglegroup.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtogglegroup.directive.js
@@ -73,6 +73,7 @@ Use this directive to render a group of toggle buttons.
function link(scope, el, attr, ctrl) {
for(let i = 0; i < scope.items.length; i++) {
scope.items[i].inputId = "umb-toggle-group-item_" + String.CreateGuid();
+ scope.items[i].labelId = "umb-toggle-group-item_" + String.CreateGuid();
}
scope.change = function(item) {
if (item.disabled) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js
index f3a0451191..69c11a11cc 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js
@@ -124,7 +124,8 @@
}
else if(defaultFocusedElement === null ){
// If the first focusable elements are either items from the umb-sub-views-nav menu or the umb-button-ellipsis we most likely want to start the focus on the second item
- var avoidStartElm = focusableElements.findIndex(elm => elm.classList.contains('umb-button-ellipsis') || elm.classList.contains('umb-sub-views-nav-item__action') || elm.classList.contains('umb-tab-button'));
+ // We don't want to focus the second button if it's in a tab otherwise the second tab is highlighted as well as the first tab
+ var avoidStartElm = focusableElements.findIndex(elm => elm.classList.contains('umb-button-ellipsis') || elm.classList.contains('umb-sub-views-nav-item__action') || (elm.classList.contains('umb-tab-button') && !elm.parent.classList.contains('umb-tab')));
if(avoidStartElm === 0) {
focusableElements[1].focus();
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js
index 6ba0aec2ee..227193128e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js
@@ -509,6 +509,7 @@
scope.openContentType = (contentTypeId) => {
const editor = {
id: contentTypeId,
+ entityType: scope.contentType,
submit: () => {
const args = { node: scope.model };
eventsService.emit("editors.documentType.reload", args);
@@ -518,8 +519,8 @@
editorService.close();
}
};
- editorService.documentTypeEditor(editor);
+ editorService.contentTypeEditor(editor);
};
/* ---------- TABS ---------- */
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js
index c073c89141..01de877b79 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js
@@ -282,6 +282,7 @@ Use this directive to generate a thumbnail grid of media items.
var flexStyle = {
"flex": flex + " 1 " + imageMinFlexWidth + "px",
"max-width": mediaItem.width + "px",
+ "max-height": itemMaxHeight + "px",
"min-width": itemMinWidth + "px",
"min-height": itemMinHeight + "px"
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js
index e09718176c..7b0a10cf31 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js
@@ -28,7 +28,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) {
* });
*
* @returns {Promise} resourcePromise object
- *
+ *
*/
get2FAProviders: function () {
@@ -203,7 +203,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) {
"PostRequestPasswordReset"), {
email: email
}),
- 'Request password reset failed for email ' + email);
+ 'An email with password reset instructions will be sent to the specified address if it matched our records');
},
/**
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js
index dfac875e5e..ccc80dee37 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js
@@ -323,7 +323,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
"contentTypeApiBaseUrl",
"DeleteContainer",
[{ id: id }])),
- 'Failed to delete content type contaier');
+ 'Failed to delete content type container');
},
/**
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js
index f7bba87ad5..a46ac9184e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js
@@ -166,7 +166,7 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter, locali
"mediaTypeApiBaseUrl",
"DeleteContainer",
[{ id: id }])),
- 'Failed to delete content type contaier');
+ 'Failed to delete content type container');
},
/**
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js
index d045340568..56c959be6b 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js
@@ -13,7 +13,7 @@
(function () {
'use strict';
- function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService, umbRequestHelper, clipboardService, notificationsService, $compile) {
+ function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService, umbRequestHelper, clipboardService, notificationsService, $compile, editorState) {
/**
* Simple mapping from property model content entry to editing model,
@@ -31,7 +31,8 @@
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
- prop.value = dataModel[prop.alias];
+ if (typeof (dataModel[prop.alias]) !== 'undefined')
+ prop.value = dataModel[prop.alias];
}
}
@@ -396,8 +397,12 @@
// removing duplicates.
scaffoldKeys = scaffoldKeys.filter((value, index, self) => self.indexOf(value) === index);
+ // get current node (for page context)
if(scaffoldKeys.length > 0) {
- tasks.push(contentResource.getScaffoldByKeys(-20, scaffoldKeys).then(scaffolds => {
+ var currentPage = editorState.getCurrent();
+ var currentPageId = currentPage ? (currentPage.id > 0 ? currentPage.id : currentPage.parentId) : null || -20;
+
+ tasks.push(contentResource.getScaffoldByKeys(currentPageId, scaffoldKeys).then(scaffolds => {
Object.values(scaffolds).forEach(scaffold => {
// self.scaffolds might not exists anymore, this happens if this instance has been destroyed before the load is complete.
if (self.scaffolds) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
index 6ce2a61197..f582be59ef 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
@@ -391,12 +391,44 @@ When building a custom infinite editor view you can use the same components as a
* Opens a content type picker in infinite editing, the submit callback returns an array of selected items
*
* @param {object} editor rendering options.
+ * @param {string} editor.entityType Entity type to open - default is document type.
* @param {boolean} editor.multiPicker Pick one or multiple items.
* @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object.
* @param {function} editor.close Callback function when the close button is clicked.
* @returns {object} editor object
*/
function contentTypePicker(editor) {
+
+ if (!editor.entityType) editor.entityType = "documentType";
+
+ switch (editor.entityType) {
+ case "documentType":
+ documentTypePicker(editor);
+ break;
+ case "mediaType":
+ mediaTypePicker(editor);
+ break;
+ case "memberType":
+ memberTypePicker(editor);
+ break;
+ }
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.editorService#documentTypePicker
+ * @methodOf umbraco.services.editorService
+ *
+ * @description
+ * Opens a document type picker in infinite editing, the submit callback returns an array of selected items
+ *
+ * @param {object} editor rendering options.
+ * @param {boolean} editor.multiPicker Pick one or multiple items.
+ * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object.
+ * @param {function} editor.close Callback function when the close button is clicked.
+ * @returns {object} editor object
+ */
+ function documentTypePicker(editor) {
editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
if (!editor.size) editor.size = "small";
editor.section = "settings";
@@ -635,6 +667,39 @@ When building a custom infinite editor view you can use the same components as a
open(editor);
}
+ /**
+ * @ngdoc method
+ * @name umbraco.services.editorService#contentTypeEditor
+ * @methodOf umbraco.services.editorService
+ *
+ * @description
+ * Opens the content type editor in infinite editing, the submit callback returns the alias of the saved content type.
+ *
+ * @param {object} editor rendering options.
+ * @param {string} editor.entityType Entity type to open - default document type.
+ * @param {number} editor.id Indicates the ID of the content type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the content type editor for creating a new content type.
+ * @param {boolean} editor.create Set to `true` to open the content type editor for creating a new content type.
+ * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object.
+ * @param {function} editor.close Callback function when the close button is clicked.
+ * @returns {object} editor object.
+ */
+ function contentTypeEditor(editor) {
+
+ if (!editor.entityType) editor.entityType = "documentType";
+
+ switch (editor.entityType) {
+ case "documentType":
+ documentTypeEditor(editor);
+ break;
+ case "mediaType":
+ mediaTypeEditor(editor);
+ break;
+ case "memberType":
+ memberTypeEditor(editor);
+ break;
+ }
+ }
+
/**
* @ngdoc method
* @name umbraco.services.editorService#documentTypeEditor
@@ -852,7 +917,6 @@ When building a custom infinite editor view you can use the same components as a
if (!editor.size) editor.size = "small";
open(editor);
}
-
/**
* @ngdoc method
* @name umbraco.services.editorService#sectionPicker
@@ -1149,10 +1213,12 @@ When building a custom infinite editor view you can use the same components as a
open: open,
close: close,
closeAll: closeAll,
- mediaEditor: mediaEditor,
contentEditor: contentEditor,
+ mediaEditor: mediaEditor,
+ memberEditor: memberEditor,
contentPicker: contentPicker,
contentTypePicker: contentTypePicker,
+ documentTypePicker: documentTypePicker,
mediaTypePicker: mediaTypePicker,
memberTypePicker: memberTypePicker,
copy: copy,
@@ -1164,6 +1230,7 @@ When building a custom infinite editor view you can use the same components as a
linkPicker: linkPicker,
mediaPicker: mediaPicker,
iconPicker: iconPicker,
+ contentTypeEditor: contentTypeEditor,
documentTypeEditor: documentTypeEditor,
mediaTypeEditor: mediaTypeEditor,
memberTypeEditor: memberTypeEditor,
@@ -1184,7 +1251,6 @@ When building a custom infinite editor view you can use the same components as a
macroPicker: macroPicker,
memberGroupPicker: memberGroupPicker,
memberPicker: memberPicker,
- memberEditor: memberEditor,
mediaCropDetails,
eventPicker : eventPicker
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js
index 77b97545b6..2354b76c92 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js
@@ -14,8 +14,8 @@
*
*/
function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState, backdropService) {
-
//the promise that will be resolved when the navigation is ready
+ var activeElement = undefined;
var navReadyPromise = $q.defer();
//the main tree's API reference, this is acquired when the tree has initialized
@@ -82,7 +82,6 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
if (appState.getGlobalState("isTablet") === true) {
appState.setGlobalState("showNavigation", false);
}
-
break;
}
}
@@ -134,9 +133,20 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
backdropService.close();
leftColumn.classList.remove(aboveClass);
}
+
+ returnFocusToTriggerElement();
}
}
+ function returnFocusToTriggerElement() {
+ if(!activeElement) return;
+
+ const elementToFocus = activeElement.querySelector(".umb-tree-item__inner .umb-button-ellipsis");
+ document.body.classList.add("tabbing-active");
+ elementToFocus.style.backgroundColor = "hsla(0,0%,100%,.8)";
+ elementToFocus.focus();
+ }
+
function showBackdrop() {
var backDropOptions = {
'element': document.getElementById('leftcolumn')
@@ -680,9 +690,12 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
if (appState.getMenuState("allowHideMenuDialog") === false) {
return;
}
+
if (showMenu) {
this.showMenu({ skipDefault: true, node: appState.getMenuState("currentNode") });
} else {
+ activeElement = document.querySelector("#tree .active");
+
closeBackdrop();
setMode("default");
}
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 8bf8eecf91..5cf975bf8e 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
@@ -295,13 +295,13 @@
saveModel.memberGroups = _.keys(_.pick(prop.value, value => value === true));
break;
case '_umb_approved':
- saveModel.isApproved = prop.value;
+ saveModel.isApproved = prop.value == true;
break;
case '_umb_lockedOut':
- saveModel.isLockedOut = prop.value;
+ saveModel.isLockedOut = prop.value == true;
break;
case '_umb_twoFactorEnabled':
- saveModel.isTwoFactorEnabled = prop.value;
+ saveModel.isTwoFactorEnabled = prop.value == true;
break;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
index e69d49d82e..91318f5cf4 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
@@ -108,7 +108,7 @@ function dateHelper() {
const localOffset = new Date().getTimezoneOffset();
const serverTimeNeedsOffsetting = -serverOffset !== localOffset;
if (serverTimeNeedsOffsetting) {
- dateVal = this.convertToLocalMomentTime(date, serverOffset, format);
+ dateVal = this.convertToLocalMomentTime(date, serverOffset);
} else {
dateVal = moment(date, parsingFormat);
}
diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html
index a74540ab74..a468de3b9b 100644
--- a/src/Umbraco.Web.UI.Client/src/index.html
+++ b/src/Umbraco.Web.UI.Client/src/index.html
@@ -14,8 +14,8 @@
-