Merge pull request #7660 from umbraco/v8/bugfix/AB4900-start-nodes-list-views
Fixing when a start node is a content item in a nested list view
This commit is contained in:
@@ -27,6 +27,13 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <returns></returns>
|
||||
bool HasContainerInPath(string contentPath);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether there is a list view content item in the path.
|
||||
/// </summary>
|
||||
/// <param name="ids"></param>
|
||||
/// <returns></returns>
|
||||
bool HasContainerInPath(params int[] ids);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
|
||||
/// </summary>
|
||||
|
||||
@@ -1309,14 +1309,16 @@ WHERE cmsContentType." + aliasColumn + @" LIKE @pattern",
|
||||
return test;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the path of a content item, this will return true if the content item exists underneath a list view content item
|
||||
/// </summary>
|
||||
/// <param name="contentPath"></param>
|
||||
/// <returns></returns>
|
||||
/// <inheritdoc />
|
||||
public bool HasContainerInPath(string contentPath)
|
||||
{
|
||||
var ids = contentPath.Split(',').Select(int.Parse);
|
||||
var ids = contentPath.Split(',').Select(int.Parse).ToArray();
|
||||
return HasContainerInPath(ids);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasContainerInPath(params int[] ids)
|
||||
{
|
||||
var sql = new Sql($@"SELECT COUNT(*) FROM cmsContentType
|
||||
INNER JOIN {Constants.DatabaseSchema.Tables.Content} ON cmsContentType.nodeId={Constants.DatabaseSchema.Tables.Content}.contentTypeId
|
||||
WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentType.isContainer=@isContainer", new { ids, isContainer = true });
|
||||
|
||||
@@ -69,6 +69,13 @@ namespace Umbraco.Core.Services
|
||||
/// <returns></returns>
|
||||
bool HasContainerInPath(string contentPath);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether there is a list view content item in the path.
|
||||
/// </summary>
|
||||
/// <param name="ids"></param>
|
||||
/// <returns></returns>
|
||||
bool HasContainerInPath(params int[] ids);
|
||||
|
||||
Attempt<OperationResult<OperationResultType, EntityContainer>> CreateContainer(int parentContainerId, string name, int userId = Constants.Security.SuperUserId);
|
||||
Attempt<OperationResult> SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId);
|
||||
EntityContainer GetContainer(int containerId);
|
||||
|
||||
+9
@@ -321,6 +321,15 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasContainerInPath(params int[] ids)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
// can use same repo for both content and media
|
||||
return Repository.HasContainerInPath(ids);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TItem> GetDescendants(int id, bool andSelf)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
|
||||
@@ -869,7 +869,7 @@ namespace Umbraco.Web.Editors
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to perform the saving of the content and add the notifications to the result
|
||||
@@ -1161,14 +1161,14 @@ namespace Umbraco.Web.Editors
|
||||
//validate if we can publish based on the mandatory language requirements
|
||||
var canPublish = ValidatePublishingMandatoryLanguages(
|
||||
cultureErrors,
|
||||
contentItem, cultureVariants, mandatoryCultures,
|
||||
contentItem, cultureVariants, mandatoryCultures,
|
||||
mandatoryVariant => mandatoryVariant.Publish);
|
||||
|
||||
//Now check if there are validation errors on each variant.
|
||||
//If validation errors are detected on a variant and it's state is set to 'publish', then we
|
||||
//need to change it to 'save'.
|
||||
//It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages.
|
||||
|
||||
|
||||
foreach (var variant in contentItem.Variants)
|
||||
{
|
||||
if (cultureErrors.Contains(variant.Culture))
|
||||
@@ -1656,14 +1656,14 @@ namespace Umbraco.Web.Editors
|
||||
[HttpPost]
|
||||
public DomainSave PostSaveLanguageAndDomains(DomainSave model)
|
||||
{
|
||||
foreach(var domain in model.Domains)
|
||||
foreach (var domain in model.Domains)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = DomainUtilities.ParseUriFromDomainName(domain.Name, Request.RequestUri);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
{
|
||||
var response = Request.CreateValidationErrorResponse(Services.TextService.Localize("assignDomain/invalidDomain"));
|
||||
throw new HttpResponseException(response);
|
||||
}
|
||||
@@ -1829,7 +1829,7 @@ namespace Umbraco.Web.Editors
|
||||
base.HandleInvalidModelState(display);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Maps the dto property values and names to the persisted model
|
||||
/// </summary>
|
||||
@@ -1842,7 +1842,7 @@ namespace Umbraco.Web.Editors
|
||||
var culture = property.PropertyType.VariesByCulture() ? variant.Culture : null;
|
||||
var segment = property.PropertyType.VariesBySegment() ? variant.Segment : null;
|
||||
return (culture, segment);
|
||||
}
|
||||
}
|
||||
|
||||
var variantIndex = 0;
|
||||
|
||||
@@ -1884,15 +1884,15 @@ namespace Umbraco.Web.Editors
|
||||
(save, property) =>
|
||||
{
|
||||
// Get property value
|
||||
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
|
||||
return property.GetValue(culture, segment);
|
||||
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
|
||||
return property.GetValue(culture, segment);
|
||||
},
|
||||
(save, property, v) =>
|
||||
{
|
||||
// Set property value
|
||||
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
|
||||
property.SetValue(v, culture, segment);
|
||||
},
|
||||
property.SetValue(v, culture, segment);
|
||||
},
|
||||
variant.Culture);
|
||||
|
||||
variantIndex++;
|
||||
@@ -2172,7 +2172,10 @@ namespace Umbraco.Web.Editors
|
||||
/// <returns></returns>
|
||||
private ContentItemDisplay MapToDisplay(IContent content)
|
||||
{
|
||||
var display = Mapper.Map<ContentItemDisplay>(content);
|
||||
var display = Mapper.Map<ContentItemDisplay>(content, context =>
|
||||
{
|
||||
context.Items["CurrentUser"] = Security.CurrentUser;
|
||||
});
|
||||
display.AllowPreview = display.AllowPreview && content.Trashed == false && content.ContentType.IsElement == false;
|
||||
return display;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Routing;
|
||||
@@ -27,6 +28,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly TabsAndPropertiesMapper<IContent> _tabsAndPropertiesMapper;
|
||||
private readonly ContentSavedStateMapper<ContentPropertyDisplay> _stateMapper;
|
||||
private readonly ContentBasicSavedStateMapper<ContentPropertyBasic> _basicStateMapper;
|
||||
@@ -34,7 +36,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
public ContentMapDefinition(CommonMapper commonMapper, ILocalizedTextService localizedTextService, IContentService contentService, IContentTypeService contentTypeService,
|
||||
IFileService fileService, IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, ILocalizationService localizationService, ILogger logger,
|
||||
IUserService userService)
|
||||
IUserService userService, IEntityService entityService)
|
||||
{
|
||||
_commonMapper = commonMapper;
|
||||
_localizedTextService = localizedTextService;
|
||||
@@ -46,7 +48,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
_localizationService = localizationService;
|
||||
_logger = logger;
|
||||
_userService = userService;
|
||||
|
||||
_entityService = entityService;
|
||||
_tabsAndPropertiesMapper = new TabsAndPropertiesMapper<IContent>(localizedTextService);
|
||||
_stateMapper = new ContentSavedStateMapper<ContentPropertyDisplay>();
|
||||
_basicStateMapper = new ContentBasicSavedStateMapper<ContentPropertyBasic>();
|
||||
@@ -80,7 +82,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
target.Icon = source.ContentType.Icon;
|
||||
target.Id = source.Id;
|
||||
target.IsBlueprint = source.Blueprint;
|
||||
target.IsChildOfListView = DermineIsChildOfListView(source);
|
||||
target.IsChildOfListView = DetermineIsChildOfListView(source, context);
|
||||
target.IsContainer = source.ContentType.IsContainer;
|
||||
target.IsElement = source.ContentType.IsElement;
|
||||
target.Key = source.Key;
|
||||
@@ -211,11 +213,63 @@ namespace Umbraco.Web.Models.Mapping
|
||||
return source.CultureInfos.TryGetValue(culture, out var name) && !name.Name.IsNullOrWhiteSpace() ? name.Name : $"({source.Name})";
|
||||
}
|
||||
|
||||
private bool DermineIsChildOfListView(IContent source)
|
||||
/// <summary>
|
||||
/// Checks if the content item is a descendant of a list view
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns>
|
||||
/// Returns true if the content item is a descendant of a list view and where the content is
|
||||
/// not a current user's start node.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// We must check if it's the current user's start node because in that case we will actually be
|
||||
/// rendering the tree node underneath the list view to visually show context. In this case we return
|
||||
/// false because the item is technically not being rendered as part of a list view but instead as a
|
||||
/// real tree node. If we didn't perform this check then tree syncing wouldn't work correctly.
|
||||
/// </remarks>
|
||||
private bool DetermineIsChildOfListView(IContent source, MapperContext context)
|
||||
{
|
||||
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
|
||||
var userStartNodes = Array.Empty<int>();
|
||||
|
||||
// In cases where a user's start node is below a list view, we will actually render
|
||||
// out the tree to that start node and in that case for that start node, we want to return
|
||||
// false here.
|
||||
if (context.HasItems && context.Items.TryGetValue("CurrentUser", out var usr) && usr is IUser currentUser)
|
||||
{
|
||||
userStartNodes = currentUser.CalculateContentStartNodeIds(_entityService);
|
||||
if (!userStartNodes.Contains(Constants.System.Root))
|
||||
{
|
||||
// return false if this is the user's actual start node, the node will be rendered in the tree
|
||||
// regardless of if it's a list view or not
|
||||
if (userStartNodes.Contains(source.Id))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var parent = _contentService.GetParent(source);
|
||||
return parent != null && (parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(parent.Path));
|
||||
|
||||
if (parent == null)
|
||||
return false;
|
||||
|
||||
var pathParts = parent.Path.Split(',').Select(x => int.TryParse(x, out var i) ? i : 0).ToList();
|
||||
|
||||
// reduce the path parts so we exclude top level content items that
|
||||
// are higher up than a user's start nodes
|
||||
foreach (var n in userStartNodes)
|
||||
{
|
||||
var index = pathParts.IndexOf(n);
|
||||
if (index != -1)
|
||||
{
|
||||
// now trim all top level start nodes to the found index
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
pathParts.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(pathParts.ToArray());
|
||||
}
|
||||
|
||||
private DateTime? GetScheduledDate(IContent source, ContentScheduleAction action, MapperContext context)
|
||||
|
||||
@@ -364,7 +364,12 @@ namespace Umbraco.Web.Trees
|
||||
var startNodes = Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes);
|
||||
//if any of these start nodes' parent is current, then we need to render children normally so we need to switch some logic and tell
|
||||
// the UI that this node does have children and that it isn't a container
|
||||
if (startNodes.Any(x => x.ParentId == e.Id))
|
||||
|
||||
if (startNodes.Any(x =>
|
||||
{
|
||||
var pathParts = x.Path.Split(',');
|
||||
return pathParts.Contains(e.Id.ToInvariantString());
|
||||
}))
|
||||
{
|
||||
renderChildren = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user