Skip to content

Commit e5e5e8c

Browse files
Zeegaankjac
andauthoredJan 8, 2025··
V15: Warn when content is unroutable (#17837)
* Add notification handler * Add IsContentPublished that bypasses caching * Add clarifying comment * Refactor, to never hit the db * Remove old comment * Don't add warnings if disabled * Dedicated configuration option to suppress unroutable content warnings --------- Co-authored-by: kjac <[email protected]>
1 parent bf9ba16 commit e5e5e8c

File tree

4 files changed

+145
-4
lines changed

4 files changed

+145
-4
lines changed
 

‎src/Umbraco.Core/Configuration/Models/ContentSettings.cs

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class ContentSettings
2828
internal const bool StaticDisableUnpublishWhenReferenced = false;
2929
internal const bool StaticAllowEditInvariantFromNonDefault = false;
3030
internal const bool StaticShowDomainWarnings = true;
31+
internal const bool StaticShowUnroutableContentWarnings = true;
3132

3233
/// <summary>
3334
/// Gets or sets a value for the content notification settings.
@@ -141,4 +142,10 @@ public class ContentSettings
141142
/// </summary>
142143
[DefaultValue(StaticShowDomainWarnings)]
143144
public bool ShowDomainWarnings { get; set; } = StaticShowDomainWarnings;
145+
146+
/// <summary>
147+
/// Gets or sets a value indicating whether to show unroutable content warnings.
148+
/// </summary>
149+
[DefaultValue(StaticShowUnroutableContentWarnings)]
150+
public bool ShowUnroutableContentWarnings { get; set; } = StaticShowUnroutableContentWarnings;
144151
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
using Microsoft.Extensions.Logging;
2+
using Microsoft.Extensions.Options;
3+
using Umbraco.Cms.Core.Configuration.Models;
4+
using Umbraco.Cms.Core.Models;
5+
using Umbraco.Cms.Core.Models.PublishedContent;
6+
using Umbraco.Cms.Core.Notifications;
7+
using Umbraco.Cms.Core.PublishedCache;
8+
using Umbraco.Cms.Core.Routing;
9+
using Umbraco.Cms.Core.Services;
10+
using Umbraco.Cms.Core.Services.Navigation;
11+
using Umbraco.Cms.Core.Web;
12+
using Umbraco.Extensions;
13+
14+
namespace Umbraco.Cms.Core.Events;
15+
16+
public class AddUnroutableContentWarningsWhenPublishingNotificationHandler : INotificationAsyncHandler<ContentPublishedNotification>
17+
{
18+
private readonly IPublishedRouter _publishedRouter;
19+
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
20+
private readonly ILanguageService _languageService;
21+
private readonly ILocalizedTextService _localizedTextService;
22+
private readonly IContentService _contentService;
23+
private readonly IVariationContextAccessor _variationContextAccessor;
24+
private readonly ILoggerFactory _loggerFactory;
25+
private readonly UriUtility _uriUtility;
26+
private readonly IPublishedUrlProvider _publishedUrlProvider;
27+
private readonly IPublishedContentCache _publishedContentCache;
28+
private readonly IDocumentNavigationQueryService _navigationQueryService;
29+
private readonly IEventMessagesFactory _eventMessagesFactory;
30+
private readonly ContentSettings _contentSettings;
31+
32+
public AddUnroutableContentWarningsWhenPublishingNotificationHandler(
33+
IPublishedRouter publishedRouter,
34+
IUmbracoContextAccessor umbracoContextAccessor,
35+
ILanguageService languageService,
36+
ILocalizedTextService localizedTextService,
37+
IContentService contentService,
38+
IVariationContextAccessor variationContextAccessor,
39+
ILoggerFactory loggerFactory,
40+
UriUtility uriUtility,
41+
IPublishedUrlProvider publishedUrlProvider,
42+
IPublishedContentCache publishedContentCache,
43+
IDocumentNavigationQueryService navigationQueryService,
44+
IEventMessagesFactory eventMessagesFactory,
45+
IOptions<ContentSettings> contentSettings)
46+
{
47+
_publishedRouter = publishedRouter;
48+
_umbracoContextAccessor = umbracoContextAccessor;
49+
_languageService = languageService;
50+
_localizedTextService = localizedTextService;
51+
_contentService = contentService;
52+
_variationContextAccessor = variationContextAccessor;
53+
_loggerFactory = loggerFactory;
54+
_uriUtility = uriUtility;
55+
_publishedUrlProvider = publishedUrlProvider;
56+
_publishedContentCache = publishedContentCache;
57+
_navigationQueryService = navigationQueryService;
58+
_eventMessagesFactory = eventMessagesFactory;
59+
_contentSettings = contentSettings.Value;
60+
}
61+
62+
public async Task HandleAsync(ContentPublishedNotification notification, CancellationToken cancellationToken)
63+
{
64+
if (_contentSettings.ShowUnroutableContentWarnings is false)
65+
{
66+
return;
67+
}
68+
69+
foreach (IContent content in notification.PublishedEntities)
70+
{
71+
string[]? successfulCultures;
72+
if (content.ContentType.VariesByCulture() is false)
73+
{
74+
// successfulCultures will be null here - change it to a wildcard and utilize this below
75+
successfulCultures = ["*"];
76+
}
77+
else
78+
{
79+
successfulCultures = content.PublishedCultures.ToArray();
80+
}
81+
82+
if (successfulCultures?.Any() is not true)
83+
{
84+
return;
85+
}
86+
87+
if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext))
88+
{
89+
return;
90+
}
91+
92+
UrlInfo[] urls = (await content.GetContentUrlsAsync(
93+
_publishedRouter,
94+
umbracoContext,
95+
_languageService,
96+
_localizedTextService,
97+
_contentService,
98+
_variationContextAccessor,
99+
_loggerFactory.CreateLogger<IContent>(),
100+
_uriUtility,
101+
_publishedUrlProvider,
102+
_publishedContentCache,
103+
_navigationQueryService)).ToArray();
104+
105+
106+
EventMessages eventMessages = _eventMessagesFactory.Get();
107+
foreach (var culture in successfulCultures)
108+
{
109+
if (urls.Where(u => u.Culture == culture || culture == "*").All(u => u.IsUrl is false))
110+
{
111+
eventMessages.Add(new EventMessage("Content published", "The document does not have a URL, possibly due to a naming collision with another document. More details can be found under Info.", EventMessageType.Warning));
112+
113+
// only add one warning here, even though there might actually be more
114+
break;
115+
}
116+
}
117+
}
118+
}
119+
}

‎src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ public virtual IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
145145

146146
private string GetLegacyRouteFormatById(Guid key, string? culture)
147147
{
148+
var isDraft = _umbracoContextAccessor.GetRequiredUmbracoContext().InPreviewMode;
148149

149-
return _documentUrlService.GetLegacyRouteFormat(key, culture, _umbracoContextAccessor.GetRequiredUmbracoContext().InPreviewMode);
150+
151+
return _documentUrlService.GetLegacyRouteFormat(key, culture, isDraft);
150152

151153

152154
}
@@ -163,9 +165,22 @@ private string GetLegacyRouteFormatById(Guid key, string? culture)
163165
throw new ArgumentException("Current URL must be absolute.", nameof(current));
164166
}
165167

168+
// This might seem to be some code duplication, as we do the same check in GetLegacyRouteFormat
169+
// but this is strictly neccesary, as if we're coming from a published notification
170+
// this document will still not always be in the memory cache. And thus we have to hit the DB
171+
// We have the published content now, so we can check if the culture is published, and thus avoid the DB hit.
172+
string route;
173+
var isDraft = _umbracoContextAccessor.GetRequiredUmbracoContext().InPreviewMode;
174+
if(isDraft is false && string.IsNullOrWhiteSpace(culture) is false && content.Cultures.Any() && content.IsInvariantOrHasCulture(culture) is false)
175+
{
176+
route = "#";
177+
}
178+
else
179+
{
180+
route = GetLegacyRouteFormatById(content.Key, culture);
181+
}
166182

167183
// will not use cache if previewing
168-
var route = GetLegacyRouteFormatById(content.Key, culture);
169184

170185
return GetUrlFromRoute(route, content.Id, current, mode, culture);
171186
}

‎src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,8 @@ public static IUmbracoBuilder AddCoreNotifications(this IUmbracoBuilder builder)
411411
.AddNotificationHandler<AssignedUserGroupPermissionsNotification, AuditNotificationsHandler>();
412412

413413
// Handlers for publish warnings
414-
builder
415-
.AddNotificationHandler<ContentPublishedNotification, AddDomainWarningsWhenPublishingNotificationHandler>();
414+
builder.AddNotificationHandler<ContentPublishedNotification, AddDomainWarningsWhenPublishingNotificationHandler>();
415+
builder.AddNotificationAsyncHandler<ContentPublishedNotification, AddUnroutableContentWarningsWhenPublishingNotificationHandler>();
416416

417417
// Handlers for save warnings
418418
builder

0 commit comments

Comments
 (0)
Please sign in to comment.