1
- using Microsoft . Extensions . DependencyInjection ;
1
+ using Microsoft . Extensions . DependencyInjection ;
2
2
using Umbraco . Cms . Core ;
3
3
using Umbraco . Cms . Core . Models ;
4
4
using Umbraco . Cms . Core . Models . Entities ;
@@ -36,17 +36,17 @@ public UserStartNodeEntitiesService(IEntityService entityService, ICoreScopeProv
36
36
/// <inheritdoc />
37
37
public IEnumerable < UserAccessEntity > RootUserAccessEntities ( UmbracoObjectTypes umbracoObjectType , int [ ] userStartNodeIds )
38
38
{
39
- // root entities for users without root access should include:
39
+ // Root entities for users without root access should include:
40
40
// - the start nodes that are actual root entities (level == 1)
41
41
// - 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")
42
42
IEntitySlim [ ] userStartEntities = userStartNodeIds . Any ( )
43
43
? _entityService . GetAll ( umbracoObjectType , userStartNodeIds ) . ToArray ( )
44
44
: Array . Empty < IEntitySlim > ( ) ;
45
45
46
- // find the start nodes that are at root level (level == 1)
46
+ // Find the start nodes that are at root level (level == 1).
47
47
IEntitySlim [ ] allowedTopmostEntities = userStartEntities . Where ( entity => entity . Level == 1 ) . ToArray ( ) ;
48
48
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.
50
50
var nonAllowedTopmostEntityIds = userStartEntities . Except ( allowedTopmostEntities )
51
51
. Select ( entity => int . TryParse ( entity . Path . Split ( Constants . CharArrays . Comma ) . Skip ( 1 ) . FirstOrDefault ( ) , out var id ) ? id : 0 )
52
52
. Where ( id => id > 0 )
@@ -63,6 +63,7 @@ public IEnumerable<UserAccessEntity> RootUserAccessEntities(UmbracoObjectTypes u
63
63
. ToArray ( ) ;
64
64
}
65
65
66
+ /// <inheritdoc/>
66
67
public IEnumerable < UserAccessEntity > ChildUserAccessEntities ( UmbracoObjectTypes umbracoObjectType , string [ ] userStartNodePaths , Guid parentKey , int skip , int take , Ordering ordering , out long totalItems )
67
68
{
68
69
Attempt < int > parentIdAttempt = _idKeyMap . GetIdForKey ( parentKey , umbracoObjectType ) ;
@@ -83,40 +84,46 @@ public IEnumerable<UserAccessEntity> ChildUserAccessEntities(UmbracoObjectTypes
83
84
IEntitySlim [ ] children ;
84
85
if ( userStartNodePaths . Any ( path => $ "{ parent . Path } ,". StartsWith ( $ "{ path } ,") ) )
85
86
{
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.
87
88
children = _entityService . GetPagedChildren ( parentKey , umbracoObjectType , skip , take , out totalItems , ordering : ordering ) . ToArray ( ) ;
88
89
return ChildUserAccessEntities ( children , userStartNodePaths ) ;
89
90
}
90
91
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 ) ;
100
93
101
94
totalItems = allowedChildIds . Length ;
102
95
if ( allowedChildIds . Length == 0 )
103
96
{
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.
105
98
return [ ] ;
106
99
}
107
100
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.
109
102
IQuery < IUmbracoEntity > query = _scopeProvider . CreateQuery < IUmbracoEntity > ( ) . Where ( x => allowedChildIds . Contains ( x . Id ) ) ;
110
103
children = _entityService . GetPagedChildren ( parentKey , umbracoObjectType , skip , take , out totalItems , query , ordering ) . ToArray ( ) ;
111
104
return ChildUserAccessEntities ( children , userStartNodePaths ) ;
112
105
}
113
106
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
+
114
120
/// <inheritdoc />
115
121
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:
117
124
// - children that are descendant-or-self of a user start node
118
125
// - 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.
120
127
=> candidateChildren . Select ( child =>
121
128
{
122
129
// is descendant-or-self of a start node?
@@ -134,9 +141,55 @@ public IEnumerable<UserAccessEntity> ChildUserAccessEntities(IEnumerable<IEntity
134
141
return null ;
135
142
} ) . WhereNotNull ( ) . ToArray ( ) ;
136
143
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
+
137
189
/// <inheritdoc />
138
190
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:
140
193
// - entities that are descendant-or-self of a user start node as regular entities
141
194
// - all other entities as "no access" entities
142
195
=> entities . Select ( entity => new UserAccessEntity ( entity , IsDescendantOrSelf ( entity , userStartNodePaths ) ) ) . ToArray ( ) ;
0 commit comments