Sort domains (Culture and Hostnames) (#13797)

* Add sort order to IDomain, UmbracoDomain and DomainDto

* Add migration to create domain sort order column

* Add Sort method to domain service

* Set sort order when persisting new domain and order results

* Add multiple and block style support to umb-button-group

* Allow sorting domains in back-office, improve UI and rewrite PostSaveLanguageAndDomains for correctly sorting domains

* Ensure routing and cache keeps the domain sort order

* Update test to assert correct domain order

* Move migration to target 11.3 and cleanup plan

* Fix formatting/styling and make SelectDomains private

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
This commit is contained in:
Ronald Barendse
2023-02-14 10:35:45 +01:00
committed by GitHub
parent 7348171c01
commit 45036f54dd
25 changed files with 858 additions and 690 deletions

View File

@@ -2246,16 +2246,14 @@ public class ContentController : ContentControllerBase
public ContentDomainsAndCulture GetCultureAndDomains(int id)
{
IDomain[]? nodeDomains = _domainService.GetAssignedDomains(id, true)?.ToArray();
IDomain? wildcard = nodeDomains?.FirstOrDefault(d => d.IsWildcard);
IEnumerable<DomainDisplay>? domains = nodeDomains?.Where(d => !d.IsWildcard)
.Select(d => new DomainDisplay(d.DomainName, d.LanguageId.GetValueOrDefault(0)));
IDomain[] assignedDomains = _domainService.GetAssignedDomains(id, true).ToArray();
IDomain? wildcard = assignedDomains.FirstOrDefault(d => d.IsWildcard);
IEnumerable<DomainDisplay> domains = assignedDomains.Where(d => !d.IsWildcard).Select(d => new DomainDisplay(d.DomainName, d.LanguageId.GetValueOrDefault(0)));
return new ContentDomainsAndCulture
{
Language = wildcard == null || !wildcard.LanguageId.HasValue ? "undefined" : wildcard.LanguageId.ToString(),
Domains = domains,
Language = wildcard == null || !wildcard.LanguageId.HasValue
? "undefined"
: wildcard.LanguageId.ToString()
};
}
@@ -2264,11 +2262,11 @@ public class ContentController : ContentControllerBase
{
if (model.Domains is not null)
{
foreach (DomainDisplay domain in model.Domains)
foreach (DomainDisplay domainDisplay in model.Domains)
{
try
{
Uri uri = DomainUtilities.ParseUriFromDomainName(domain.Name, new Uri(Request.GetEncodedUrl()));
DomainUtilities.ParseUriFromDomainName(domainDisplay.Name, new Uri(Request.GetEncodedUrl()));
}
catch (UriFormatException)
{
@@ -2277,18 +2275,16 @@ public class ContentController : ContentControllerBase
}
}
// Validate node
IContent? node = _contentService.GetById(model.NodeId);
if (node == null)
{
HttpContext.SetReasonPhrase("Node Not Found.");
return NotFound("There is no content node with id {model.NodeId}.");
}
EntityPermission? permission =
_userService.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, node.Path);
// Validate permissions on node
EntityPermission? permission = _userService.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, node.Path);
if (permission?.AssignedPermissions.Contains(ActionAssignDomain.ActionLetter.ToString(), StringComparer.Ordinal) == false)
{
HttpContext.SetReasonPhrase("Permission Denied.");
@@ -2296,120 +2292,118 @@ public class ContentController : ContentControllerBase
}
model.Valid = true;
IDomain[]? domains = _domainService.GetAssignedDomains(model.NodeId, true)?.ToArray();
ILanguage[] languages = _localizationService.GetAllLanguages().ToArray();
ILanguage? language = model.Language > 0 ? languages.FirstOrDefault(l => l.Id == model.Language) : null;
// process wildcard
if (language != null)
IDomain[] assignedDomains = _domainService.GetAssignedDomains(model.NodeId, true).ToArray();
ILanguage[] languages = _localizationService.GetAllLanguages().ToArray();
// Process language
ILanguage? language = model.Language > 0 ? languages.FirstOrDefault(l => l.Id == model.Language) : null;
if (language is not null)
{
// yet there is a race condition here...
IDomain? wildcard = domains?.FirstOrDefault(d => d.IsWildcard);
if (wildcard != null)
// Update or create language on wildcard domain
IDomain? assignedWildcardDomain = assignedDomains.FirstOrDefault(d => d.IsWildcard);
if (assignedWildcardDomain is not null)
{
wildcard.LanguageId = language.Id;
assignedWildcardDomain.LanguageId = language.Id;
}
else
{
wildcard = new UmbracoDomain("*" + model.NodeId)
assignedWildcardDomain = new UmbracoDomain("*" + model.NodeId)
{
LanguageId = model.Language,
RootContentId = model.NodeId
};
}
Attempt<OperationResult?> saveAttempt = _domainService.Save(wildcard);
if (saveAttempt == false)
Attempt<OperationResult?> saveAttempt = _domainService.Save(assignedWildcardDomain);
if (saveAttempt.Success == false)
{
HttpContext.SetReasonPhrase(saveAttempt.Result?.Result.ToString());
return BadRequest("Saving domain failed");
}
}
else
// Delete every domain that's in the database, but not in the model
foreach (IDomain? assignedDomain in assignedDomains.Where(d => (d.IsWildcard && language is null) || (d.IsWildcard == false && (model.Domains is null || model.Domains.All(m => m.Name.InvariantEquals(d.DomainName) == false)))))
{
IDomain? wildcard = domains?.FirstOrDefault(d => d.IsWildcard);
if (wildcard != null)
{
_domainService.Delete(wildcard);
}
_domainService.Delete(assignedDomain);
}
// process domains
// delete every (non-wildcard) domain, that exists in the DB yet is not in the model
foreach (IDomain domain in domains?.Where(d =>
d.IsWildcard == false &&
(model.Domains?.All(m => m.Name.InvariantEquals(d.DomainName) == false) ??
false)) ??
Array.Empty<IDomain>())
// Process domains
if (model.Domains is not null)
{
_domainService.Delete(domain);
}
var names = new List<string>();
// create or update domains in the model
foreach (DomainDisplay domainModel in model.Domains?.Where(m => string.IsNullOrWhiteSpace(m.Name) == false) ??
Array.Empty<DomainDisplay>())
{
language = languages.FirstOrDefault(l => l.Id == domainModel.Lang);
if (language == null)
var savedDomains = new List<IDomain>();
foreach (DomainDisplay domainDisplay in model.Domains.Where(m => string.IsNullOrWhiteSpace(m.Name) == false))
{
continue;
}
var name = domainModel.Name.ToLowerInvariant();
if (names.Contains(name))
{
domainModel.Duplicate = true;
continue;
}
names.Add(name);
IDomain? domain = domains?.FirstOrDefault(d => d.DomainName.InvariantEquals(domainModel.Name));
if (domain != null)
{
domain.LanguageId = language.Id;
_domainService.Save(domain);
}
else if (_domainService.Exists(domainModel.Name))
{
domainModel.Duplicate = true;
IDomain? xdomain = _domainService.GetByName(domainModel.Name);
var xrcid = xdomain?.RootContentId;
if (xrcid.HasValue)
language = languages.FirstOrDefault(l => l.Id == domainDisplay.Lang);
if (language == null)
{
IContent? xcontent = _contentService.GetById(xrcid.Value);
var xnames = new List<string>();
while (xcontent != null)
continue;
}
var domainName = domainDisplay.Name.ToLowerInvariant();
if (savedDomains.Any(d => d.DomainName == domainName))
{
domainDisplay.Duplicate = true;
continue;
}
IDomain? domain = assignedDomains.FirstOrDefault(d => d.DomainName.InvariantEquals(domainName));
if (domain is null && _domainService.GetByName(domainName) is IDomain existingDomain)
{
// Domain name already exists on another node
domainDisplay.Duplicate = true;
// Add node breadcrumbs
if (existingDomain.RootContentId is int rootContentId)
{
if (xcontent.Name is not null)
var breadcrumbs = new List<string?>();
IContent? content = _contentService.GetById(rootContentId);
while (content is not null)
{
xnames.Add(xcontent.Name);
breadcrumbs.Add(content.Name);
if (content.ParentId < -1)
{
breadcrumbs.Add("Recycle Bin");
}
content = _contentService.GetParent(content);
}
if (xcontent.ParentId < -1)
{
xnames.Add("Recycle Bin");
}
xcontent = _contentService.GetParent(xcontent);
breadcrumbs.Reverse();
domainDisplay.Other = "/" + string.Join("/", breadcrumbs);
}
xnames.Reverse();
domainModel.Other = "/" + string.Join("/", xnames);
continue;
}
}
else
{
// yet there is a race condition here...
var newDomain = new UmbracoDomain(name) { LanguageId = domainModel.Lang, RootContentId = model.NodeId };
Attempt<OperationResult?> saveAttempt = _domainService.Save(newDomain);
if (saveAttempt == false)
// Update or create domain
if (domain != null)
{
domain.LanguageId = language.Id;
}
else
{
domain = new UmbracoDomain(domainName)
{
LanguageId = language.Id,
RootContentId = model.NodeId,
};
}
Attempt<OperationResult?> saveAttempt = _domainService.Save(domain);
if (saveAttempt.Success == false)
{
HttpContext.SetReasonPhrase(saveAttempt.Result?.Result.ToString());
return BadRequest("Saving new domain failed");
return BadRequest("Saving domain failed");
}
savedDomains.Add(domain);
}
// Sort saved domains
_domainService.Sort(savedDomains);
}
model.Valid = model.Domains?.All(m => m.Duplicate == false) ?? false;