@@ -50,17 +50,30 @@ public function resolve(IPolicyDefinition $definition, PolicyContext $context):
5050 );
5151 }
5252
53- foreach ($ groupLayers as $ layer ) {
54- [$ currentValue , $ currentSourceScope , $ canOverrideBelow , $ visible ] = $ this ->applyLayer (
53+ if ($ definition -> resolutionMode () === ' value_choice ' ) {
54+ [$ currentValue , $ currentSourceScope , $ canOverrideBelow , $ visible ] = $ this ->applyValueChoiceGroupLayers (
5555 $ definition ,
5656 $ resolved ,
57- $ layer ,
57+ $ groupLayers ,
5858 $ context ,
5959 $ currentValue ,
6060 $ currentSourceScope ,
6161 $ canOverrideBelow ,
6262 $ visible ,
6363 );
64+ } else {
65+ foreach ($ groupLayers as $ layer ) {
66+ [$ currentValue , $ currentSourceScope , $ canOverrideBelow , $ visible ] = $ this ->applyLayer (
67+ $ definition ,
68+ $ resolved ,
69+ $ layer ,
70+ $ context ,
71+ $ currentValue ,
72+ $ currentSourceScope ,
73+ $ canOverrideBelow ,
74+ $ visible ,
75+ );
76+ }
6477 }
6578
6679 $ userPreference = $ this ->source ->loadUserPreference ($ policyKey , $ context );
@@ -99,6 +112,62 @@ public function resolve(IPolicyDefinition $definition, PolicyContext $context):
99112 return $ resolved ;
100113 }
101114
115+ /**
116+ * @param list<PolicyLayer> $layers
117+ * @return array{0: mixed, 1: string, 2: bool, 3: bool}
118+ */
119+ private function applyValueChoiceGroupLayers (
120+ IPolicyDefinition $ definition ,
121+ ResolvedPolicy $ resolved ,
122+ array $ layers ,
123+ PolicyContext $ context ,
124+ mixed $ currentValue ,
125+ string $ currentSourceScope ,
126+ bool $ canOverrideBelow ,
127+ bool $ visible ,
128+ ): array {
129+ if ($ layers === [] || !$ visible || !$ canOverrideBelow ) {
130+ return [$ currentValue , $ currentSourceScope , $ canOverrideBelow , $ visible ];
131+ }
132+
133+ $ upstreamAllowedValues = $ resolved ->getAllowedValues ();
134+ $ combinedChoices = [];
135+ $ groupDefaultValues = [];
136+ $ hasVisibleLayer = false ;
137+
138+ foreach ($ layers as $ layer ) {
139+ if (!$ layer ->isVisibleToChild ()) {
140+ continue ;
141+ }
142+
143+ $ hasVisibleLayer = true ;
144+ $ layerChoices = $ this ->resolveValueChoiceLayerChoices ($ definition , $ layer , $ upstreamAllowedValues , $ context );
145+ $ combinedChoices = $ this ->mergeUnionAllowedValues (
146+ $ definition ->allowedValues ($ context ),
147+ $ combinedChoices ,
148+ $ layerChoices ,
149+ );
150+
151+ $ normalizedDefault = $ definition ->normalizeValue ($ layer ->getValue ());
152+ if ($ layer ->getValue () !== null && in_array ($ normalizedDefault , $ combinedChoices , true ) && !in_array ($ normalizedDefault , $ groupDefaultValues , true )) {
153+ $ groupDefaultValues [] = $ normalizedDefault ;
154+ }
155+ }
156+
157+ if (!$ hasVisibleLayer || $ combinedChoices === []) {
158+ return [$ currentValue , $ currentSourceScope , false , $ visible && $ hasVisibleLayer ];
159+ }
160+
161+ $ resolved ->setAllowedValues ($ combinedChoices );
162+
163+ return [
164+ $ this ->pickValueChoiceDefault ($ definition , $ currentValue , $ combinedChoices , $ groupDefaultValues , $ context ),
165+ 'group ' ,
166+ count ($ combinedChoices ) > 1 ,
167+ true ,
168+ ];
169+ }
170+
102171 #[\Override]
103172 /** @param list<IPolicyDefinition> $definitions */
104173 public function resolveMany (array $ definitions , PolicyContext $ context ): array {
@@ -126,13 +195,13 @@ private function applyLayer(
126195 $ visible = $ visible && $ layer ->isVisibleToChild ();
127196 $ resolved ->setAllowedValues ($ this ->mergeAllowedValues ($ resolved ->getAllowedValues (), $ layer ->getAllowedValues ()));
128197
129- if ($ layer ->getValue () !== null && ( $ currentSourceScope === ' system ' || $ canOverrideBelow) ) {
198+ if ($ layer ->getValue () !== null && $ canOverrideBelow ) {
130199 $ currentValue = $ definition ->normalizeValue ($ layer ->getValue ());
131200 $ definition ->validateValue ($ currentValue , $ context );
132201 $ currentSourceScope = $ layer ->getScope ();
133202 }
134203
135- $ canOverrideBelow = $ layer ->isAllowChildOverride ();
204+ $ canOverrideBelow = $ canOverrideBelow && $ layer ->isAllowChildOverride ();
136205
137206 return [$ currentValue , $ currentSourceScope , $ canOverrideBelow , $ visible ];
138207 }
@@ -174,4 +243,90 @@ private function mergeAllowedValues(array $currentAllowedValues, array $layerAll
174243
175244 return array_values (array_intersect ($ currentAllowedValues , $ layerAllowedValues ));
176245 }
246+
247+ /**
248+ * @param list<mixed> $upstreamAllowedValues
249+ * @return list<mixed>
250+ */
251+ private function resolveValueChoiceLayerChoices (
252+ IPolicyDefinition $ definition ,
253+ PolicyLayer $ layer ,
254+ array $ upstreamAllowedValues ,
255+ PolicyContext $ context ,
256+ ): array {
257+ if ($ layer ->isAllowChildOverride ()) {
258+ $ choices = $ layer ->getAllowedValues () === []
259+ ? $ upstreamAllowedValues
260+ : array_values (array_intersect ($ upstreamAllowedValues , $ layer ->getAllowedValues ()));
261+
262+ $ defaultValue = $ definition ->normalizeValue ($ definition ->defaultSystemValue ());
263+ return array_values (array_filter (
264+ $ choices ,
265+ static fn (mixed $ choice ): bool => $ choice !== $ defaultValue ,
266+ ));
267+ }
268+
269+ if ($ layer ->getValue () === null ) {
270+ return [];
271+ }
272+
273+ $ value = $ definition ->normalizeValue ($ layer ->getValue ());
274+ if ($ upstreamAllowedValues !== [] && !in_array ($ value , $ upstreamAllowedValues , true )) {
275+ return [];
276+ }
277+
278+ $ definition ->validateValue ($ value , $ context );
279+ return [$ value ];
280+ }
281+
282+ /**
283+ * @param list<mixed> $canonicalOrder
284+ * @param list<mixed> $currentValues
285+ * @param list<mixed> $newValues
286+ * @return list<mixed>
287+ */
288+ private function mergeUnionAllowedValues (array $ canonicalOrder , array $ currentValues , array $ newValues ): array {
289+ $ merged = [];
290+ foreach ($ canonicalOrder as $ candidate ) {
291+ if ((in_array ($ candidate , $ currentValues , true ) || in_array ($ candidate , $ newValues , true )) && !in_array ($ candidate , $ merged , true )) {
292+ $ merged [] = $ candidate ;
293+ }
294+ }
295+
296+ foreach ([$ currentValues , $ newValues ] as $ values ) {
297+ foreach ($ values as $ candidate ) {
298+ if (!in_array ($ candidate , $ merged , true )) {
299+ $ merged [] = $ candidate ;
300+ }
301+ }
302+ }
303+
304+ return $ merged ;
305+ }
306+
307+ /**
308+ * @param list<mixed> $allowedValues
309+ * @param list<mixed> $groupDefaultValues
310+ */
311+ private function pickValueChoiceDefault (
312+ IPolicyDefinition $ definition ,
313+ mixed $ currentValue ,
314+ array $ allowedValues ,
315+ array $ groupDefaultValues ,
316+ PolicyContext $ context ,
317+ ): mixed {
318+ $ normalizedCurrentValue = $ definition ->normalizeValue ($ currentValue );
319+ $ defaultValue = $ definition ->normalizeValue ($ definition ->defaultSystemValue ());
320+
321+ if (count ($ groupDefaultValues ) === 1 && in_array ($ groupDefaultValues [0 ], $ allowedValues , true )) {
322+ return $ groupDefaultValues [0 ];
323+ }
324+
325+ if ($ normalizedCurrentValue !== $ defaultValue && in_array ($ normalizedCurrentValue , $ allowedValues , true )) {
326+ return $ normalizedCurrentValue ;
327+ }
328+
329+ $ orderedAllowedValues = $ this ->mergeUnionAllowedValues ($ definition ->allowedValues ($ context ), [], $ allowedValues );
330+ return $ orderedAllowedValues [0 ] ?? $ normalizedCurrentValue ;
331+ }
177332}
0 commit comments