1010using Umbraco . Cms . Core . Serialization ;
1111using Umbraco . Cms . Core . Services ;
1212using Umbraco . Cms . Core . Strings ;
13+ using Umbraco . Extensions ;
1314
1415namespace Umbraco . Cms . Core . PropertyEditors ;
1516
@@ -291,6 +292,11 @@ private void MapBlockItemDataFromEditor(List<BlockItemData> items)
291292 bool canUpdateInvariantData ,
292293 HashSet < string > allowedCultures )
293294 {
295+ if ( canUpdateInvariantData is false && targetValue is null )
296+ {
297+ return sourceValue ;
298+ }
299+
294300 BlockEditorData < TValue , TLayout > ? source = BlockEditorValues . DeserializeAndClean ( sourceValue ) ;
295301 BlockEditorData < TValue , TLayout > ? target = BlockEditorValues . DeserializeAndClean ( targetValue ) ;
296302
@@ -310,48 +316,110 @@ private void MapBlockItemDataFromEditor(List<BlockItemData> items)
310316 bool canUpdateInvariantData ,
311317 HashSet < string > allowedCultures )
312318 {
313- source = UpdateSourceInvariantData ( source , target , canUpdateInvariantData ) ;
319+ var mergedInvariant = UpdateSourceInvariantData ( source , target , canUpdateInvariantData ) ;
314320
315- if ( source is null && target is null )
321+ // if the structure (invariant) is not defined after merger, the target content does not matter
322+ if ( mergedInvariant is null )
316323 {
317324 return null ;
318325 }
319326
320- if ( source is null && target ? . Layout is not null )
321- {
322- source = new BlockEditorData < TValue , TLayout > ( [ ] , CreateWithLayout ( target . Layout ) ) ;
323- }
324- else if ( target is null && source ? . Layout is not null )
327+ // since we merged the invariant data (layout) before we get to this point
328+ // we just need an empty valid object to run comparisons at this point
329+ if ( source is null )
325330 {
326- target = new BlockEditorData < TValue , TLayout > ( [ ] , CreateWithLayout ( source . Layout ) ) ;
331+ source = new BlockEditorData < TValue , TLayout > ( [ ] , new TValue ( ) ) ;
327332 }
328333
329- // at this point the layout should have been merged or fallback created
330- if ( source is null || target is null )
331- {
332- throw new ArgumentException ( "invalid sourceValue or targetValue" ) ;
333- }
334+ // update the target with the merged invariant
335+ target ! . BlockValue . Layout = mergedInvariant . BlockValue . Layout ;
334336
335337 // remove all the blocks that are no longer part of the layout
336338 target . BlockValue . ContentData . RemoveAll ( contentBlock =>
337339 target . Layout ! . Any ( layoutItem => layoutItem . ReferencesContent ( contentBlock . Key ) ) is false ) ;
340+ // remove any exposes that no longer have content assigned to them
341+ target . BlockValue . Expose . RemoveAll ( expose => target . BlockValue . ContentData . Any ( data => data . Key == expose . ContentKey ) is false ) ;
338342
339343 target . BlockValue . SettingsData . RemoveAll ( settingsBlock =>
340344 target . Layout ! . Any ( layoutItem => layoutItem . ReferencesSetting ( settingsBlock . Key ) ) is false ) ;
341345
342346 CleanupVariantValues ( source . BlockValue . ContentData , target . BlockValue . ContentData , canUpdateInvariantData , allowedCultures ) ;
343347 CleanupVariantValues ( source . BlockValue . SettingsData , target . BlockValue . SettingsData , canUpdateInvariantData , allowedCultures ) ;
344348
349+ // every source block value for a culture that is not allowed to be edited should be present on the target
350+ RestoreMissingValues (
351+ source . BlockValue . ContentData ,
352+ target . BlockValue . ContentData ,
353+ mergedInvariant . Layout ! ,
354+ ( layoutItem , itemData ) => layoutItem . ContentKey == itemData . Key ,
355+ canUpdateInvariantData ,
356+ allowedCultures ) ;
357+ RestoreMissingValues (
358+ source . BlockValue . SettingsData ,
359+ target . BlockValue . SettingsData ,
360+ mergedInvariant . Layout ! ,
361+ ( layoutItem , itemData ) => layoutItem . SettingsKey == itemData . Key ,
362+ canUpdateInvariantData ,
363+ allowedCultures ) ;
364+
365+ // update the expose list from source for any blocks that were restored
366+ var missingSourceExposes =
367+ source . BlockValue . Expose . Where ( sourceExpose =>
368+ target . BlockValue . Expose . Any ( targetExpose => targetExpose . ContentKey == sourceExpose . ContentKey ) is false
369+ && target . BlockValue . ContentData . Any ( data => data . Key == sourceExpose . ContentKey ) ) . ToList ( ) ;
370+ foreach ( BlockItemVariation missingSourceExpose in missingSourceExposes )
371+ {
372+ target . BlockValue . Expose . Add ( missingSourceExpose ) ;
373+ }
374+
345375 return target . BlockValue ;
346376 }
347377
378+ private void RestoreMissingValues (
379+ List < BlockItemData > sourceBlockItemData ,
380+ List < BlockItemData > targetBlockItemData ,
381+ IEnumerable < TLayout > mergedLayout ,
382+ Func < TLayout , BlockItemData , bool > relevantBlockItemMatcher ,
383+ bool canUpdateInvariantData ,
384+ HashSet < string > allowedCultures )
385+ {
386+ IEnumerable < BlockItemData > blockItemsToCheck = sourceBlockItemData . Where ( itemData =>
387+ mergedLayout . Any ( layoutItem => relevantBlockItemMatcher ( layoutItem , itemData ) ) ) ;
388+ foreach ( BlockItemData blockItemData in blockItemsToCheck )
389+ {
390+ var relevantValues = blockItemData . Values . Where ( value =>
391+ ( value . Culture is null && canUpdateInvariantData is false )
392+ || ( value . Culture is not null && allowedCultures . Contains ( value . Culture ) is false ) ) . ToList ( ) ;
393+ if ( relevantValues . Count < 1 )
394+ {
395+ continue ;
396+ }
397+
398+ BlockItemData targetBlockData =
399+ targetBlockItemData . FirstOrDefault ( itemData => itemData . Key == blockItemData . Key )
400+ ?? new BlockItemData ( blockItemData . Key , blockItemData . ContentTypeKey , blockItemData . ContentTypeAlias ) ;
401+ foreach ( BlockPropertyValue missingValue in relevantValues . Where ( value => targetBlockData . Values . Any ( targetValue =>
402+ targetValue . Alias == value . Alias
403+ && targetValue . Culture == value . Culture
404+ && targetValue . Segment == value . Segment ) is false ) )
405+ {
406+ targetBlockData . Values . Add ( missingValue ) ;
407+ }
408+
409+ if ( targetBlockItemData . Any ( existingBlockItemData => existingBlockItemData . Key == targetBlockData . Key ) is false )
410+ {
411+ targetBlockItemData . Add ( blockItemData ) ;
412+ }
413+ }
414+ }
415+
348416 private void CleanupVariantValues (
349417 List < BlockItemData > sourceBlockItems ,
350418 List < BlockItemData > targetBlockItems ,
351419 bool canUpdateInvariantData ,
352420 HashSet < string > allowedCultures )
353421 {
354- // merge the source values into the target values for culture
422+ // merge the source values into the target values per culture
355423 foreach ( BlockItemData targetBlockItem in targetBlockItems )
356424 {
357425 BlockItemData ? sourceBlockItem = sourceBlockItems . FirstOrDefault ( i => i . Key == targetBlockItem . Key ) ;
0 commit comments