Merge remote-tracking branch 'origin/v8/dev' into netcore/dev

# Conflicts:
#	src/Umbraco.Infrastructure/PropertyEditors/DataValueReferenceFactoryCollection.cs
#	src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs
#	src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs
This commit is contained in:
Bjarke Berg
2020-02-25 11:18:35 +01:00
18 changed files with 432 additions and 46 deletions

View File

@@ -2,7 +2,7 @@
using System.Resources;
[assembly: AssemblyCompany("Umbraco")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2019")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View File

@@ -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);

View File

@@ -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>

View File

@@ -1312,14 +1312,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 });

View File

@@ -16,35 +16,46 @@ namespace Umbraco.Core.PropertyEditors
public IEnumerable<UmbracoEntityReference> GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors)
{
var trackedRelations = new List<UmbracoEntityReference>();
var trackedRelations = new HashSet<UmbracoEntityReference>();
foreach (var p in properties)
{
if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue;
//TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here
if (!p.PropertyType.VariesByNothing()) continue;
var val = p.GetValue(); // get the invariant value
//TODO: We will need to change this once we support tracking via variants/segments
// for now, we are tracking values from ALL variants
var valueEditor = editor.GetValueEditor();
if (valueEditor is IDataValueReference reference)
foreach(var propertyVal in p.Values)
{
var refs = reference.GetReferences(val);
trackedRelations.AddRange(refs);
var val = propertyVal.EditedValue;
var valueEditor = editor.GetValueEditor();
if (valueEditor is IDataValueReference reference)
{
var refs = reference.GetReferences(val);
foreach(var r in refs)
trackedRelations.Add(r);
}
// Loop over collection that may be add to existing property editors
// implementation of GetReferences in IDataValueReference.
// Allows developers to add support for references by a
// package /property editor that did not implement IDataValueReference themselves
foreach (var item in this)
{
// Check if this value reference is for this datatype/editor
// Then call it's GetReferences method - to see if the value stored
// in the dataeditor/property has referecnes to media/content items
if (item.IsForEditor(editor))
trackedRelations.AddRange(item.GetDataValueReference().GetReferences(val));
// Loop over collection that may be add to existing property editors
// implementation of GetReferences in IDataValueReference.
// Allows developers to add support for references by a
// package /property editor that did not implement IDataValueReference themselves
foreach (var item in this)
{
// Check if this value reference is for this datatype/editor
// Then call it's GetReferences method - to see if the value stored
// in the dataeditor/property has referecnes to media/content items
if (item.IsForEditor(editor))
{
foreach(var r in item.GetDataValueReference().GetReferences(val))
trackedRelations.Add(r);
}
}
}
}
return trackedRelations;

View File

@@ -1,12 +1,23 @@
using System;
using HeyRed.MarkdownSharp;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Strings;
using Umbraco.Web.Templates;
namespace Umbraco.Core.PropertyEditors.ValueConverters
{
[DefaultPropertyValueConverter]
public class MarkdownEditorValueConverter : PropertyValueConverterBase
{
private readonly HtmlLocalLinkParser _localLinkParser;
private readonly HtmlUrlParser _urlParser;
public MarkdownEditorValueConverter(HtmlLocalLinkParser localLinkParser, HtmlUrlParser urlParser)
{
_localLinkParser = localLinkParser;
_urlParser = urlParser;
}
public override bool IsConverter(IPublishedPropertyType propertyType)
=> Constants.PropertyEditors.Aliases.MarkdownEditor.Equals(propertyType.EditorAlias);
@@ -15,20 +26,26 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
// PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
=> PropertyCacheLevel.Snapshot;
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
{
// in xml a string is: string
// in the database a string is: string
// default value is: null
return source;
if (source == null) return null;
var sourceString = source.ToString();
// ensures string is parsed for {localLink} and urls are resolved correctly
sourceString = _localLinkParser.EnsureInternalLinks(sourceString, preview);
sourceString = _urlParser.EnsureUrls(sourceString);
return sourceString;
}
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
// convert markup to HTML for frontend rendering.
// source should come from ConvertSource and be a string (or null) already
return new HtmlEncodedString(inter == null ? string.Empty : (string) inter);
var mark = new Markdown();
return new HtmlEncodedString(inter == null ? string.Empty : mark.Transform((string)inter));
}
public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)

View File

@@ -318,6 +318,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))

View File

@@ -0,0 +1,255 @@
using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web.PropertyEditors;
using static Umbraco.Core.Models.Property;
namespace Umbraco.Tests.PropertyEditors
{
[TestFixture]
public class DataValueReferenceFactoryCollectionTests
{
IDataTypeService DataTypeService { get; } = Mock.Of<IDataTypeService>();
private IIOHelper IOHelper { get; } = TestHelper.IOHelper;
ILocalizedTextService LocalizedTextService { get; } = Mock.Of<ILocalizedTextService>();
ILocalizationService LocalizationService { get; } = Mock.Of<ILocalizationService>();
IShortStringHelper ShortStringHelper { get; } = Mock.Of<IShortStringHelper>();
[Test]
public void GetAllReferences_All_Variants_With_IDataValueReferenceFactory()
{
var collection = new DataValueReferenceFactoryCollection(new TestDataValueReferenceFactory().Yield());
// label does not implement IDataValueReference
var labelEditor = new LabelPropertyEditor(
Mock.Of<ILogger>(),
IOHelper,
DataTypeService,
LocalizedTextService,
LocalizationService,
ShortStringHelper
);
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(labelEditor.Yield()));
var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi3 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi4 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var property = new Property(new PropertyType(ShortStringHelper, new DataType(labelEditor))
{
Variations = ContentVariation.CultureAndSegment
})
{
Values = new List<PropertyValue>
{
// Ignored (no culture)
new PropertyValue
{
EditedValue = trackedUdi1
},
new PropertyValue
{
Culture = "en-US",
EditedValue = trackedUdi2
},
new PropertyValue
{
Culture = "en-US",
Segment = "A",
EditedValue = trackedUdi3
},
// Ignored (no culture)
new PropertyValue
{
Segment = "A",
EditedValue = trackedUdi4
},
// duplicate
new PropertyValue
{
Culture = "en-US",
Segment = "B",
EditedValue = trackedUdi3
}
}
};
var properties = new PropertyCollection
{
property
};
var result = collection.GetAllReferences(properties, propertyEditors);
Assert.AreEqual(2, result.Count());
Assert.AreEqual(trackedUdi2, result.ElementAt(0).Udi.ToString());
Assert.AreEqual(trackedUdi3, result.ElementAt(1).Udi.ToString());
}
[Test]
public void GetAllReferences_All_Variants_With_IDataValueReference_Editor()
{
var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
// mediaPicker does implement IDataValueReference
var mediaPicker = new MediaPickerPropertyEditor(
Mock.Of<ILogger>(),
DataTypeService,
LocalizationService,
IOHelper,
ShortStringHelper,
LocalizedTextService
);
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(mediaPicker.Yield()));
var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi3 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi4 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var property = new Property(new PropertyType(ShortStringHelper, new DataType(mediaPicker))
{
Variations = ContentVariation.CultureAndSegment
})
{
Values = new List<PropertyValue>
{
// Ignored (no culture)
new PropertyValue
{
EditedValue = trackedUdi1
},
new PropertyValue
{
Culture = "en-US",
EditedValue = trackedUdi2
},
new PropertyValue
{
Culture = "en-US",
Segment = "A",
EditedValue = trackedUdi3
},
// Ignored (no culture)
new PropertyValue
{
Segment = "A",
EditedValue = trackedUdi4
},
// duplicate
new PropertyValue
{
Culture = "en-US",
Segment = "B",
EditedValue = trackedUdi3
}
}
};
var properties = new PropertyCollection
{
property
};
var result = collection.GetAllReferences(properties, propertyEditors);
Assert.AreEqual(2, result.Count());
Assert.AreEqual(trackedUdi2, result.ElementAt(0).Udi.ToString());
Assert.AreEqual(trackedUdi3, result.ElementAt(1).Udi.ToString());
}
[Test]
public void GetAllReferences_Invariant_With_IDataValueReference_Editor()
{
var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
// mediaPicker does implement IDataValueReference
var mediaPicker = new MediaPickerPropertyEditor(
Mock.Of<ILogger>(),
DataTypeService,
LocalizationService,
IOHelper,
ShortStringHelper,
LocalizedTextService
);
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(mediaPicker.Yield()));
var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi3 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi4 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var property = new Property(new PropertyType(ShortStringHelper, new DataType(mediaPicker))
{
Variations = ContentVariation.Nothing | ContentVariation.Segment
})
{
Values = new List<PropertyValue>
{
new PropertyValue
{
EditedValue = trackedUdi1
},
// Ignored (has culture)
new PropertyValue
{
Culture = "en-US",
EditedValue = trackedUdi2
},
// Ignored (has culture)
new PropertyValue
{
Culture = "en-US",
Segment = "A",
EditedValue = trackedUdi3
},
new PropertyValue
{
Segment = "A",
EditedValue = trackedUdi4
},
// duplicate
new PropertyValue
{
Segment = "B",
EditedValue = trackedUdi4
}
}
};
var properties = new PropertyCollection
{
property
};
var result = collection.GetAllReferences(properties, propertyEditors);
Assert.AreEqual(2, result.Count());
Assert.AreEqual(trackedUdi1, result.ElementAt(0).Udi.ToString());
Assert.AreEqual(trackedUdi4, result.ElementAt(1).Udi.ToString());
}
private class TestDataValueReferenceFactory : IDataValueReferenceFactory
{
public IDataValueReference GetDataValueReference() => new TestMediaDataValueReference();
public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias == Constants.PropertyEditors.Aliases.Label;
private class TestMediaDataValueReference : IDataValueReference
{
public IEnumerable<UmbracoEntityReference> GetReferences(object value)
{
// This is the same as the media picker, it will just try to parse the value directly as a UDI
var asString = value is string str ? str : value?.ToString();
if (string.IsNullOrEmpty(asString)) yield break;
if (UdiParser.TryParse(asString, out var udi))
yield return new UmbracoEntityReference(udi);
}
}
}
}
}

View File

@@ -149,6 +149,7 @@
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\EntityRepositoryTest.cs" />
<Compile Include="UmbracoExamine\ExamineExtensions.cs" />
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollectionTests.cs" />
<Compile Include="PublishedContent\NuCacheChildrenTests.cs" />
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 581 KiB

View File

@@ -9,6 +9,7 @@
ng-model="vm.model"
ng-change="vm.onChange()"
ng-keydown="vm.onKeyDown($event)"
ng-blur="vm.onBlur($event)"
prevent-enter-submit
no-dirty-check>
</ng-form>

View File

@@ -10,7 +10,8 @@
bindings: {
model: "=",
onStartTyping: "&?",
onSearch: "&?"
onSearch: "&?",
onBlur: "&?"
}
});

View File

@@ -147,7 +147,7 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en
_.each($scope.model.value, function (item){
// we must reload the "document" link URLs to match the current editor culture
if (item.udi.indexOf("/document/") > 0) {
if (item.udi && item.udi.indexOf("/document/") > 0) {
item.url = null;
entityResource.getUrlByUdi(item.udi).then(function (data) {
item.url = data;

View File

@@ -14,7 +14,7 @@
vm.userStates = [];
vm.selection = [];
vm.newUser = {};
vm.usersOptions = {filter:null};
vm.usersOptions = {};
vm.userSortData = [
{ label: "Name (A-Z)", key: "Name", direction: "Ascending" },
{ label: "Name (Z-A)", key: "Name", direction: "Descending" },
@@ -112,6 +112,7 @@
vm.selectAll = selectAll;
vm.areAllSelected = areAllSelected;
vm.searchUsers = searchUsers;
vm.onBlurSearch = onBlurSearch;
vm.getFilterName = getFilterName;
vm.setUserStatesFilter = setUserStatesFilter;
vm.setUserGroupFilter = setUserGroupFilter;
@@ -150,10 +151,12 @@
function initViewOptions() {
// Start with default view options.
vm.usersOptions.filter = "";
vm.usersOptions.orderBy = "Name";
vm.usersOptions.orderDirection = "Ascending";
// Update from querystring if available.
initViewOptionFromQueryString("filter");
initViewOptionFromQueryString("orderBy");
initViewOptionFromQueryString("orderDirection");
initViewOptionFromQueryString("pageNumber");
@@ -451,7 +454,8 @@
var search = _.debounce(function () {
$scope.$apply(function () {
changePageNumber(1);
vm.usersOptions.pageNumber = 1;
getUsers();
});
}, 500);
@@ -459,6 +463,10 @@
search();
}
function onBlurSearch() {
updateLocation("filter", vm.usersOptions.filter);
}
function getFilterName(array) {
var name = vm.labels.all;
var found = false;
@@ -547,6 +555,7 @@
}
function updateLocation(key, value) {
$location.search("filter", vm.usersOptions.filter);// update filter, but first when something else requests a url update.
$location.search(key, value);
}
@@ -657,7 +666,8 @@
function usersOptionsAsQueryString() {
var qs = "?orderBy=" + vm.usersOptions.orderBy +
"&orderDirection=" + vm.usersOptions.orderDirection +
"&pageNumber=" + vm.usersOptions.pageNumber;
"&pageNumber=" + vm.usersOptions.pageNumber +
"&filter=" + vm.usersOptions.filter;
qs += addUsersOptionsFilterCollectionToQueryString("userStates", vm.usersOptions.userStates);
qs += addUsersOptionsFilterCollectionToQueryString("userGroups", vm.usersOptions.userGroups);

View File

@@ -28,7 +28,7 @@
</umb-editor-sub-header-section>
<umb-editor-sub-header-section>
<umb-mini-search model="vm.usersOptions.filter" on-search="vm.searchUsers()" on-start-typing="vm.searchUsers()">
<umb-mini-search model="vm.usersOptions.filter" on-search="vm.searchUsers()" on-blur="vm.onBlurSearch()">
</umb-mini-search>
</umb-editor-sub-header-section>

View File

@@ -1671,7 +1671,7 @@ namespace Umbraco.Web.Editors
[HttpPost]
public DomainSave PostSaveLanguageAndDomains(DomainSave model)
{
foreach(var domain in model.Domains)
foreach (var domain in model.Domains)
{
try
{
@@ -2188,7 +2188,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;
}

View File

@@ -7,6 +7,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Routing;
@@ -30,6 +31,7 @@ namespace Umbraco.Web.Models.Mapping
private readonly ILocalizationService _localizationService;
private readonly ILogger _logger;
private readonly IUserService _userService;
private readonly IEntityService _entityService;
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IPublishedUrlProvider _publishedUrlProvider;
private readonly UriUtility _uriUtility;
@@ -38,9 +40,10 @@ namespace Umbraco.Web.Models.Mapping
private readonly ContentBasicSavedStateMapper<ContentPropertyBasic> _basicStateMapper;
private readonly ContentVariantMapper _contentVariantMapper;
public ContentMapDefinition(CommonMapper commonMapper, ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, IContentService contentService, IContentTypeService contentTypeService,
IFileService fileService, IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, ILocalizationService localizationService, ILogger logger,
IUserService userService, IVariationContextAccessor variationContextAccessor, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, UriUtility uriUtility, IPublishedUrlProvider publishedUrlProvider)
IUserService userService, IVariationContextAccessor variationContextAccessor, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, UriUtility uriUtility, IPublishedUrlProvider publishedUrlProvider, IEntityService entityService)
{
_commonMapper = commonMapper;
_cultureDictionary = cultureDictionary;
@@ -53,6 +56,7 @@ namespace Umbraco.Web.Models.Mapping
_localizationService = localizationService;
_logger = logger;
_userService = userService;
_entityService = entityService;
_variationContextAccessor = variationContextAccessor;
_uriUtility = uriUtility;
_publishedUrlProvider = publishedUrlProvider;
@@ -90,7 +94,7 @@ namespace Umbraco.Web.Models.Mapping
target.Icon = source.ContentType.Icon;
target.Id = source.Id;
target.IsBlueprint = source.Blueprint;
target.IsChildOfListView = DetermineIsChildOfListView(source);
target.IsChildOfListView = DetermineIsChildOfListView(source, context);
target.IsContainer = source.ContentType.IsContainer;
target.IsElement = source.ContentType.IsElement;
target.Key = source.Key;
@@ -221,13 +225,66 @@ namespace Umbraco.Web.Models.Mapping
return source.CultureInfos.TryGetValue(culture, out var name) && !name.Name.IsNullOrWhiteSpace() ? name.Name : $"({source.Name})";
}
private bool DetermineIsChildOfListView(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)
{
var culture = context.GetCulture() ?? string.Empty;

View File

@@ -381,7 +381,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;
}