Skip to content

Commit a627930

Browse files
kjacZeegaan
authored andcommitted
Warn about un-routable content at publish time (#17705)
(cherry picked from commit 2d9cfc8)
1 parent ee8bdfc commit a627930

File tree

5 files changed

+110
-15
lines changed

5 files changed

+110
-15
lines changed

src/Umbraco.Core/EmbeddedResources/Lang/da.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,7 @@ Mange hilsner fra Umbraco robotten
14691469
<key alias="publishWithMissingDomain">Der er ikke noget domæne konfigureret for %0%, kontakt vensligst en
14701470
administrator, se loggen for mere information
14711471
</key>
1472+
<key alias="publishWithNoUrl">Dokumentet har ikke nogen URL, muligvis grundet en kollision med et andet dokuments navn. Flere detaljer kan ses under Info.</key>
14721473
<key alias="copySuccessMessage">Dit systems information er blevet kopieret til udklipsholderen</key>
14731474
<key alias="cannotCopyInformation">Kunne desværre ikke kopiere dit systems information til udklipsholderen</key>
14741475
<key alias="webhookSaved">Webhook gemt</key>

src/Umbraco.Core/EmbeddedResources/Lang/en.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
17001700
<key alias="publishWithMissingDomain">There is no domain configured for %0%, please contact an administrator, see
17011701
log for more information
17021702
</key>
1703+
<key alias="publishWithNoUrl">The document does not have a URL, possibly due to a naming collision with another document. More details can be found under Info.</key>
17031704
<key alias="copySuccessMessage">Your system information has successfully been copied to the clipboard</key>
17041705
<key alias="cannotCopyInformation">Could not copy your system information to the clipboard</key>
17051706
<key alias="webhookSaved">Webhook saved</key>

src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
17321732
<key alias="publishWithMissingDomain">There is no domain configured for %0%, please contact an administrator, see
17331733
log for more information
17341734
</key>
1735+
<key alias="publishWithNoUrl">The document does not have a URL, possibly due to a naming collision with another document. More details can be found under Info.</key>
17351736
<key alias="preventCleanupEnableError">An error occurred while enabling version cleanup for %0%</key>
17361737
<key alias="preventCleanupDisableError">An error occurred while disabling version cleanup for %0%</key>
17371738
<key alias="copySuccessMessage">Your system information has successfully been copied to the clipboard</key>

src/Umbraco.Web.BackOffice/Controllers/ContentController.cs

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,8 +1134,12 @@ private bool EnsureUniqueName(string? name, IContent? content, string modelName)
11341134
{
11351135
PublishResult publishStatus = PublishInternal(contentItem, defaultCulture, cultureForInvariantErrors, out wasCancelled, out var successfulCultures);
11361136
// Add warnings if domains are not set up correctly
1137-
AddDomainWarnings(publishStatus.Content, successfulCultures, globalNotifications);
1137+
var addedDomainWarnings = AddDomainWarnings(publishStatus.Content, successfulCultures, globalNotifications, defaultCulture);
11381138
AddPublishStatusNotifications(new[] { publishStatus }, globalNotifications, notifications, successfulCultures);
1139+
if (addedDomainWarnings is false)
1140+
{
1141+
AddPublishRoutableErrorNotifications(new[] { publishStatus }, globalNotifications, successfulCultures);
1142+
}
11391143
}
11401144
break;
11411145
case ContentSaveAction.PublishWithDescendants:
@@ -1151,8 +1155,12 @@ private bool EnsureUniqueName(string? name, IContent? content, string modelName)
11511155
}
11521156

11531157
var publishStatus = PublishBranchInternal(contentItem, false, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList();
1154-
AddDomainWarnings(publishStatus, successfulCultures, globalNotifications);
1158+
var addedDomainWarnings = AddDomainWarnings(publishStatus, successfulCultures, globalNotifications, defaultCulture);
11551159
AddPublishStatusNotifications(publishStatus, globalNotifications, notifications, successfulCultures);
1160+
if (addedDomainWarnings is false)
1161+
{
1162+
AddPublishRoutableErrorNotifications(publishStatus, globalNotifications, successfulCultures);
1163+
}
11561164
}
11571165
break;
11581166
case ContentSaveAction.PublishWithDescendantsForce:
@@ -1235,6 +1243,48 @@ private void AddPublishStatusNotifications(
12351243
}
12361244
}
12371245

1246+
private void AddPublishRoutableErrorNotifications(
1247+
IReadOnlyCollection<PublishResult> publishStatus,
1248+
SimpleNotificationModel globalNotifications,
1249+
string[]? successfulCultures)
1250+
{
1251+
IContent? content = publishStatus.FirstOrDefault()?.Content;
1252+
if (content is null)
1253+
{
1254+
return;
1255+
}
1256+
1257+
if (content.ContentType.VariesByCulture() is false)
1258+
{
1259+
// successfulCultures will be null here - change it to a wildcard and utilize this below
1260+
successfulCultures = ["*"];
1261+
}
1262+
1263+
if (successfulCultures?.Any() is not true)
1264+
{
1265+
return;
1266+
}
1267+
1268+
ContentItemDisplay? contentItemDisplay = _umbracoMapper.Map<ContentItemDisplay>(publishStatus.FirstOrDefault()?.Content);
1269+
if (contentItemDisplay?.Urls is null)
1270+
{
1271+
return;
1272+
}
1273+
1274+
foreach (var culture in successfulCultures)
1275+
{
1276+
if (contentItemDisplay.Urls.Where(u => u.Culture == culture || culture == "*").All(u => u.IsUrl is false))
1277+
{
1278+
globalNotifications.AddWarningNotification(
1279+
_localizedTextService.Localize("auditTrails", "publish"),
1280+
_localizedTextService.Localize("speechBubbles", "publishWithNoUrl"));
1281+
1282+
// only add one warning here, even though there might actually be more
1283+
break;
1284+
}
1285+
}
1286+
}
1287+
12381288
/// <summary>
12391289
/// Validates critical data for persistence and updates the ModelState and result accordingly
12401290
/// </summary>
@@ -1770,12 +1820,15 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string? defau
17701820
}
17711821
}
17721822

1773-
private void AddDomainWarnings(IEnumerable<PublishResult> publishResults, string[]? culturesPublished, SimpleNotificationModel globalNotifications)
1823+
private bool AddDomainWarnings(IEnumerable<PublishResult> publishResults, string[]? culturesPublished, SimpleNotificationModel globalNotifications, string? defaultCulture)
17741824
{
1825+
var addedDomainWarnings = false;
17751826
foreach (PublishResult publishResult in publishResults)
17761827
{
1777-
AddDomainWarnings(publishResult.Content, culturesPublished, globalNotifications);
1828+
addedDomainWarnings &= AddDomainWarnings(publishResult.Content, culturesPublished, globalNotifications, defaultCulture);
17781829
}
1830+
1831+
return addedDomainWarnings;
17791832
}
17801833

17811834
/// <summary>
@@ -1788,24 +1841,25 @@ private void AddDomainWarnings(IEnumerable<PublishResult> publishResults, string
17881841
/// <param name="persistedContent"></param>
17891842
/// <param name="culturesPublished"></param>
17901843
/// <param name="globalNotifications"></param>
1791-
internal void AddDomainWarnings(IContent? persistedContent, string[]? culturesPublished, SimpleNotificationModel globalNotifications)
1844+
/// <param name="defaultCulture"></param>
1845+
internal bool AddDomainWarnings(IContent? persistedContent, string[]? culturesPublished, SimpleNotificationModel globalNotifications, string? defaultCulture)
17921846
{
17931847
if (_contentSettings.ShowDomainWarnings is false)
17941848
{
1795-
return;
1849+
return false;
17961850
}
17971851

17981852
// Don't try to verify if no cultures were published
17991853
if (culturesPublished is null)
18001854
{
1801-
return;
1855+
return false;
18021856
}
18031857

18041858
var publishedCultures = GetPublishedCulturesFromAncestors(persistedContent).ToList();
18051859
// If only a single culture is published we shouldn't have any routing issues
18061860
if (publishedCultures.Count < 2)
18071861
{
1808-
return;
1862+
return false;
18091863
}
18101864

18111865
// If more than a single culture is published we need to verify that there's a domain registered for each published culture
@@ -1827,6 +1881,12 @@ internal void AddDomainWarnings(IContent? persistedContent, string[]? culturesPu
18271881
// No domains at all, add a warning, to add domains.
18281882
if (assignedDomains is null || assignedDomains.Count == 0)
18291883
{
1884+
// If only the default culture was published we shouldn't have any routing issues
1885+
if (culturesPublished.Length == 1 && culturesPublished[0].InvariantEquals(defaultCulture))
1886+
{
1887+
return false;
1888+
}
1889+
18301890
globalNotifications.AddWarningNotification(
18311891
_localizedTextService.Localize("auditTrails", "publish"),
18321892
_localizedTextService.Localize("speechBubbles", "publishWithNoDomains"));
@@ -1835,14 +1895,16 @@ internal void AddDomainWarnings(IContent? persistedContent, string[]? culturesPu
18351895
"The root node {RootNodeName} was published with multiple cultures, but no domains are configured, this will cause routing and caching issues, please register domains for: {Cultures}",
18361896
persistedContent?.Name,
18371897
string.Join(", ", publishedCultures));
1838-
return;
1898+
return true;
18391899
}
18401900

18411901
// If there is some domains, verify that there's a domain for each of the published cultures
1902+
var addedDomainWarnings = false;
18421903
foreach (var culture in culturesPublished
18431904
.Where(culture => assignedDomains.Any(x =>
18441905
x.LanguageIsoCode?.Equals(culture, StringComparison.OrdinalIgnoreCase) ?? false) is false))
18451906
{
1907+
addedDomainWarnings = true;
18461908
globalNotifications.AddWarningNotification(
18471909
_localizedTextService.Localize("auditTrails", "publish"),
18481910
_localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new[] { culture }));
@@ -1852,6 +1914,8 @@ internal void AddDomainWarnings(IContent? persistedContent, string[]? culturesPu
18521914
persistedContent?.Name,
18531915
culture);
18541916
}
1917+
1918+
return addedDomainWarnings;
18551919
}
18561920

18571921
/// <summary>

tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void Root_Node_With_Domains_Causes_No_Warning()
5656
var notifications = new SimpleNotificationModel();
5757

5858
var contentController = CreateContentController(domainServiceMock.Object);
59-
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
59+
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications, "en-us");
6060

6161
Assert.IsEmpty(notifications.Notifications);
6262
}
@@ -82,7 +82,7 @@ public void Node_With_Single_Published_Culture_Causes_No_Warning()
8282
var notifications = new SimpleNotificationModel();
8383

8484
var contentController = CreateContentController(domainServiceMock.Object);
85-
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
85+
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications, "en-us");
8686

8787
Assert.IsEmpty(notifications.Notifications);
8888
}
@@ -111,7 +111,7 @@ public void Root_Node_Without_Domains_Causes_SingleWarning()
111111
var notifications = new SimpleNotificationModel();
112112

113113
var contentController = CreateContentController(domainServiceMock.Object);
114-
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
114+
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications, "en-us");
115115
Assert.AreEqual(1, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
116116
}
117117

@@ -139,10 +139,38 @@ public void One_Warning_Per_Culture_Being_Published()
139139
var notifications = new SimpleNotificationModel();
140140

141141
var contentController = CreateContentController(domainServiceMock.Object);
142-
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
142+
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications, "en-us");
143143
Assert.AreEqual(3, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
144144
}
145145

146+
[Test]
147+
public void Does_Not_Warn_When_Only_Publishing_The_Default_Culture_With_No_Domains_Assigned()
148+
{
149+
var domainServiceMock = new Mock<IDomainService>();
150+
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
151+
.Returns(Enumerable.Empty<IDomain>());
152+
153+
var rootNode = new ContentBuilder()
154+
.WithContentType(CreateContentType())
155+
.WithId(1060)
156+
.AddContentCultureInfosCollection()
157+
.AddCultureInfos()
158+
.WithCultureIso("da-dk")
159+
.Done()
160+
.AddCultureInfos()
161+
.WithCultureIso("en-us")
162+
.Done()
163+
.Done()
164+
.Build();
165+
166+
var culturesPublished = new[] { "en-us" };
167+
var notifications = new SimpleNotificationModel();
168+
169+
var contentController = CreateContentController(domainServiceMock.Object);
170+
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications, "en-us");
171+
Assert.IsEmpty(notifications.Notifications);
172+
}
173+
146174
[Test]
147175
public void Ancestor_Domains_Counts()
148176
{
@@ -189,7 +217,7 @@ public void Ancestor_Domains_Counts()
189217
var contentController = CreateContentController(domainServiceMock.Object);
190218
var notifications = new SimpleNotificationModel();
191219

192-
contentController.AddDomainWarnings(level3Node, culturesPublished, notifications);
220+
contentController.AddDomainWarnings(level3Node, culturesPublished, notifications, "en-us");
193221

194222
// We expect one error because all domains except "de-de" is registered somewhere in the ancestor path
195223
Assert.AreEqual(1, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
@@ -225,7 +253,7 @@ public void Only_Warns_About_Cultures_Being_Published()
225253
var notifications = new SimpleNotificationModel();
226254

227255
var contentController = CreateContentController(domainServiceMock.Object);
228-
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
256+
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications, "en-us");
229257

230258
// We only get two errors, one for each culture being published, so no errors from previously published cultures.
231259
Assert.AreEqual(2, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));

0 commit comments

Comments
 (0)