From 63ae29a1aa6df0b482b453e62fdaa35267e13aed Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:48:55 +0200 Subject: [PATCH] Preview: Fixes a potential issue where the preview URL could be a different backoffice host (#20591) * hotfix: ensures that local urls stay relative so we land up on the correct backoffice host that the user initiated the preview session from originally * feat: since ensureAbsoluteUrl is never supplied anymore, we can remove the parameter altogether * Remove unused dependency * Expose IsExternal for URLs * feat: adds localize controller * chore: generates api models * feat: marks the internal preview default url as relative, so that the `` tag is taken into consideration - that way the URL will open on whatever host is active * Remove IsExternal from the API again * regenerate types --------- Co-authored-by: kjac --- .../Factories/DocumentUrlFactory.cs | 13 ++++--------- src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs | 4 ++-- .../workspace/document-workspace.context.ts | 8 +++++++- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Factories/DocumentUrlFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DocumentUrlFactory.cs index 998eb80873..53bd255a2b 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/DocumentUrlFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/DocumentUrlFactory.cs @@ -16,7 +16,6 @@ public class DocumentUrlFactory : IDocumentUrlFactory private readonly UrlProviderCollection _urlProviders; private readonly IPreviewService _previewService; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IAbsoluteUrlBuilder _absoluteUrlBuilder; private readonly ILogger _logger; public DocumentUrlFactory( @@ -24,14 +23,12 @@ public class DocumentUrlFactory : IDocumentUrlFactory UrlProviderCollection urlProviders, IPreviewService previewService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IAbsoluteUrlBuilder absoluteUrlBuilder, ILogger logger) { _publishedUrlInfoProvider = publishedUrlInfoProvider; _urlProviders = urlProviders; _previewService = previewService; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _absoluteUrlBuilder = absoluteUrlBuilder; _logger = logger; } @@ -39,7 +36,7 @@ public class DocumentUrlFactory : IDocumentUrlFactory { ISet urlInfos = await _publishedUrlInfoProvider.GetAllAsync(content); return urlInfos - .Select(urlInfo => CreateDocumentUrlInfo(urlInfo, false)) + .Select(CreateDocumentUrlInfo) .ToArray(); } @@ -89,18 +86,16 @@ public class DocumentUrlFactory : IDocumentUrlFactory } } - return CreateDocumentUrlInfo(previewUrlInfo, previewUrlInfo.IsExternal is false); + return CreateDocumentUrlInfo(previewUrlInfo); } - private DocumentUrlInfo CreateDocumentUrlInfo(UrlInfo urlInfo, bool ensureAbsoluteUrl) + private DocumentUrlInfo CreateDocumentUrlInfo(UrlInfo urlInfo) { var url = urlInfo.Url?.ToString(); return new DocumentUrlInfo { Culture = urlInfo.Culture, - Url = ensureAbsoluteUrl && url is not null - ? _absoluteUrlBuilder.ToAbsoluteUrl(url).ToString() - : url, + Url = url, Message = urlInfo.Message, Provider = urlInfo.Provider, }; diff --git a/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs b/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs index c4385bb5ad..e6d4def79e 100644 --- a/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs @@ -144,7 +144,7 @@ public class NewDefaultUrlProvider : IUrlProvider public Task GetPreviewUrlAsync(IContent content, string? culture, string? segment) => Task.FromResult( UrlInfo.AsUrl( - $"/{Constants.System.UmbracoPathSegment}/preview?id={content.Key}&culture={culture}&segment={segment}", + $"preview?id={content.Key}&culture={culture}&segment={segment}", Alias, culture, isExternal: false)); @@ -182,7 +182,7 @@ public class NewDefaultUrlProvider : IUrlProvider // We have the published content now, so we can check if the culture is published, and thus avoid the DB hit. string route; var isDraft = _umbracoContextAccessor.GetRequiredUmbracoContext().InPreviewMode; - if(isDraft is false && string.IsNullOrWhiteSpace(culture) is false && content.Cultures.Any() && content.IsInvariantOrHasCulture(culture) is false) + if (isDraft is false && string.IsNullOrWhiteSpace(culture) is false && content.Cultures.Any() && content.IsInvariantOrHasCulture(culture) is false) { route = "#"; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 9eef3cde1b..0d199d874c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -40,6 +40,7 @@ import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/documen import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import type { UmbVariantPropertyGuardRule } from '@umbraco-cms/backoffice/property'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; type ContentModel = UmbDocumentDetailModel; type ContentTypeModel = UmbDocumentTypeDetailModel; @@ -70,6 +71,7 @@ export class UmbDocumentWorkspaceContext #isTrashedContext = new UmbIsTrashedEntityContext(this); #documentSegmentRepository = new UmbDocumentSegmentRepository(this); #actionEventContext?: typeof UMB_ACTION_EVENT_CONTEXT.TYPE; + #localize = new UmbLocalizationController(this); constructor(host: UmbControllerHost) { super(host, { @@ -350,7 +352,11 @@ export class UmbDocumentWorkspaceContext } if (previewUrlData.message) { - umbPeekError(this._host, { color: 'danger', headline: 'Preview error', message: previewUrlData.message }); + umbPeekError(this._host, { + color: 'danger', + headline: this.#localize.term('general_preview'), + message: previewUrlData.message, + }); throw new Error(previewUrlData.message); } }