Skip to content

Commit 2846325

Browse files
bergmaniaMigaroez
authored andcommitted
Fixes routing issues (#17572)
* Child of second root should also hide root path and backoffice needs to show all domains for each language. * Fixes routing issues based on findings #17572 (comment) * Revert "Fixes routing issues based on findings #17572 (comment)" This reverts commit ba7fb5c. * Fix urls of descendants of non-first roots do not show the root urlsegment when HideTopLevel is true
1 parent 37be812 commit 2846325

File tree

4 files changed

+222
-66
lines changed

4 files changed

+222
-66
lines changed

src/Umbraco.Core/Services/DocumentUrlService.cs

Lines changed: 137 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,19 @@ public async Task DeleteUrlsFromCacheAsync(IEnumerable<Guid> documentKeysEnumera
393393
return GetTopMostRootKey(isDraft, culture);
394394
}
395395

396-
// Otherwise we have to find the root items (or child of the first root when hideTopLevelNodeFromPath is true) and follow the url segments in them to get to correct document key
396+
// Special case for all top level nodes except the first (that will have /)
397+
if (runnerKey is null && urlSegments.Length == 1 && hideTopLevelNodeFromPath is true)
398+
{
399+
IEnumerable<Guid> rootKeys = GetKeysInRoot(false, isDraft, culture);
400+
Guid? rootKeyWithUrlSegment = GetChildWithUrlSegment(rootKeys, urlSegments.First(), culture, isDraft);
401+
402+
if (rootKeyWithUrlSegment is not null)
403+
{
404+
return rootKeyWithUrlSegment;
405+
}
406+
}
407+
408+
// Otherwise we have to find the root items (or child of the roots when hideTopLevelNodeFromPath is true) and follow the url segments in them to get to correct document key
397409
for (var index = 0; index < urlSegments.Length; index++)
398410
{
399411
var urlSegment = urlSegments[index];
@@ -447,7 +459,7 @@ public string GetLegacyRouteFormat(Guid documentKey, string? culture, bool isDra
447459
var cultureOrDefault = string.IsNullOrWhiteSpace(culture) is false ? culture : _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult();
448460

449461
Guid[] ancestorsOrSelfKeysArray = ancestorsOrSelfKeys as Guid[] ?? ancestorsOrSelfKeys.ToArray();
450-
IDictionary<Guid, Domain?> ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray.ToDictionary(x => x, ancestorKey =>
462+
ILookup<Guid, Domain?> ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray.ToLookup(x => x, ancestorKey =>
451463
{
452464
Attempt<int> idAttempt = _idKeyMap.GetIdForKey(ancestorKey, UmbracoObjectTypes.Document);
453465

@@ -466,13 +478,11 @@ public string GetLegacyRouteFormat(Guid documentKey, string? culture, bool isDra
466478

467479
foreach (Guid ancestorOrSelfKey in ancestorsOrSelfKeysArray)
468480
{
469-
if (ancestorOrSelfKeyToDomains.TryGetValue(ancestorOrSelfKey, out Domain? domain))
481+
var domains = ancestorOrSelfKeyToDomains[ancestorOrSelfKey].WhereNotNull();
482+
if (domains.Any())
470483
{
471-
if (domain is not null)
472-
{
473-
foundDomain = domain;
474-
break;
475-
}
484+
foundDomain = domains.First();// What todo here that is better?
485+
break;
476486
}
477487

478488
if (_cache.TryGetValue(CreateCacheKey(ancestorOrSelfKey, cultureOrDefault, isDraft), out PublishedDocumentUrlSegment? publishedDocumentUrlSegment))
@@ -528,7 +538,7 @@ public async Task<IEnumerable<UrlInfo>> ListUrlsAsync(Guid contentKey)
528538
var cultures = languages.ToDictionary(x=>x.IsoCode);
529539

530540
Guid[] ancestorsOrSelfKeysArray = ancestorsOrSelfKeys as Guid[] ?? ancestorsOrSelfKeys.ToArray();
531-
Dictionary<Guid, Task<Dictionary<string, Domain>>> ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray.ToDictionary(x => x, async ancestorKey =>
541+
Dictionary<Guid, Task<ILookup<string, Domain>>> ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray.ToDictionary(x => x, async ancestorKey =>
532542
{
533543
Attempt<int> idAttempt = _idKeyMap.GetIdForKey(ancestorKey, UmbracoObjectTypes.Document);
534544

@@ -537,70 +547,119 @@ public async Task<IEnumerable<UrlInfo>> ListUrlsAsync(Guid contentKey)
537547
return null;
538548
}
539549
IEnumerable<Domain> domains = _domainCacheService.GetAssigned(idAttempt.Result, false);
540-
return domains.ToDictionary(x => x.Culture!);
550+
return domains.ToLookup(x => x.Culture!);
541551
})!;
542552

543553
foreach ((string culture, ILanguage language) in cultures)
544554
{
545-
var urlSegments = new List<string>();
546-
Domain? foundDomain = null;
555+
var urlSegments = new List<string>();
556+
List<Domain?> foundDomains = new List<Domain?>();
547557

548-
var hasUrlInCulture = true;
549-
foreach (Guid ancestorOrSelfKey in ancestorsOrSelfKeysArray)
550-
{
551-
if (ancestorOrSelfKeyToDomains.TryGetValue(ancestorOrSelfKey, out Task<Dictionary<string, Domain>>? domainDictionaryTask))
558+
var hasUrlInCulture = true;
559+
foreach (Guid ancestorOrSelfKey in ancestorsOrSelfKeysArray)
560+
{
561+
var domainLookup = await ancestorOrSelfKeyToDomains[ancestorOrSelfKey];
562+
if (domainLookup.Any())
552563
{
553-
Dictionary<string, Domain> domainDictionary = await domainDictionaryTask;
554-
if (domainDictionary.TryGetValue(culture, out Domain? domain))
564+
var domains = domainLookup[culture];
565+
foreach (Domain domain in domains)
555566
{
556-
Attempt<Guid> domainKeyAttempt = _idKeyMap.GetKeyForId(domain.ContentId, UmbracoObjectTypes.Document);
567+
Attempt<Guid> domainKeyAttempt =
568+
_idKeyMap.GetKeyForId(domain.ContentId, UmbracoObjectTypes.Document);
557569
if (domainKeyAttempt.Success)
558570
{
559571
if (_publishStatusQueryService.IsDocumentPublished(domainKeyAttempt.Result, culture))
560572
{
561-
foundDomain = domain;
562-
break;
573+
foundDomains.Add(domain);
563574
}
564575
}
565576
}
577+
578+
if (foundDomains.Any())
579+
{
580+
break;
581+
}
566582
}
567583

568-
if (_cache.TryGetValue(CreateCacheKey(ancestorOrSelfKey, culture, false), out PublishedDocumentUrlSegment? publishedDocumentUrlSegment))
584+
if (_cache.TryGetValue(
585+
CreateCacheKey(ancestorOrSelfKey, culture, false),
586+
out PublishedDocumentUrlSegment? publishedDocumentUrlSegment))
569587
{
570588
urlSegments.Add(publishedDocumentUrlSegment.UrlSegment);
571589
}
572590
else
573591
{
574592
hasUrlInCulture = false;
575593
}
576-
}
594+
}
595+
596+
//If we did not find a domain and this is not the default language, then the content is not routable
597+
if (foundDomains.Any() is false && language.IsDefault is false)
598+
{
599+
continue;
600+
}
577601

578-
//If we did not find a domain and this is not the default language, then the content is not routable
579-
if (foundDomain is null && language.IsDefault is false)
580-
{
581-
continue;
582-
}
583602

584-
var isRootFirstItem = GetTopMostRootKey(false, culture) == ancestorsOrSelfKeysArray.Last();
603+
var isRootFirstItem = GetTopMostRootKey(false, culture) == ancestorsOrSelfKeysArray.Last();
604+
605+
var leftToRight = _globalSettings.ForceCombineUrlPathLeftToRight
606+
|| CultureInfo.GetCultureInfo(culture).TextInfo.IsRightToLeft is false;
607+
if (leftToRight)
608+
{
609+
urlSegments.Reverse();
610+
}
611+
612+
// If no domain was found, we need to add a null domain to the list to make sure we check for no domains.
613+
if (foundDomains.Any() is false)
614+
{
615+
foundDomains.Add(null);
616+
}
617+
618+
foreach (Domain? foundDomain in foundDomains)
619+
{
620+
var foundUrl = GetFullUrl(isRootFirstItem, urlSegments, foundDomain, leftToRight);
621+
622+
if (foundDomain is not null)
623+
{
624+
// if we found a domain, it should be safe to show url
625+
result.Add(new UrlInfo(
626+
text: foundUrl,
627+
isUrl: hasUrlInCulture,
628+
culture: culture
629+
));
630+
}
631+
else
632+
{
633+
// otherwise we need to ensure that no other page has the same url
634+
// e.g. a site with two roots that both have a child with the same name
635+
Guid? documentKeyByRoute = GetDocumentKeyByRoute(foundUrl, culture, foundDomain?.ContentId, false);
636+
if (contentKey.Equals(documentKeyByRoute))
637+
{
638+
result.Add(new UrlInfo(
639+
text: foundUrl,
640+
isUrl: hasUrlInCulture,
641+
culture: culture
642+
));
643+
}
644+
else
645+
{
646+
result.Add(new UrlInfo(
647+
text: "Conflict: Other page has the same url",
648+
isUrl: false,
649+
culture: culture
650+
));
651+
}
652+
}
585653

586-
var leftToRight = _globalSettings.ForceCombineUrlPathLeftToRight
587-
|| CultureInfo.GetCultureInfo(culture).TextInfo.IsRightToLeft is false;
588-
if (leftToRight)
589-
{
590-
urlSegments.Reverse();
591-
}
592654

593-
result.Add(new UrlInfo(
594-
text: GetFullUrl(isRootFirstItem, urlSegments, foundDomain, leftToRight),
595-
isUrl: hasUrlInCulture,
596-
culture: culture
597-
));
598655

656+
}
599657
}
600658

601659
return result;
602660
}
603661

662+
604663
private string GetFullUrl(bool isRootFirstItem, List<string> segments, Domain? foundDomain, bool leftToRight)
605664
{
606665
var urlSegments = new List<string>(segments);
@@ -610,7 +669,7 @@ private string GetFullUrl(bool isRootFirstItem, List<string> segments, Domain? f
610669
return foundDomain.Name.EnsureEndsWith("/") + string.Join('/', urlSegments);
611670
}
612671

613-
var hideTopLevel = _globalSettings.HideTopLevelNodeFromPath && isRootFirstItem;
672+
var hideTopLevel = HideTopLevel(_globalSettings.HideTopLevelNodeFromPath, isRootFirstItem, urlSegments);
614673
if (leftToRight)
615674
{
616675
return '/' + string.Join('/', urlSegments.Skip(hideTopLevel ? 1 : 0));
@@ -624,6 +683,21 @@ private string GetFullUrl(bool isRootFirstItem, List<string> segments, Domain? f
624683
return '/' + string.Join('/', urlSegments);
625684
}
626685

686+
private bool HideTopLevel(bool hideTopLevelNodeFromPath, bool isRootFirstItem, List<string> urlSegments)
687+
{
688+
if (hideTopLevelNodeFromPath is false)
689+
{
690+
return false;
691+
}
692+
693+
if(isRootFirstItem is false && urlSegments.Count == 1)
694+
{
695+
return false;
696+
}
697+
698+
return true;
699+
}
700+
627701
public async Task CreateOrUpdateUrlSegmentsWithDescendantsAsync(Guid key)
628702
{
629703
var id = _idKeyMap.GetIdForKey(key, UmbracoObjectTypes.Document).Result;
@@ -646,7 +720,7 @@ public async Task CreateOrUpdateUrlSegmentsAsync(Guid key)
646720
}
647721
}
648722

649-
private IEnumerable<Guid> GetKeysInRoot(bool addFirstLevelChildren, bool isDraft, string culture)
723+
private IEnumerable<Guid> GetKeysInRoot(bool considerFirstLevelAsRoot, bool isDraft, string culture)
650724
{
651725
if (_documentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeysEnumerable) is false)
652726
{
@@ -655,13 +729,10 @@ private IEnumerable<Guid> GetKeysInRoot(bool addFirstLevelChildren, bool isDraft
655729

656730
IEnumerable<Guid> rootKeys = rootKeysEnumerable as Guid[] ?? rootKeysEnumerable.ToArray();
657731

658-
foreach (Guid rootKey in rootKeys)
732+
if (considerFirstLevelAsRoot)
659733
{
660-
yield return rootKey;
661-
}
734+
yield return rootKeys.First();
662735

663-
if (addFirstLevelChildren)
664-
{
665736
foreach (Guid rootKey in rootKeys)
666737
{
667738
if (isDraft is false && IsContentPublished(rootKey, culture) is false)
@@ -677,6 +748,13 @@ private IEnumerable<Guid> GetKeysInRoot(bool addFirstLevelChildren, bool isDraft
677748
}
678749
}
679750
}
751+
else
752+
{
753+
foreach (Guid rootKey in rootKeys)
754+
{
755+
yield return rootKey;
756+
}
757+
}
680758

681759
}
682760

@@ -710,23 +788,28 @@ private IEnumerable<Guid> GetChildKeys(Guid documentKey)
710788
return Enumerable.Empty<Guid>();
711789
}
712790

713-
/// <summary>
714-
/// Gets the top most root key.
715-
/// </summary>
716-
/// <returns>The top most root key.</returns>
717-
private Guid? GetTopMostRootKey(bool isDraft, string culture)
791+
private IEnumerable<Guid> GetRootKeys(bool isDraft, string culture)
718792
{
719793
if (_documentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys))
720794
{
721795
foreach (Guid rootKey in rootKeys)
722796
{
723797
if (isDraft || IsContentPublished(rootKey, culture))
724798
{
725-
return rootKey;
799+
yield return rootKey;
726800
}
727801
}
728802
}
729-
return null;
803+
}
804+
805+
806+
/// <summary>
807+
/// Gets the top most root key.
808+
/// </summary>
809+
/// <returns>The top most root key.</returns>
810+
private Guid? GetTopMostRootKey(bool isDraft, string culture)
811+
{
812+
return GetRootKeys(isDraft, culture).Cast<Guid?>().FirstOrDefault();
730813
}
731814

732815
[MethodImpl(MethodImplOptions.AggressiveInlining)]

src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-links.element.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class UmbDocumentWorkspaceViewInfoLinksElement extends UmbLitElement {
2222
private _variantOptions?: Array<UmbDocumentVariantOptionModel>;
2323

2424
@state()
25-
private _lookup: Record<string, string> = {};
25+
private _lookup: Record<string, string[]> = {};
2626

2727
constructor() {
2828
super();
@@ -56,7 +56,10 @@ export class UmbDocumentWorkspaceViewInfoLinksElement extends UmbLitElement {
5656
if (data?.length) {
5757
data[0].urls.forEach((item) => {
5858
if (item.culture && item.url) {
59-
this._lookup[item.culture] = item.url;
59+
if(this._lookup[item.culture] == null){
60+
this._lookup[item.culture] = [];
61+
}
62+
this._lookup[item.culture].push(item.url);
6063
}
6164
});
6265
this.requestUpdate('_lookup');
@@ -108,18 +111,20 @@ export class UmbDocumentWorkspaceViewInfoLinksElement extends UmbLitElement {
108111
#renderUrl(variantOption: UmbDocumentVariantOptionModel) {
109112
const varies = !!variantOption.culture;
110113
const culture = varies ? variantOption.culture! : variantOption.language.unique;
111-
const url = this._lookup[culture];
114+
const urls = this._lookup[culture];
112115
return when(
113-
url,
116+
urls && urls.length >= 1,
114117
() => html`
115-
<a class="link-item" href=${url} target="_blank">
116-
<span>
117-
<span class="culture">${varies ? culture : nothing}</span>
118-
<span>${url}</span>
119-
</span>
120-
<uui-icon name="icon-out"></uui-icon>
121-
</a>
122-
`,
118+
${urls.map((url) =>
119+
html`
120+
<a class="link-item" href=${url} target="_blank">
121+
<span>
122+
<span class="culture">${varies ? culture : nothing}</span>
123+
<span>${url}</span>
124+
</span>
125+
<uui-icon name="icon-out"></uui-icon>
126+
</a>`
127+
)}`,
123128
() => html`
124129
<div class="link-item">
125130
<span>

0 commit comments

Comments
 (0)