Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
297d123
Initial implementation of non existing property editor
lauraneto Aug 22, 2025
9f00aef
Adjust `MissingPropertyEditor` to not require registering in Property…
lauraneto Aug 26, 2025
e83b3f6
Add `MissingPropertyEditor.name` back
lauraneto Aug 26, 2025
d42e367
Remove unused dependencies from DataTypeService
lauraneto Aug 26, 2025
c085672
Removed reference to non existing property
lauraneto Aug 26, 2025
c9d074f
Add parameterless constructor back to MissingPropertyEditor
lauraneto Aug 26, 2025
0cf91ab
Merge branch 'main' into v16/feature/non-existing-property-editor
lauraneto Aug 27, 2025
911fdd1
Add validation error on document open to property with missing editor
lauraneto Sep 1, 2025
0fe3af6
Update labels
lauraneto Sep 2, 2025
29d6922
Removed public editor alias const
lauraneto Sep 2, 2025
0d995b9
Update src/Umbraco.Web.UI.Client/src/packages/property-editors/missin…
lauraneto Sep 2, 2025
beda961
Add test that checks whether the new MissingPropertyEditor is returne…
lauraneto Sep 3, 2025
ef0f19b
Also check if the editor UI alias is correct in the test
lauraneto Sep 3, 2025
4101e07
Apply suggestions from code review
lauraneto Sep 3, 2025
b459589
Share property editor instances between properties
lauraneto Sep 3, 2025
b2f04f4
Only store missing property editors in memory in `ContentMapDefinitio…
lauraneto Sep 8, 2025
653fe65
Merge branch 'main' into v16/feature/non-existing-property-editor
lauraneto Sep 8, 2025
4877b89
Add value converter for the missing property editor to always return …
lauraneto Sep 8, 2025
4980648
Small improvements to code block
lauraneto Sep 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Umbraco.Cms.Api.Management.ViewModels.Content;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.ViewModels.Content;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.PropertyEditors;
Expand All @@ -12,45 +14,68 @@ public abstract class ContentMapDefinition<TContent, TValueViewModel, TVariantVi
where TVariantViewModel : VariantResponseModelBase, new()
{
private readonly PropertyEditorCollection _propertyEditorCollection;
private readonly IDataValueEditorFactory _dataValueEditorFactory;

protected ContentMapDefinition(PropertyEditorCollection propertyEditorCollection) => _propertyEditorCollection = propertyEditorCollection;
protected ContentMapDefinition(
PropertyEditorCollection propertyEditorCollection,
IDataValueEditorFactory dataValueEditorFactory)
{
_propertyEditorCollection = propertyEditorCollection;
_dataValueEditorFactory = dataValueEditorFactory;
}

[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in Umbraco 18.")]
protected ContentMapDefinition(PropertyEditorCollection propertyEditorCollection)
: this(
propertyEditorCollection,
StaticServiceProvider.Instance.GetRequiredService<IDataValueEditorFactory>())
{
}

protected delegate void ValueViewModelMapping(IDataEditor propertyEditor, TValueViewModel variantViewModel);

protected delegate void VariantViewModelMapping(string? culture, string? segment, TVariantViewModel variantViewModel);

protected IEnumerable<TValueViewModel> MapValueViewModels(IEnumerable<IProperty> properties, ValueViewModelMapping? additionalPropertyMapping = null, bool published = false) =>
properties
protected IEnumerable<TValueViewModel> MapValueViewModels(
IEnumerable<IProperty> properties,
ValueViewModelMapping? additionalPropertyMapping = null,
bool published = false)
{
Dictionary<string, IDataEditor> dataEditors = [];
return properties
.SelectMany(property => property
.Values
.Select(propertyValue =>
{
IDataEditor? propertyEditor = _propertyEditorCollection[property.PropertyType.PropertyEditorAlias];
if (propertyEditor == null)
{
return null;
}
IDataEditor propertyEditor = GetDataEditor(dataEditors, property);

IProperty? publishedProperty = null;
if (published)
{
publishedProperty = new Property(property.PropertyType);
publishedProperty.SetValue(propertyValue.PublishedValue, propertyValue.Culture, propertyValue.Segment);
publishedProperty.SetValue(
propertyValue.PublishedValue,
propertyValue.Culture,
propertyValue.Segment);
}

var variantViewModel = new TValueViewModel
{
Culture = propertyValue.Culture,
Segment = propertyValue.Segment,
Alias = property.Alias,
Value = propertyEditor.GetValueEditor().ToEditor(publishedProperty ?? property, propertyValue.Culture, propertyValue.Segment),
EditorAlias = propertyEditor.Alias
Value = propertyEditor.GetValueEditor().ToEditor(
publishedProperty ?? property,
propertyValue.Culture,
propertyValue.Segment),
EditorAlias = propertyEditor.Alias,
};
additionalPropertyMapping?.Invoke(propertyEditor, variantViewModel);
return variantViewModel;
}))
.WhereNotNull()
.ToArray();
}

protected IEnumerable<TVariantViewModel> MapVariantViewModels(TContent source, VariantViewModelMapping? additionalVariantMapping = null)
{
Expand All @@ -77,4 +102,22 @@ protected IEnumerable<TVariantViewModel> MapVariantViewModels(TContent source, V
}))
.ToArray();
}

private IDataEditor GetDataEditor(Dictionary<string, IDataEditor> dataEditors, IProperty property)
{
IDataEditor propertyEditor;
if (dataEditors.TryGetValue(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor))
{
propertyEditor = dataEditor;
}
else
{
propertyEditor = _propertyEditorCollection[property.PropertyType.PropertyEditorAlias]
?? new MissingPropertyEditor(property.PropertyType.PropertyEditorAlias, _dataValueEditorFactory);

dataEditors[property.PropertyType.PropertyEditorAlias] = propertyEditor;
}

return propertyEditor;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Mapping.Content;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Api.Management.ViewModels.Document.Collection;
using Umbraco.Cms.Api.Management.ViewModels.DocumentBlueprint;
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Mapping;
Expand All @@ -15,8 +17,23 @@ public class DocumentMapDefinition : ContentMapDefinition<IContent, DocumentValu
{
private readonly CommonMapper _commonMapper;

public DocumentMapDefinition(PropertyEditorCollection propertyEditorCollection, CommonMapper commonMapper)
: base(propertyEditorCollection) => _commonMapper = commonMapper;
public DocumentMapDefinition(
PropertyEditorCollection propertyEditorCollection,
CommonMapper commonMapper,
IDataValueEditorFactory dataValueEditorFactory)
: base(propertyEditorCollection, dataValueEditorFactory)
=> _commonMapper = commonMapper;

[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in Umbraco 18.")]
public DocumentMapDefinition(
PropertyEditorCollection propertyEditorCollection,
CommonMapper commonMapper)
: this(
propertyEditorCollection,
commonMapper,
StaticServiceProvider.Instance.GetRequiredService<IDataValueEditorFactory>())
{
}

public void DefineMaps(IUmbracoMapper mapper)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Umbraco.Cms.Api.Management.Mapping.Content;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Mapping.Content;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
Expand All @@ -11,8 +13,19 @@ namespace Umbraco.Cms.Api.Management.Mapping.Document;

public class DocumentVersionMapDefinition : ContentMapDefinition<IContent, DocumentValueResponseModel, DocumentVariantResponseModel>, IMapDefinition
{
public DocumentVersionMapDefinition(PropertyEditorCollection propertyEditorCollection)
: base(propertyEditorCollection)
public DocumentVersionMapDefinition(
PropertyEditorCollection propertyEditorCollection,
IDataValueEditorFactory dataValueEditorFactory)
: base(propertyEditorCollection, dataValueEditorFactory)
{
}

[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in Umbraco 18.")]
public DocumentVersionMapDefinition(
PropertyEditorCollection propertyEditorCollection)
: this(
propertyEditorCollection,
StaticServiceProvider.Instance.GetRequiredService<IDataValueEditorFactory>())
{
}

Expand Down
20 changes: 18 additions & 2 deletions src/Umbraco.Cms.Api.Management/Mapping/Media/MediaMapDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Mapping.Content;
using Umbraco.Cms.Api.Management.ViewModels.Media;
using Umbraco.Cms.Api.Management.ViewModels.Media.Collection;
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Mapping;
Expand All @@ -14,10 +16,24 @@ public class MediaMapDefinition : ContentMapDefinition<IMedia, MediaValueRespons
{
private readonly CommonMapper _commonMapper;

public MediaMapDefinition(PropertyEditorCollection propertyEditorCollection, CommonMapper commonMapper)
: base(propertyEditorCollection)
public MediaMapDefinition(
PropertyEditorCollection propertyEditorCollection,
CommonMapper commonMapper,
IDataValueEditorFactory dataValueEditorFactory)
: base(propertyEditorCollection, dataValueEditorFactory)
=> _commonMapper = commonMapper;

[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in Umbraco 18.")]
public MediaMapDefinition(
PropertyEditorCollection propertyEditorCollection,
CommonMapper commonMapper)
: this(
propertyEditorCollection,
commonMapper,
StaticServiceProvider.Instance.GetRequiredService<IDataValueEditorFactory>())
{
}

public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<IMedia, MediaResponseModel>((_, _) => new MediaResponseModel(), Map);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Mapping.Content;
using Umbraco.Cms.Api.Management.ViewModels.Member;
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
Expand All @@ -9,8 +11,19 @@ namespace Umbraco.Cms.Api.Management.Mapping.Member;

public class MemberMapDefinition : ContentMapDefinition<IMember, MemberValueResponseModel, MemberVariantResponseModel>, IMapDefinition
{
public MemberMapDefinition(PropertyEditorCollection propertyEditorCollection)
: base(propertyEditorCollection)
public MemberMapDefinition(
PropertyEditorCollection propertyEditorCollection,
IDataValueEditorFactory dataValueEditorFactory)
: base(propertyEditorCollection, dataValueEditorFactory)
{
}

[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in Umbraco 18.")]
public MemberMapDefinition(
PropertyEditorCollection propertyEditorCollection)
: this(
propertyEditorCollection,
StaticServiceProvider.Instance.GetRequiredService<IDataValueEditorFactory>())
{
}

Expand Down
69 changes: 64 additions & 5 deletions src/Umbraco.Core/PropertyEditors/MissingPropertyEditor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Strings;

namespace Umbraco.Cms.Core.PropertyEditors;

Expand All @@ -10,19 +15,73 @@ namespace Umbraco.Cms.Core.PropertyEditors;
[HideFromTypeFinder]
public class MissingPropertyEditor : IDataEditor
{
public string Alias => "Umbraco.Missing";
private const string EditorAlias = "Umbraco.Missing";
private readonly IDataValueEditorFactory _dataValueEditorFactory;
private IDataValueEditor? _valueEditor;

/// <summary>
/// Initializes a new instance of the <see cref="MissingPropertyEditor"/> class.
/// </summary>
public MissingPropertyEditor(
string missingEditorAlias,
IDataValueEditorFactory dataValueEditorFactory)
{
_dataValueEditorFactory = dataValueEditorFactory;
Alias = missingEditorAlias;
}

[Obsolete("Use the non-obsolete constructor instead. Scheduled for removal in Umbraco 18.")]
public MissingPropertyEditor()
: this(
EditorAlias,
StaticServiceProvider.Instance.GetRequiredService<IDataValueEditorFactory>())
{
}

/// <inheritdoc />
public string Alias { get; }

/// <summary>
/// Gets the name of the editor.
/// </summary>
public string Name => "Missing property editor";

/// <inheritdoc />
public bool IsDeprecated => false;

public IDictionary<string, object> DefaultConfiguration => throw new NotImplementedException();
/// <inheritdoc />
public bool SupportsReadOnly => true;

/// <inheritdoc />
public IDictionary<string, object> DefaultConfiguration => new Dictionary<string, object>();

/// <inheritdoc />
public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory();

/// <inheritdoc />
public IDataValueEditor GetValueEditor() => _valueEditor
??= _dataValueEditorFactory.Create<MissingPropertyValueEditor>(
new DataEditorAttribute(EditorAlias));

public IPropertyIndexValueFactory PropertyIndexValueFactory => throw new NotImplementedException();
/// <inheritdoc />
public IDataValueEditor GetValueEditor(object? configurationObject) => GetValueEditor();

/// <inheritdoc />
public IConfigurationEditor GetConfigurationEditor() => new ConfigurationEditor();

public IDataValueEditor GetValueEditor() => throw new NotImplementedException();
// provides the property value editor
internal sealed class MissingPropertyValueEditor : DataValueEditor
{
public MissingPropertyValueEditor(
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
DataEditorAttribute attribute)
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
{
}

public IDataValueEditor GetValueEditor(object? configurationObject) => throw new NotImplementedException();
/// <inheritdoc />
public override bool IsReadOnly => true;
}
}
Loading
Loading