From 7850078623c1339fd358a634cfb36a2f1ed04526 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 24 Jan 2025 19:46:28 +0100 Subject: [PATCH] Redirect to the published URL when exiting preview (#18114) Co-authored-by: Andy Butland --- .../Controllers/PreviewController.cs | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 17875c2950..6ab88844b7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -1,9 +1,12 @@ +using System.Text.RegularExpressions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Editors; using Umbraco.Cms.Core.Features; using Umbraco.Cms.Core.Hosting; @@ -27,7 +30,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers; [DisableBrowserCache] [Area(Constants.Web.Mvc.BackOfficeArea)] -public class PreviewController : Controller +public partial class PreviewController : Controller { private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; private readonly ICookieManager _cookieManager; @@ -39,7 +42,9 @@ public class PreviewController : Controller private readonly IRuntimeMinifier _runtimeMinifier; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ICompositeViewEngine _viewEngines; + private readonly WebRoutingSettings _webRoutingSettings; + [Obsolete("Please use the non-obsolete constructor.")] public PreviewController( UmbracoFeatures features, IOptionsSnapshot globalSettings, @@ -51,9 +56,38 @@ public class PreviewController : Controller IRuntimeMinifier runtimeMinifier, ICompositeViewEngine viewEngines, IUmbracoContextAccessor umbracoContextAccessor) + : this( + features, + globalSettings, + StaticServiceProvider.Instance.GetRequiredService>(), + publishedSnapshotService, + backofficeSecurityAccessor, + localizationService, + hostingEnvironment, + cookieManager, + runtimeMinifier, + viewEngines, + umbracoContextAccessor) + { + } + + [ActivatorUtilitiesConstructor] + public PreviewController( + UmbracoFeatures features, + IOptionsSnapshot globalSettings, + IOptionsSnapshot webRoutingSettings, + IPublishedSnapshotService publishedSnapshotService, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + ILocalizationService localizationService, + IHostingEnvironment hostingEnvironment, + ICookieManager cookieManager, + IRuntimeMinifier runtimeMinifier, + ICompositeViewEngine viewEngines, + IUmbracoContextAccessor umbracoContextAccessor) { _features = features; _globalSettings = globalSettings.Value; + _webRoutingSettings = webRoutingSettings.Value; _publishedSnapshotService = publishedSnapshotService; _backofficeSecurityAccessor = backofficeSecurityAccessor; _localizationService = localizationService; @@ -153,6 +187,43 @@ public class PreviewController : Controller // Expire Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. _cookieManager.ExpireCookie(Constants.Web.AcceptPreviewCookieName); + // are we attempting a redirect to the default route (by ID with optional culture)? + Match match = DefaultPreviewRedirectRegex().Match(redir ?? string.Empty); + if (match.Success) + { + var id = int.Parse(match.Groups["id"].Value); + + // first try to resolve the published URL + if (_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext) && + umbracoContext.Content is not null) + { + IPublishedContent? publishedContent = umbracoContext.Content.GetById(id); + if (publishedContent is null) + { + // content is not published, redirect to root + return Redirect("/"); + } + + var culture = publishedContent.ContentType.VariesByCulture() + && match.Groups.TryGetValue("culture", out Group? group) + ? group.Value + : null; + + var publishedUrl = publishedContent.Url(culture); + if (WebPath.IsWellFormedWebPath(publishedUrl, UriKind.RelativeOrAbsolute)) + { + return Redirect(publishedUrl); + } + } + + // could not resolve the published URL - are we allowed to route content by ID? + if (_webRoutingSettings.DisableFindContentByIdPath) + { + // no we are not - redirect to root instead + return Redirect("/"); + } + } + if (WebPath.IsWellFormedWebPath(redir, UriKind.Relative) && Uri.TryCreate(redir, UriKind.Relative, out Uri? url)) { @@ -161,4 +232,7 @@ public class PreviewController : Controller return Redirect("/"); } + + [GeneratedRegex("^\\/(?\\d*)(\\?culture=(?[\\w-]*))?$")] + private static partial Regex DefaultPreviewRedirectRegex(); }