using System.Diagnostics; using System.Runtime.Serialization; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents a data editor. /// /// /// /// Editors can be deserialized from e.g. manifests, which is. why the class is not abstract, /// the json serialization attributes are required, and the properties have an internal setter. /// /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "(),nq}")] [HideFromTypeFinder] [DataContract] public class DataEditor : IDataEditor { private readonly bool _canReuseValueEditor; private IDataValueEditor? _reusableValueEditor; private IDictionary? _defaultConfiguration; /// /// Initializes a new instance of the class. /// public DataEditor(IDataValueEditorFactory dataValueEditorFactory, EditorType type = EditorType.PropertyValue) { // defaults DataValueEditorFactory = dataValueEditorFactory; Type = type; Icon = Constants.Icons.PropertyEditor; Group = Constants.PropertyEditors.Groups.Common; // assign properties based on the attribute, if it is found Attribute = GetType().GetCustomAttribute(false); if (Attribute == null) { Alias = string.Empty; Name = string.Empty; return; } Alias = Attribute.Alias; Type = Attribute.Type; Name = Attribute.Name; Icon = Attribute.Icon; Group = Attribute.Group; IsDeprecated = Attribute.IsDeprecated; _canReuseValueEditor = Attribute.ValueEditorIsReusable; } /// /// Gets or sets an explicit value editor. /// /// Used for manifest data editors. [DataMember(Name = "editor")] public IDataValueEditor? ExplicitValueEditor { get; set; } /// /// Gets the editor attribute. /// protected DataEditorAttribute? Attribute { get; } protected IDataValueEditorFactory DataValueEditorFactory { get; } /// /// Gets or sets an explicit configuration editor. /// /// Used for manifest data editors. [DataMember(Name = "config")] public IConfigurationEditor? ExplicitConfigurationEditor { get; set; } /// [DataMember(Name = "alias", IsRequired = true)] public string Alias { get; set; } /// [DataMember(Name = "supportsReadOnly", IsRequired = true)] public bool SupportsReadOnly { get; set; } /// [IgnoreDataMember] public EditorType Type { get; } /// [DataMember(Name = "name", IsRequired = true)] public string Name { get; internal set; } /// [DataMember(Name = "icon")] public string Icon { get; internal set; } /// [DataMember(Name = "group")] public string Group { get; internal set; } /// [IgnoreDataMember] public bool IsDeprecated { get; } /// [DataMember(Name = "defaultConfig")] public IDictionary DefaultConfiguration { // for property value editors, get the ConfigurationEditor.DefaultConfiguration // else fallback to a default, empty dictionary get => _defaultConfiguration ?? ((Type & EditorType.PropertyValue) > 0 ? GetConfigurationEditor().DefaultConfiguration : _defaultConfiguration = new Dictionary()); set => _defaultConfiguration = value; } /// /// /// /// If an explicit value editor has been assigned, then this explicit /// instance is returned. Otherwise, a new instance is created by CreateValueEditor. /// /// /// The instance created by CreateValueEditor is cached if allowed by the DataEditor /// attribute ( == true). /// /// public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_canReuseValueEditor ? _reusableValueEditor ??= CreateValueEditor() : CreateValueEditor()); /// /// /// /// If an explicit value editor has been assigned, then this explicit /// instance is returned. Otherwise, a new instance is created by CreateValueEditor, /// and configured with the configuration. /// /// /// The instance created by CreateValueEditor is not cached, i.e. /// a new instance is created each time the property value is retrieved. The /// property editor is a singleton, and the value editor cannot be a singleton /// since it depends on the datatype configuration. /// /// /// Technically, it could be cached by datatype but let's keep things /// simple enough for now. /// /// public virtual IDataValueEditor GetValueEditor(object? configuration) { // if an explicit value editor has been set (by the manifest parser) // then return it, and ignore the configuration, which is going to be // empty anyways if (ExplicitValueEditor != null) { return ExplicitValueEditor; } IDataValueEditor editor = CreateValueEditor(); if (configuration is not null) { ((DataValueEditor)editor).Configuration = configuration; // TODO: casting is bad } return editor; } /// /// /// /// If an explicit configuration editor has been assigned, then this explicit /// instance is returned. Otherwise, a new instance is created by CreateConfigurationEditor. /// /// /// The instance created by CreateConfigurationEditor is not cached, i.e. /// a new instance is created each time. The property editor is a singleton, and although the /// configuration editor could technically be a singleton too, we'd rather not keep configuration editor /// cached. /// /// public IConfigurationEditor GetConfigurationEditor() => ExplicitConfigurationEditor ?? CreateConfigurationEditor(); /// public virtual IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); /// /// Creates a value editor instance. /// /// protected virtual IDataValueEditor CreateValueEditor() { if (Attribute == null) { throw new InvalidOperationException($"The editor is not attributed with {nameof(DataEditorAttribute)}"); } return DataValueEditorFactory.Create(Attribute); } /// /// Creates a configuration editor instance. /// protected virtual IConfigurationEditor CreateConfigurationEditor() { var editor = new ConfigurationEditor(); // pass the default configuration if this is not a property value editor if ((Type & EditorType.PropertyValue) == 0 && _defaultConfiguration is not null) { editor.DefaultConfiguration = _defaultConfiguration; } return editor; } /// /// Provides a summary of the PropertyEditor for use with the . /// protected virtual string DebuggerDisplay() => $"Name: {Name}, Alias: {Alias}"; }