1- using Microsoft . Extensions . DependencyInjection ;
1+ using Microsoft . Extensions . DependencyInjection ;
22using Umbraco . Cms . Core ;
33using Umbraco . Cms . Core . Models ;
44using Umbraco . Cms . Core . Models . Entities ;
@@ -36,17 +36,17 @@ public UserStartNodeEntitiesService(IEntityService entityService, ICoreScopeProv
3636 /// <inheritdoc />
3737 public IEnumerable < UserAccessEntity > RootUserAccessEntities ( UmbracoObjectTypes umbracoObjectType , int [ ] userStartNodeIds )
3838 {
39- // root entities for users without root access should include:
39+ // Root entities for users without root access should include:
4040 // - the start nodes that are actual root entities (level == 1)
4141 // - the root level ancestors to the rest of the start nodes (required for browsing to the actual start nodes - will be marked as "no access")
4242 IEntitySlim [ ] userStartEntities = userStartNodeIds . Any ( )
4343 ? _entityService . GetAll ( umbracoObjectType , userStartNodeIds ) . ToArray ( )
4444 : Array . Empty < IEntitySlim > ( ) ;
4545
46- // find the start nodes that are at root level (level == 1)
46+ // Find the start nodes that are at root level (level == 1).
4747 IEntitySlim [ ] allowedTopmostEntities = userStartEntities . Where ( entity => entity . Level == 1 ) . ToArray ( ) ;
4848
49- // find the root level ancestors of the rest of the start nodes, and add those as well
49+ // Find the root level ancestors of the rest of the start nodes, and add those as well.
5050 var nonAllowedTopmostEntityIds = userStartEntities . Except ( allowedTopmostEntities )
5151 . Select ( entity => int . TryParse ( entity . Path . Split ( Constants . CharArrays . Comma ) . Skip ( 1 ) . FirstOrDefault ( ) , out var id ) ? id : 0 )
5252 . Where ( id => id > 0 )
@@ -63,6 +63,7 @@ public IEnumerable<UserAccessEntity> RootUserAccessEntities(UmbracoObjectTypes u
6363 . ToArray ( ) ;
6464 }
6565
66+ /// <inheritdoc/>
6667 public IEnumerable < UserAccessEntity > ChildUserAccessEntities ( UmbracoObjectTypes umbracoObjectType , string [ ] userStartNodePaths , Guid parentKey , int skip , int take , Ordering ordering , out long totalItems )
6768 {
6869 Attempt < int > parentIdAttempt = _idKeyMap . GetIdForKey ( parentKey , umbracoObjectType ) ;
@@ -83,40 +84,46 @@ public IEnumerable<UserAccessEntity> ChildUserAccessEntities(UmbracoObjectTypes
8384 IEntitySlim [ ] children ;
8485 if ( userStartNodePaths . Any ( path => $ "{ parent . Path } ,". StartsWith ( $ "{ path } ,") ) )
8586 {
86- // the requested parent is one of the user start nodes (or a descendant of one), all children are by definition allowed
87+ // The requested parent is one of the user start nodes (or a descendant of one), all children are by definition allowed.
8788 children = _entityService . GetPagedChildren ( parentKey , umbracoObjectType , skip , take , out totalItems , ordering : ordering ) . ToArray ( ) ;
8889 return ChildUserAccessEntities ( children , userStartNodePaths ) ;
8990 }
9091
91- // if one or more of the user start nodes are descendants of the requested parent, find the "next child IDs" in those user start node paths
92- // - e.g. given the user start node path "-1,2,3,4,5", if the requested parent ID is 3, the "next child ID" is 4.
93- var userStartNodePathIds = userStartNodePaths . Select ( path => path . Split ( Constants . CharArrays . Comma ) . Select ( int . Parse ) . ToArray ( ) ) . ToArray ( ) ;
94- var allowedChildIds = userStartNodePathIds
95- . Where ( ids => ids . Contains ( parentId ) )
96- // given the previous checks, the parent ID can never be the last in the user start node path, so this is safe
97- . Select ( ids => ids [ ids . IndexOf ( parentId ) + 1 ] )
98- . Distinct ( )
99- . ToArray ( ) ;
92+ int [ ] allowedChildIds = GetAllowedIds ( userStartNodePaths , parentId ) ;
10093
10194 totalItems = allowedChildIds . Length ;
10295 if ( allowedChildIds . Length == 0 )
10396 {
104- // the requested parent is outside the scope of any user start nodes
97+ // The requested parent is outside the scope of any user start nodes.
10598 return [ ] ;
10699 }
107100
108- // even though we know the IDs of the allowed child entities to fetch, we still use a Query to yield correctly sorted children
101+ // Even though we know the IDs of the allowed child entities to fetch, we still use a Query to yield correctly sorted children.
109102 IQuery < IUmbracoEntity > query = _scopeProvider . CreateQuery < IUmbracoEntity > ( ) . Where ( x => allowedChildIds . Contains ( x . Id ) ) ;
110103 children = _entityService . GetPagedChildren ( parentKey , umbracoObjectType , skip , take , out totalItems , query , ordering ) . ToArray ( ) ;
111104 return ChildUserAccessEntities ( children , userStartNodePaths ) ;
112105 }
113106
107+ private static int [ ] GetAllowedIds ( string [ ] userStartNodePaths , int parentId )
108+ {
109+ // If one or more of the user start nodes are descendants of the requested parent, find the "next child IDs" in those user start node paths
110+ // that are the final entries in the path.
111+ // E.g. given the user start node path "-1,2,3,4,5", if the requested parent ID is 3, the "next child ID" is 4.
112+ var userStartNodePathIds = userStartNodePaths . Select ( path => path . Split ( Constants . CharArrays . Comma ) . Select ( int . Parse ) . ToArray ( ) ) . ToArray ( ) ;
113+ return userStartNodePathIds
114+ . Where ( ids => ids . Contains ( parentId ) )
115+ . Select ( ids => ids [ ids . IndexOf ( parentId ) + 1 ] ) // Given the previous checks, the parent ID can never be the last in the user start node path, so this is safe
116+ . Distinct ( )
117+ . ToArray ( ) ;
118+ }
119+
114120 /// <inheritdoc />
115121 public IEnumerable < UserAccessEntity > ChildUserAccessEntities ( IEnumerable < IEntitySlim > candidateChildren , string [ ] userStartNodePaths )
116- // child entities for users without root access should include:
122+
123+ // Child or sibling entities for users without root access should include:
117124 // - children that are descendant-or-self of a user start node
118125 // - children that are ancestors of a user start node (required for browsing to the actual start nodes - will be marked as "no access")
119- // all other candidate children should be discarded
126+ // All other candidate children should be discarded.
120127 => candidateChildren . Select ( child =>
121128 {
122129 // is descendant-or-self of a start node?
@@ -134,9 +141,55 @@ public IEnumerable<UserAccessEntity> ChildUserAccessEntities(IEnumerable<IEntity
134141 return null ;
135142 } ) . WhereNotNull ( ) . ToArray ( ) ;
136143
144+ /// <inheritdoc />
145+ public IEnumerable < UserAccessEntity > SiblingUserAccessEntities ( UmbracoObjectTypes umbracoObjectType , string [ ] userStartNodePaths , Guid targetKey , int before , int after , Ordering ordering )
146+ {
147+ Attempt < int > targetIdAttempt = _idKeyMap . GetIdForKey ( targetKey , umbracoObjectType ) ;
148+ if ( targetIdAttempt . Success is false )
149+ {
150+ return [ ] ;
151+ }
152+
153+ var targetId = targetIdAttempt . Result ;
154+ IEntitySlim ? target = _entityService . Get ( targetId ) ;
155+ if ( target is null )
156+ {
157+ return [ ] ;
158+ }
159+
160+ IEntitySlim [ ] siblings ;
161+
162+ IEntitySlim ? targetParent = _entityService . Get ( target . ParentId ) ;
163+ if ( targetParent is null ) // Even if the parent is the root, we still expect to get a value here.
164+ {
165+ return [ ] ;
166+ }
167+
168+ if ( userStartNodePaths . Any ( path => $ "{ targetParent ? . Path } ,". StartsWith ( $ "{ path } ,") ) )
169+ {
170+ // The requested parent of the target is one of the user start nodes (or a descendant of one), all siblings are by definition allowed.
171+ siblings = _entityService . GetSiblings ( targetKey , umbracoObjectType , before , after , ordering : ordering ) . ToArray ( ) ;
172+ return ChildUserAccessEntities ( siblings , userStartNodePaths ) ;
173+ }
174+
175+ int [ ] allowedSiblingIds = GetAllowedIds ( userStartNodePaths , targetParent . Id ) ;
176+
177+ if ( allowedSiblingIds . Length == 0 )
178+ {
179+ // The requested target is outside the scope of any user start nodes.
180+ return [ ] ;
181+ }
182+
183+ // Even though we know the IDs of the allowed sibling entities to fetch, we still use a Query to yield correctly sorted children.
184+ IQuery < IUmbracoEntity > query = _scopeProvider . CreateQuery < IUmbracoEntity > ( ) . Where ( x => allowedSiblingIds . Contains ( x . Id ) ) ;
185+ siblings = _entityService . GetSiblings ( targetKey , umbracoObjectType , before , after , query , ordering ) . ToArray ( ) ;
186+ return ChildUserAccessEntities ( siblings , userStartNodePaths ) ;
187+ }
188+
137189 /// <inheritdoc />
138190 public IEnumerable < UserAccessEntity > UserAccessEntities ( IEnumerable < IEntitySlim > entities , string [ ] userStartNodePaths )
139- // entities for users without root access should include:
191+
192+ // Entities for users without root access should include:
140193 // - entities that are descendant-or-self of a user start node as regular entities
141194 // - all other entities as "no access" entities
142195 => entities . Select ( entity => new UserAccessEntity ( entity , IsDescendantOrSelf ( entity , userStartNodePaths ) ) ) . ToArray ( ) ;
0 commit comments