2024-02-21 09:05:44 +01:00
using Umbraco.Cms.Core.Cache ;
2023-10-31 12:52:35 +01:00
using Umbraco.Cms.Core.IO ;
using Umbraco.Cms.Core.Models ;
using Umbraco.Cms.Core.Models.Blocks ;
using Umbraco.Cms.Core.Models.Editors ;
2024-09-30 07:01:18 +02:00
using Umbraco.Cms.Core.PropertyEditors.ValueConverters ;
2023-10-31 12:52:35 +01:00
using Umbraco.Cms.Core.Serialization ;
using Umbraco.Cms.Core.Services ;
using Umbraco.Cms.Core.Strings ;
2025-04-21 10:30:12 +02:00
using Umbraco.Extensions ;
2023-10-31 12:52:35 +01:00
namespace Umbraco.Cms.Core.PropertyEditors ;
2024-07-26 07:16:08 +01:00
public abstract class BlockValuePropertyValueEditorBase < TValue , TLayout > : DataValueEditor , IDataValueReference , IDataValueTags
2023-11-02 11:55:12 +01:00
where TValue : BlockValue < TLayout > , new ( )
where TLayout : class , IBlockLayoutItem , new ( )
2023-10-31 12:52:35 +01:00
{
2024-02-21 09:05:44 +01:00
private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache ;
2023-10-31 12:52:35 +01:00
private readonly PropertyEditorCollection _propertyEditors ;
2024-09-30 07:01:18 +02:00
private readonly IJsonSerializer _jsonSerializer ;
2024-01-23 10:15:49 +01:00
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactoryCollection ;
2024-09-30 07:01:18 +02:00
private readonly BlockEditorVarianceHandler _blockEditorVarianceHandler ;
private BlockEditorValues < TValue , TLayout > ? _blockEditorValues ;
2024-10-15 12:03:05 +02:00
private readonly ILanguageService _languageService ;
2023-10-31 12:52:35 +01:00
2024-10-15 12:03:05 +02:00
protected BlockValuePropertyValueEditorBase (
PropertyEditorCollection propertyEditors ,
IDataTypeConfigurationCache dataTypeConfigurationCache ,
IShortStringHelper shortStringHelper ,
IJsonSerializer jsonSerializer ,
2024-09-30 07:01:18 +02:00
DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection ,
2024-10-15 12:03:05 +02:00
BlockEditorVarianceHandler blockEditorVarianceHandler ,
2024-11-07 09:12:04 +01:00
ILanguageService languageService ,
IIOHelper ioHelper ,
DataEditorAttribute attribute )
: base ( shortStringHelper , jsonSerializer , ioHelper , attribute )
2023-10-31 12:52:35 +01:00
{
_propertyEditors = propertyEditors ;
2024-02-21 09:05:44 +01:00
_dataTypeConfigurationCache = dataTypeConfigurationCache ;
2024-09-30 07:01:18 +02:00
_jsonSerializer = jsonSerializer ;
2024-01-23 10:15:49 +01:00
_dataValueReferenceFactoryCollection = dataValueReferenceFactoryCollection ;
2024-09-30 07:01:18 +02:00
_blockEditorVarianceHandler = blockEditorVarianceHandler ;
2024-10-15 12:03:05 +02:00
_languageService = languageService ;
2023-10-31 12:52:35 +01:00
}
/// <inheritdoc />
public abstract IEnumerable < UmbracoEntityReference > GetReferences ( object? value ) ;
2024-09-30 07:01:18 +02:00
protected abstract TValue CreateWithLayout ( IEnumerable < TLayout > layout ) ;
protected BlockEditorValues < TValue , TLayout > BlockEditorValues
{
get = > _blockEditorValues ? ? throw new NullReferenceException ( $"The property {nameof(BlockEditorValues)} must be initialized at value editor construction" ) ;
set = > _blockEditorValues = value ;
}
2023-11-02 11:55:12 +01:00
protected IEnumerable < UmbracoEntityReference > GetBlockValueReferences ( TValue blockValue )
2023-10-31 12:52:35 +01:00
{
2024-01-23 18:56:47 +01:00
var result = new HashSet < UmbracoEntityReference > ( ) ;
2024-09-30 07:01:18 +02:00
BlockPropertyValue [ ] blockPropertyValues = blockValue . ContentData . Concat ( blockValue . SettingsData )
. SelectMany ( x = > x . Values ) . ToArray ( ) ;
if ( blockPropertyValues . Any ( p = > p . PropertyType is null ) )
{
throw new ArgumentException ( "One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to find references within them." , nameof ( blockValue ) ) ;
}
foreach ( IGrouping < string , object? > valuesByPropertyEditorAlias in blockPropertyValues . GroupBy ( x = > x . PropertyType ! . PropertyEditorAlias , x = > x . Value ) )
2023-10-31 12:52:35 +01:00
{
2024-01-23 10:15:49 +01:00
if ( ! _propertyEditors . TryGet ( valuesByPropertyEditorAlias . Key , out IDataEditor ? dataEditor ) )
2023-10-31 12:52:35 +01:00
{
2024-01-23 10:15:49 +01:00
continue ;
}
2023-10-31 12:52:35 +01:00
2025-05-19 10:54:22 +02:00
var distinctValues = valuesByPropertyEditorAlias . Distinct ( ) . ToArray ( ) ;
2023-10-31 12:52:35 +01:00
2024-01-23 10:15:49 +01:00
if ( dataEditor . GetValueEditor ( ) is IDataValueReference reference )
{
2025-05-19 10:54:22 +02:00
foreach ( UmbracoEntityReference value in distinctValues . SelectMany ( reference . GetReferences ) )
2023-10-31 12:52:35 +01:00
{
2024-01-23 18:56:47 +01:00
result . Add ( value ) ;
2023-10-31 12:52:35 +01:00
}
2024-01-23 10:15:49 +01:00
}
2023-10-31 12:52:35 +01:00
2025-05-19 10:54:22 +02:00
IEnumerable < UmbracoEntityReference > references = _dataValueReferenceFactoryCollection . GetReferences ( dataEditor , distinctValues ) ;
2023-10-31 12:52:35 +01:00
2024-01-23 10:15:49 +01:00
foreach ( UmbracoEntityReference value in references )
{
2024-01-23 18:56:47 +01:00
result . Add ( value ) ;
2023-10-31 12:52:35 +01:00
}
}
return result ;
}
/// <inheritdoc />
public abstract IEnumerable < ITag > GetTags ( object? value , object? dataTypeConfiguration , int? languageId ) ;
2023-11-02 11:55:12 +01:00
protected IEnumerable < ITag > GetBlockValueTags ( TValue blockValue , int? languageId )
2023-10-31 12:52:35 +01:00
{
var result = new List < ITag > ( ) ;
2024-02-21 09:05:44 +01:00
2023-10-31 12:52:35 +01:00
// loop through all content and settings data
foreach ( BlockItemData row in blockValue . ContentData . Concat ( blockValue . SettingsData ) )
{
2024-09-30 07:01:18 +02:00
foreach ( BlockPropertyValue blockPropertyValue in row . Values )
2023-10-31 12:52:35 +01:00
{
2024-09-30 07:01:18 +02:00
if ( blockPropertyValue . PropertyType is null )
{
throw new ArgumentException ( "One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to find tags within them." , nameof ( blockValue ) ) ;
}
IDataEditor ? propEditor = _propertyEditors [ blockPropertyValue . PropertyType . PropertyEditorAlias ] ;
2023-10-31 12:52:35 +01:00
IDataValueEditor ? valueEditor = propEditor ? . GetValueEditor ( ) ;
if ( valueEditor is not IDataValueTags tagsProvider )
{
continue ;
}
2024-09-30 07:01:18 +02:00
object? configuration = _dataTypeConfigurationCache . GetConfiguration ( blockPropertyValue . PropertyType . DataTypeKey ) ;
2023-10-31 12:52:35 +01:00
2024-10-15 12:03:05 +02:00
var tagLanguageId = blockPropertyValue . Culture is not null
? _languageService . GetAsync ( blockPropertyValue . Culture ) . GetAwaiter ( ) . GetResult ( ) ? . Id
: languageId ;
result . AddRange ( tagsProvider . GetTags ( blockPropertyValue . Value , configuration , tagLanguageId ) ) ;
2023-10-31 12:52:35 +01:00
}
}
return result ;
}
2025-06-30 14:21:10 +03:00
protected void MapBlockValueFromEditor ( TValue ? editedBlockValue , TValue ? currentBlockValue , Guid contentKey )
{
MapBlockItemDataFromEditor (
editedBlockValue ? . ContentData ? ? [ ] ,
currentBlockValue ? . ContentData ? ? [ ] ,
contentKey ) ;
MapBlockItemDataFromEditor (
editedBlockValue ? . SettingsData ? ? [ ] ,
currentBlockValue ? . SettingsData ? ? [ ] ,
contentKey ) ;
}
private void MapBlockItemDataFromEditor ( List < BlockItemData > editedItems , List < BlockItemData > currentItems , Guid contentKey )
{
// Create mapping between edited and current block items.
IEnumerable < BlockStateMapping < BlockItemData > > itemsMapping = GetBlockStatesMapping ( editedItems , currentItems , ( mapping , current ) = > mapping . Edited ? . Key = = current . Key ) ;
foreach ( BlockStateMapping < BlockItemData > itemMapping in itemsMapping )
{
// Create mapping between edited and current block item values.
IEnumerable < BlockStateMapping < BlockPropertyValue > > valuesMapping = GetBlockStatesMapping ( itemMapping . Edited ? . Values , itemMapping . Current ? . Values , ( mapping , current ) = > mapping . Edited ? . Alias = = current . Alias ) ;
foreach ( BlockStateMapping < BlockPropertyValue > valueMapping in valuesMapping )
{
BlockPropertyValue ? editedValue = valueMapping . Edited ;
BlockPropertyValue ? currentValue = valueMapping . Current ;
IPropertyType propertyType = editedValue ? . PropertyType
? ? currentValue ? . PropertyType
? ? throw new ArgumentException ( "One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to map them from editor." , nameof ( editedItems ) ) ;
// Lookup the property editor.
IDataEditor ? propertyEditor = _propertyEditors [ propertyType . PropertyEditorAlias ] ;
if ( propertyEditor is null )
{
continue ;
}
// Fetch the property types prevalue.
var configuration = _dataTypeConfigurationCache . GetConfiguration ( propertyType . DataTypeKey ) ;
// Create a real content property data object.
var propertyData = new ContentPropertyData ( editedValue ? . Value , configuration )
{
ContentKey = contentKey ,
PropertyTypeKey = propertyType . Key ,
} ;
// Get the property editor to do it's conversion.
IDataValueEditor valueEditor = propertyEditor . GetValueEditor ( ) ;
var newValue = valueEditor . FromEditor ( propertyData , currentValue ? . Value ) ;
// Update the raw value since this is what will get serialized out.
if ( editedValue ! = null )
{
editedValue . Value = newValue ;
}
}
}
}
private sealed class BlockStateMapping < T >
2023-10-31 12:52:35 +01:00
{
2025-06-30 14:21:10 +03:00
public T ? Edited { get ; set ; }
public T ? Current { get ; set ; }
}
private static IEnumerable < BlockStateMapping < T > > GetBlockStatesMapping < T > ( IList < T > ? editedItems , IList < T > ? currentItems , Func < BlockStateMapping < T > , T , bool > condition )
{
// filling with edited items first
List < BlockStateMapping < T > > mapping = editedItems ?
. Select ( editedItem = > new BlockStateMapping < T >
{
Current = default ,
Edited = editedItem ,
} )
. ToList ( )
? ? [ ] ;
if ( currentItems is null )
{
return mapping ;
}
// then adding current items
foreach ( T currentItem in currentItems )
{
BlockStateMapping < T > ? mappingItem = mapping . FirstOrDefault ( x = > condition ( x , currentItem ) ) ;
if ( mappingItem = = null ) // if there is no edited item, then adding just current
{
mapping . Add ( new BlockStateMapping < T >
{
Current = currentItem ,
Edited = default ,
} ) ;
}
else
{
mappingItem . Current = currentItem ;
}
}
return mapping ;
2023-10-31 12:52:35 +01:00
}
2024-09-30 07:01:18 +02:00
protected void MapBlockValueToEditor ( IProperty property , TValue blockValue , string? culture , string? segment )
2023-10-31 12:52:35 +01:00
{
2024-09-30 07:01:18 +02:00
MapBlockItemDataToEditor ( property , blockValue . ContentData , culture , segment ) ;
MapBlockItemDataToEditor ( property , blockValue . SettingsData , culture , segment ) ;
_blockEditorVarianceHandler . AlignExposeVariance ( blockValue ) ;
2023-10-31 12:52:35 +01:00
}
2024-08-15 07:11:17 +02:00
protected IEnumerable < Guid > ConfiguredElementTypeKeys ( IBlockConfiguration configuration )
{
yield return configuration . ContentElementTypeKey ;
if ( configuration . SettingsElementTypeKey is not null )
{
yield return configuration . SettingsElementTypeKey . Value ;
}
}
2024-09-30 07:01:18 +02:00
private void MapBlockItemDataToEditor ( IProperty property , List < BlockItemData > items , string? culture , string? segment )
2023-10-31 12:52:35 +01:00
{
2024-09-30 07:01:18 +02:00
var valueEditorsByKey = new Dictionary < Guid , IDataValueEditor > ( ) ;
2023-10-31 12:52:35 +01:00
2024-09-30 07:01:18 +02:00
foreach ( BlockItemData item in items )
2023-10-31 12:52:35 +01:00
{
2025-03-26 11:27:23 +01:00
// if changes were made to the element type variations, we need those changes reflected in the block property values.
// for regular content this happens when a content type is saved (copies of property values are created in the DB),
// but for local block level properties we don't have that kind of handling, so we to do it manually.
// to be friendly we'll map "formerly invariant properties" to the default language ISO code instead of performing a
// hard reset of the property values (which would likely be the most correct thing to do from a data point of view).
item . Values = _blockEditorVarianceHandler . AlignPropertyVarianceAsync ( item . Values , culture ) . GetAwaiter ( ) . GetResult ( ) ;
2024-09-30 07:01:18 +02:00
foreach ( BlockPropertyValue blockPropertyValue in item . Values )
2023-10-31 12:52:35 +01:00
{
2024-09-30 07:01:18 +02:00
IPropertyType ? propertyType = blockPropertyValue . PropertyType ;
if ( propertyType is null )
2023-10-31 12:52:35 +01:00
{
2024-09-30 07:01:18 +02:00
throw new ArgumentException ( "One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to map them to editor." , nameof ( items ) ) ;
}
IDataEditor ? propertyEditor = _propertyEditors [ propertyType . PropertyEditorAlias ] ;
if ( propertyEditor is null )
{
// leave the current block property value as-is - will be used to render a fallback output in the client
2023-10-31 12:52:35 +01:00
continue ;
}
2024-09-30 07:01:18 +02:00
if ( ! valueEditorsByKey . TryGetValue ( propertyType . DataTypeKey , out IDataValueEditor ? valueEditor ) )
2023-10-31 12:52:35 +01:00
{
2024-09-30 07:01:18 +02:00
var configuration = _dataTypeConfigurationCache . GetConfiguration ( propertyType . DataTypeKey ) ;
valueEditor = propertyEditor . GetValueEditor ( configuration ) ;
2023-10-31 12:52:35 +01:00
2024-09-30 07:01:18 +02:00
valueEditorsByKey . Add ( propertyType . DataTypeKey , valueEditor ) ;
2023-10-31 12:52:35 +01:00
}
2024-09-30 07:01:18 +02:00
var tempProp = new Property ( propertyType ) ;
tempProp . SetValue ( blockPropertyValue . Value , blockPropertyValue . Culture , blockPropertyValue . Segment ) ;
var editorValue = valueEditor . ToEditor ( tempProp , blockPropertyValue . Culture , blockPropertyValue . Segment ) ;
2023-10-31 12:52:35 +01:00
// update the raw value since this is what will get serialized out
2024-09-30 07:01:18 +02:00
blockPropertyValue . Value = editorValue ;
2023-10-31 12:52:35 +01:00
}
}
}
2025-01-06 14:58:00 +01:00
/// <summary>
/// Updates the invariant data in the source with the invariant data in the value if allowed
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
/// <param name="canUpdateInvariantData"></param>
/// <returns></returns>
internal virtual BlockEditorData < TValue , TLayout > ? UpdateSourceInvariantData ( BlockEditorData < TValue , TLayout > ? source , BlockEditorData < TValue , TLayout > ? target , bool canUpdateInvariantData )
{
if ( source is null & & target is null )
{
return null ;
}
if ( source is null )
{
return MergeNewInvariant ( target ! , canUpdateInvariantData ) ;
}
if ( target is null )
{
return MergeRemovalInvariant ( source , canUpdateInvariantData ) ;
}
return MergeInvariant ( source , target , canUpdateInvariantData ) ;
}
internal virtual object? MergeVariantInvariantPropertyValue (
object? sourceValue ,
object? targetValue ,
bool canUpdateInvariantData ,
HashSet < string > allowedCultures )
{
2025-04-21 10:30:12 +02:00
if ( canUpdateInvariantData is false & & targetValue is null )
{
return sourceValue ;
}
2025-01-06 14:58:00 +01:00
BlockEditorData < TValue , TLayout > ? source = BlockEditorValues . DeserializeAndClean ( sourceValue ) ;
BlockEditorData < TValue , TLayout > ? target = BlockEditorValues . DeserializeAndClean ( targetValue ) ;
2025-02-11 12:45:09 +01:00
TValue ? mergedBlockValue =
MergeVariantInvariantPropertyValueTyped ( source , target , canUpdateInvariantData , allowedCultures ) ;
Merge branch 'v15/dev' into v16/dev (#18971)
* Only prevent the unpublish or delete of a related item when configured to do so if it is related as a child, not as a parent (#18886)
* Only prevent the unpubkish or delete of a related item when configured to do so if it is related as a child, not as a parent.
* Fixed incorect parameter names.
* Fixed failing integration tests.
* Use using variable instead to reduce nesting
* Applied suggestions from code review.
* Used simple using statement throughout RelationService for consistency.
* Applied XML header comments consistently.
---------
Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
* Feature: highlight invariant doc with variant blocks is unsupported (#18806)
* mark variant blocks in invariant docs as invalid
* implement RTE Blocks
* Fix pagination for users restricted by start nodes (#18907)
* Fix pagination for users restricted by start nodes
* Default implementation to avoid breakage
* Review comments
* Fix failing test
* Add media start node tests
* Fix issue preventing blueprint derived values from being scaffolded (#18917)
* Fix issue preventing blueprint derived values from being scaffolded.
* fix manipulating frooen array
* compare with variantId as well
---------
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
* ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com
* ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com
* ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com
* Remove admin permission on user configuration, allowing users with user section access only to manaage users and groups. (#18848)
* Tiptap RTE: Style Menu extension kind (#18918)
* Adds 'styleMenu' Tiptap toolbar extension kind
* Adds icons for `<h4>` and `<p>` tags
* Adds commands to HTML Global Attributes extension
for setting the `class` and `id` attributes.
* Renamed "default-tiptap-toolbar-element.api.ts" file
The "element" part was confusing.
* Toolbar Menu: uses correct `item` value
* Cascading Menu: adds localization for the label
* Adds `label` attribute to UUI components
for accessibility.
* Toolbar Menu: uses correct `appearance` value
* Removed unrequired `api` from Style Select
* Destructs the `item.data` object
* Ensure has children reflects only items with folder children when folders only are queried. (#18790)
* Ensure has children reflects only items with folder children when folders only are queried.
* Added supression for change to integration test public code.
---------
Co-authored-by: Migaroez <geusens@gmail.com>
* Only apply validation on content update to variant cultures where the editor has permission for the culture (#18778)
* Only apply validation on content update to variant cultures where the editor has permission for the culture.
* Remove inadvertent comment updates.
* Fixed failing integration test.
* Adds ancestor ID details on document tree and collection responses (#18909)
* Populate ancestor keys on document tree response items.
* Populate ancestor keys on document collection response items.
* Update OpenApi.json
* Use array of objects rather than Ids for the ancestor collection.
* Update OpenApi.json.
* Move publish with descendants to a background task with polling (#18497)
* Use background queue for database cache rebuild and track rebuilding status.
* Updated OpenApi.json and client-side types.
* Updated client to poll for completion of database rebuild.
* Move IBackgroundTaskQueue to core and prepare publish branch to run as background task.
* Endpoints for retrieval of status and result from branch publish operations.
* Poll and retrieve result for publish with descendants.
* Handled issues from testing.
* Rework to single controller for status and result.
* Updated client side sdk.
* OpenApi post dev merge gen
---------
Co-authored-by: Migaroez <geusens@gmail.com>
* Clear roots before rebuilding navigation dictionary (#18766)
* Clear roots before rebuilding navigation dictionary.
* Added tests to verify fix.
* Correct test implementation.
* Convert integration tests with method overloads into test cases.
* Integration test compatibility supressions.
* Fixes save of empty, invariant block list on variant content. (#18932)
* remove unnecessary code (#18927)
* V15/bugfix/fix route issue from 18859 (#18931)
* unique check
* unique for workspace empty path
* more unique routes
* Bump vite from 6.2.3 to 6.2.4 in /src/Umbraco.Web.UI.Client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.3 to 6.2.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.4/packages/vite)
---
updated-dependencies:
- dependency-name: vite
dependency-version: 6.2.4
dependency-type: direct:development
...
Signed-off-by: dependabot[bot] <support@github.com>
* removes autogenerated workflows
* make getHasUnpersistedChanges public (#18929)
* Added management API endpoint, service and repository for retrieval of references from the recycle bin (#18882)
* Added management API endpoint, service and repository for retrieval of references from the recycle bin.
* Update src/Umbraco.Cms.Api.Management/Controllers/Document/RecycleBin/ReferencedByDocumentRecycleBinController.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Removed unused code.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Updated management API endpoint and model for data type references to align with that used for documents, media etc. (#18905)
* Updated management API endpoint and model for data type references to align with that used for documents, media etc.
* Refactoring.
* Update src/Umbraco.Core/Constants-ReferenceTypes.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Fixed typos.
* Added id to tracked reference content type response.
* Updated OpenApi.json.
* Added missing updates.
* Renamed model and constants from code review feedback.
* Fix typo
* Fix multiple enumeration
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
* Skip lock tests
* Look-up redirect in content finder for multi-lingual sites using path and legacy route prefixed with the integer ID of the node with domains defined (#18763)
* Look-up redirect in content finder for multi-lingual sites using path and legacy route prefixed with the integer ID of the node with domains defined.
* Added tests to verify functionality.
* Added reference to previous PR.
* Referenced second PR.
* Assemble URLs for all cultures, not just the default.
* Revert previous update.
* Display an original URL if we have one.
* Bump vite from 6.2.4 to 6.2.5 in /src/Umbraco.Web.UI.Client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.4 to 6.2.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.5/packages/vite)
---
updated-dependencies:
- dependency-name: vite
dependency-version: 6.2.5
dependency-type: direct:development
...
Signed-off-by: dependabot[bot] <support@github.com>
* Add raw value validation to multiple text strings property editor (#18936)
* Add raw value validation to multiple text strings property editor
* Added additional assert on unit test and comment on validation logic.
* Don't remove items to obtain a valid value
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Integration tests for content publishing with ancestor unpublished (#18941)
* Resolved warnings in test class.
* Refactor regions into partial classes.
* Aligned test names.
* Variable name refactoring.
* Added tests for unpublished paths.
* Adjust tests to verify current behaviour.
* Cleaned up project file.
* fix circular icon import (#18952)
* remove segment toggle for elements (#18949)
* Fix modal route registration circular import (#18953)
* fix modal route registration circular import
* Update modal-route-registration.controller.ts
* V15/fix/18595 (#18925)
* fix for #18595
* updates the en.ts
* Avoid unneeded Dictionary operations (#18890)
* Avoid some heap allocations
* Remove unneeded double seek
* Avoid allocating new empty arrays, reuse existing empty array
* Avoid allocating strings for parsing comma separated int values (#18199)
* Data type References UI: Workspace + Delete (#18914)
* Updated management API endpoint and model for data type references to align with that used for documents, media etc.
* Refactoring.
* Update src/Umbraco.Core/Constants-ReferenceTypes.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Fixed typos.
* generate server models
* add extension slot
* register data type reference info app
* add reference data mappers
* Added id to tracked reference content type response.
* Updated OpenApi.json.
* Added missing updates.
* generate new models
* update models
* register ref item
* remove debugger
* render types
* register member type property type ref
* register media type property type ref
* Renamed model and constants from code review feedback.
* register reference workspace info app kind
* use kind for document references
* use kind for media references
* use kind for member references
* use deleteWithRelation kind when deleting data types
* fix manifest types
* fix types
* Update types.gen.ts
* update code to fit new server models
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Feature: discard changes for block workspace (#18930)
* make getHasUnpersistedChanges public
* Discard changes impl for Block Workspace
* fix 18367 (#18956)
* Merge commit from fork
* Prevent path traveral vulnerability with upload of temporary files.
* Used BadRequest instead of NotFound for invalid file name response.
* V15 QA Fixing the failing media acceptance tests (#18881)
* Fixed the function name due to test helper changes
* Updated assertion steps due to UI changes
* Added more waits
* Bumped version
* Increase timeout
* Reverted
---------
Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
* V15 QA added clipboard test for not being able to copy to root when block is not allowed at root (#18937)
* Added clipboard test
* Bumped version
* Updated to use the name
* Run all tests on the pipeline
* Reverted command
* build: adjusts circular ref number to 4
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Co-authored-by: Lee Kelleher <leekelleher@users.noreply.github.com>
Co-authored-by: Migaroez <geusens@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: Jacob Welander Jensen <64834767+Welander1994@users.noreply.github.com>
Co-authored-by: Henrik <hg@impact.dk>
Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
2025-04-09 09:58:01 +02:00
if ( mergedBlockValue is null )
{
return null ;
}
2025-02-11 12:45:09 +01:00
return _jsonSerializer . Serialize ( mergedBlockValue ) ;
}
internal virtual TValue ? MergeVariantInvariantPropertyValueTyped (
BlockEditorData < TValue , TLayout > ? source ,
BlockEditorData < TValue , TLayout > ? target ,
bool canUpdateInvariantData ,
HashSet < string > allowedCultures )
{
2025-04-21 10:30:12 +02:00
var mergedInvariant = UpdateSourceInvariantData ( source , target , canUpdateInvariantData ) ;
2025-01-06 14:58:00 +01:00
2025-04-21 10:30:12 +02:00
// if the structure (invariant) is not defined after merger, the target content does not matter
2025-07-23 20:10:02 +02:00
if ( mergedInvariant ? . Layout is null )
2025-01-06 14:58:00 +01:00
{
return null ;
}
2025-04-21 10:30:12 +02:00
// since we merged the invariant data (layout) before we get to this point
// we just need an empty valid object to run comparisons at this point
if ( source is null )
2025-01-06 14:58:00 +01:00
{
2025-04-21 10:30:12 +02:00
source = new BlockEditorData < TValue , TLayout > ( [ ] , new TValue ( ) ) ;
2025-01-06 14:58:00 +01:00
}
2025-04-21 10:30:12 +02:00
// update the target with the merged invariant
target ! . BlockValue . Layout = mergedInvariant . BlockValue . Layout ;
2025-01-06 14:58:00 +01:00
// remove all the blocks that are no longer part of the layout
target . BlockValue . ContentData . RemoveAll ( contentBlock = >
2025-02-05 16:21:14 +01:00
target . Layout ! . Any ( layoutItem = > layoutItem . ReferencesContent ( contentBlock . Key ) ) is false ) ;
2025-04-21 10:30:12 +02:00
// remove any exposes that no longer have content assigned to them
target . BlockValue . Expose . RemoveAll ( expose = > target . BlockValue . ContentData . Any ( data = > data . Key = = expose . ContentKey ) is false ) ;
2025-01-06 14:58:00 +01:00
target . BlockValue . SettingsData . RemoveAll ( settingsBlock = >
2025-02-05 16:21:14 +01:00
target . Layout ! . Any ( layoutItem = > layoutItem . ReferencesSetting ( settingsBlock . Key ) ) is false ) ;
2025-01-06 14:58:00 +01:00
CleanupVariantValues ( source . BlockValue . ContentData , target . BlockValue . ContentData , canUpdateInvariantData , allowedCultures ) ;
CleanupVariantValues ( source . BlockValue . SettingsData , target . BlockValue . SettingsData , canUpdateInvariantData , allowedCultures ) ;
2025-04-21 10:30:12 +02:00
// every source block value for a culture that is not allowed to be edited should be present on the target
RestoreMissingValues (
source . BlockValue . ContentData ,
target . BlockValue . ContentData ,
2025-07-23 20:10:02 +02:00
mergedInvariant . Layout ,
2025-04-21 10:30:12 +02:00
( layoutItem , itemData ) = > layoutItem . ContentKey = = itemData . Key ,
canUpdateInvariantData ,
allowedCultures ) ;
RestoreMissingValues (
source . BlockValue . SettingsData ,
target . BlockValue . SettingsData ,
2025-07-23 20:10:02 +02:00
mergedInvariant . Layout ,
2025-04-21 10:30:12 +02:00
( layoutItem , itemData ) = > layoutItem . SettingsKey = = itemData . Key ,
canUpdateInvariantData ,
allowedCultures ) ;
// update the expose list from source for any blocks that were restored
var missingSourceExposes =
source . BlockValue . Expose . Where ( sourceExpose = >
target . BlockValue . Expose . Any ( targetExpose = > targetExpose . ContentKey = = sourceExpose . ContentKey ) is false
& & target . BlockValue . ContentData . Any ( data = > data . Key = = sourceExpose . ContentKey ) ) . ToList ( ) ;
foreach ( BlockItemVariation missingSourceExpose in missingSourceExposes )
{
target . BlockValue . Expose . Add ( missingSourceExpose ) ;
}
2025-02-11 12:45:09 +01:00
return target . BlockValue ;
2025-01-06 14:58:00 +01:00
}
2025-04-21 10:30:12 +02:00
private void RestoreMissingValues (
List < BlockItemData > sourceBlockItemData ,
List < BlockItemData > targetBlockItemData ,
IEnumerable < TLayout > mergedLayout ,
Func < TLayout , BlockItemData , bool > relevantBlockItemMatcher ,
bool canUpdateInvariantData ,
HashSet < string > allowedCultures )
{
IEnumerable < BlockItemData > blockItemsToCheck = sourceBlockItemData . Where ( itemData = >
mergedLayout . Any ( layoutItem = > relevantBlockItemMatcher ( layoutItem , itemData ) ) ) ;
foreach ( BlockItemData blockItemData in blockItemsToCheck )
{
var relevantValues = blockItemData . Values . Where ( value = >
( value . Culture is null & & canUpdateInvariantData is false )
| | ( value . Culture is not null & & allowedCultures . Contains ( value . Culture ) is false ) ) . ToList ( ) ;
if ( relevantValues . Count < 1 )
{
continue ;
}
BlockItemData targetBlockData =
targetBlockItemData . FirstOrDefault ( itemData = > itemData . Key = = blockItemData . Key )
? ? new BlockItemData ( blockItemData . Key , blockItemData . ContentTypeKey , blockItemData . ContentTypeAlias ) ;
foreach ( BlockPropertyValue missingValue in relevantValues . Where ( value = > targetBlockData . Values . Any ( targetValue = >
targetValue . Alias = = value . Alias
& & targetValue . Culture = = value . Culture
& & targetValue . Segment = = value . Segment ) is false ) )
{
targetBlockData . Values . Add ( missingValue ) ;
}
if ( targetBlockItemData . Any ( existingBlockItemData = > existingBlockItemData . Key = = targetBlockData . Key ) is false )
{
targetBlockItemData . Add ( blockItemData ) ;
}
}
}
2025-01-06 14:58:00 +01:00
private void CleanupVariantValues (
List < BlockItemData > sourceBlockItems ,
List < BlockItemData > targetBlockItems ,
bool canUpdateInvariantData ,
HashSet < string > allowedCultures )
{
2025-04-21 10:30:12 +02:00
// merge the source values into the target values per culture
2025-01-06 14:58:00 +01:00
foreach ( BlockItemData targetBlockItem in targetBlockItems )
{
BlockItemData ? sourceBlockItem = sourceBlockItems . FirstOrDefault ( i = > i . Key = = targetBlockItem . Key ) ;
var valuesToRemove = new List < BlockPropertyValue > ( ) ;
foreach ( BlockPropertyValue targetBlockPropertyValue in targetBlockItem . Values )
{
2025-03-05 14:23:33 +01:00
BlockPropertyValue ? sourceBlockPropertyValue = sourceBlockItem ? . Values . FirstOrDefault ( v
= > v . Alias = = targetBlockPropertyValue . Alias & & v . Culture = = targetBlockPropertyValue . Culture ) ;
2025-01-06 14:58:00 +01:00
// todo double check if this path can have an invariant value, but it shouldn't right???
// => it can be a null culture, but we shouldn't do anything? as the invariant section should have done it already
if ( ( targetBlockPropertyValue . Culture is null & & canUpdateInvariantData = = false )
| | ( targetBlockPropertyValue . Culture is not null & & allowedCultures . Contains ( targetBlockPropertyValue . Culture ) is false ) )
{
// not allowed to update this culture, set the value back to the source
if ( sourceBlockPropertyValue is null )
{
valuesToRemove . Add ( targetBlockPropertyValue ) ;
}
else
{
targetBlockPropertyValue . Value = sourceBlockPropertyValue . Value ;
}
continue ;
}
// is this another editor that supports partial merging? i.e. blocks within blocks.
IDataEditor ? mergingDataEditor = null ;
var shouldPerformPartialMerge = targetBlockPropertyValue . PropertyType is not null
& & _propertyEditors . TryGet ( targetBlockPropertyValue . PropertyType . PropertyEditorAlias , out mergingDataEditor )
& & mergingDataEditor . CanMergePartialPropertyValues ( targetBlockPropertyValue . PropertyType ) ;
if ( shouldPerformPartialMerge is false )
{
continue ;
}
// marge subdata
targetBlockPropertyValue . Value = mergingDataEditor ! . MergeVariantInvariantPropertyValue (
sourceBlockPropertyValue ? . Value ,
targetBlockPropertyValue . Value ,
canUpdateInvariantData ,
allowedCultures ) ;
}
foreach ( BlockPropertyValue value in valuesToRemove )
{
targetBlockItem . Values . Remove ( value ) ;
}
}
}
private BlockEditorData < TValue , TLayout > ? MergeNewInvariant ( BlockEditorData < TValue , TLayout > target , bool canUpdateInvariantData )
{
if ( canUpdateInvariantData is false )
{
// source value was null and not allowed to update the structure which is invariant => nothing remains
return null ;
}
// create a new source object based on the target value that only has the invariant data (structure)
return target . Layout is not null
? new BlockEditorData < TValue , TLayout > ( [ ] , CreateWithLayout ( target . Layout ) )
: null ;
}
private BlockEditorData < TValue , TLayout > ? MergeRemovalInvariant ( BlockEditorData < TValue , TLayout > source , bool canUpdateInvariantData )
{
if ( canUpdateInvariantData )
{
// if the structure is removed, everything is gone anyway
return null ;
}
// create a new target object based on the source value that only has the invariant data (structure)
return source . Layout is not null
? new BlockEditorData < TValue , TLayout > ( [ ] , CreateWithLayout ( source . Layout ) )
: null ;
}
private BlockEditorData < TValue , TLayout > MergeInvariant ( BlockEditorData < TValue , TLayout > source , BlockEditorData < TValue , TLayout > target , bool canUpdateInvariantData )
{
if ( canUpdateInvariantData )
{
source . BlockValue . Layout = target . BlockValue . Layout ;
source . BlockValue . Expose = target . BlockValue . Expose ;
}
return source ;
}
2024-09-30 07:01:18 +02:00
internal virtual object? MergePartialPropertyValueForCulture ( object? sourceValue , object? targetValue , string? culture )
{
if ( sourceValue is null )
{
return null ;
}
// parse the source value as block editor data
BlockEditorData < TValue , TLayout > ? sourceBlockEditorValues = BlockEditorValues . DeserializeAndClean ( sourceValue ) ;
if ( sourceBlockEditorValues ? . Layout is null )
{
return null ;
}
// parse the target value as block editor data (fallback to an empty set of block editor data)
BlockEditorData < TValue , TLayout > targetBlockEditorValues =
( targetValue is not null ? BlockEditorValues . DeserializeAndClean ( targetValue ) : null )
? ? new BlockEditorData < TValue , TLayout > ( [ ] , CreateWithLayout ( sourceBlockEditorValues . Layout ) ) ;
TValue mergeResult = MergeBlockEditorDataForCulture ( sourceBlockEditorValues . BlockValue , targetBlockEditorValues . BlockValue , culture ) ;
return _jsonSerializer . Serialize ( mergeResult ) ;
}
protected TValue MergeBlockEditorDataForCulture ( TValue sourceBlockValue , TValue targetBlockValue , string? culture )
{
// structure is global, layout and expose follows structure
targetBlockValue . Layout = sourceBlockValue . Layout ;
targetBlockValue . Expose = sourceBlockValue . Expose ;
MergePartialPropertyValueForCulture ( sourceBlockValue . ContentData , targetBlockValue . ContentData , culture ) ;
MergePartialPropertyValueForCulture ( sourceBlockValue . SettingsData , targetBlockValue . SettingsData , culture ) ;
return targetBlockValue ;
}
private void MergePartialPropertyValueForCulture ( List < BlockItemData > sourceBlockItems , List < BlockItemData > targetBlockItems , string? culture )
{
// remove all target blocks that are not part of the source blocks (structure is global)
targetBlockItems . RemoveAll ( pb = > sourceBlockItems . Any ( eb = > eb . Key = = pb . Key ) is false ) ;
// merge the source values into the target values for culture
foreach ( BlockItemData sourceBlockItem in sourceBlockItems )
{
BlockItemData ? targetBlockItem = targetBlockItems . FirstOrDefault ( i = > i . Key = = sourceBlockItem . Key ) ;
if ( targetBlockItem is null )
{
targetBlockItem = new BlockItemData (
sourceBlockItem . Key ,
sourceBlockItem . ContentTypeKey ,
sourceBlockItem . ContentTypeAlias ) ;
// NOTE: this only works because targetBlockItem is by ref!
targetBlockItems . Add ( targetBlockItem ) ;
}
foreach ( BlockPropertyValue sourceBlockPropertyValue in sourceBlockItem . Values )
{
// is this another editor that supports partial merging? i.e. blocks within blocks.
IDataEditor ? mergingDataEditor = null ;
var shouldPerformPartialMerge = sourceBlockPropertyValue . PropertyType is not null
& & _propertyEditors . TryGet ( sourceBlockPropertyValue . PropertyType . PropertyEditorAlias , out mergingDataEditor )
& & mergingDataEditor . CanMergePartialPropertyValues ( sourceBlockPropertyValue . PropertyType ) ;
if ( shouldPerformPartialMerge is false & & sourceBlockPropertyValue . Culture ! = culture )
{
// skip for now (irrelevant for the current culture, but might be included in the next pass)
continue ;
}
BlockPropertyValue ? targetBlockPropertyValue = targetBlockItem
. Values
. FirstOrDefault ( v = >
v . Alias = = sourceBlockPropertyValue . Alias & &
v . Culture = = sourceBlockPropertyValue . Culture & &
v . Segment = = sourceBlockPropertyValue . Segment ) ;
if ( targetBlockPropertyValue is null )
{
targetBlockPropertyValue = new BlockPropertyValue
{
Alias = sourceBlockPropertyValue . Alias ,
Culture = sourceBlockPropertyValue . Culture ,
Segment = sourceBlockPropertyValue . Segment
} ;
targetBlockItem . Values . Add ( targetBlockPropertyValue ) ;
}
// assign source value to target value (or perform partial merge, depending on context)
targetBlockPropertyValue . Value = shouldPerformPartialMerge is false
? sourceBlockPropertyValue . Value
: mergingDataEditor ! . MergePartialPropertyValueForCulture ( sourceBlockPropertyValue . Value , targetBlockPropertyValue . Value , culture ) ;
2023-10-31 12:52:35 +01:00
}
}
}
}