diff --git a/src/Umbraco.Core/Components/CompositionExtensions.cs b/src/Umbraco.Core/Components/CompositionExtensions.cs index dedbc9c3fc..7e94e4dc2b 100644 --- a/src/Umbraco.Core/Components/CompositionExtensions.cs +++ b/src/Umbraco.Core/Components/CompositionExtensions.cs @@ -67,8 +67,8 @@ namespace Umbraco.Core.Components /// Gets the validators collection builder. /// /// The composition. - internal static ManifestValidatorCollectionBuilder Validators(this Composition composition) - => composition.Container.GetInstance(); + internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition) + => composition.Container.GetInstance(); /// /// Gets the post-migrations collection builder. diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index b1f5810206..f462c1b6ea 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -117,8 +117,8 @@ namespace Umbraco.Core.Composing public static ParameterEditorCollection ParameterEditors => Container.GetInstance(); - internal static ManifestValidatorCollection ManifestValidators - => Container.GetInstance(); + internal static ManifestValueValidatorCollection ManifestValidators + => Container.GetInstance(); internal static PackageActionCollection PackageActions => Container.GetInstance(); diff --git a/src/Umbraco.Core/Manifest/DataEditorConverter.cs b/src/Umbraco.Core/Manifest/DataEditorConverter.cs index dad31aa62e..20efee1607 100644 --- a/src/Umbraco.Core/Manifest/DataEditorConverter.cs +++ b/src/Umbraco.Core/Manifest/DataEditorConverter.cs @@ -74,7 +74,7 @@ namespace Umbraco.Core.Manifest // explicitely assign a value editor of type ValueEditor // (else the deserializer will try to read it before setting it) // (and besides it's an interface) - target.ValueEditor = new DataValueEditor(); + target.ExplicitValueEditor = new DataValueEditor(); // in the manifest, validators are a simple dictionary eg // { @@ -91,7 +91,7 @@ namespace Umbraco.Core.Manifest // explicitely assign a configuration editor of type ConfigurationEditor // (else the deserializer will try to read it before setting it) // (and besides it's an interface) - target.ConfigurationEditor = new ConfigurationEditor(); + target.ExplicitConfigurationEditor = new ConfigurationEditor(); // see note about validators, above - same applies to field validators if (config["fields"] is JArray jarray) @@ -135,7 +135,7 @@ namespace Umbraco.Core.Manifest if (jobject.Property("view") != null) { // explicitely assign a value editor of type ParameterValueEditor - target.ValueEditor = new DataValueEditor(); + target.ExplicitValueEditor = new DataValueEditor(); // move the 'view' property jobject["editor"] = new JObject { ["view"] = jobject["view"] }; @@ -157,8 +157,8 @@ namespace Umbraco.Core.Manifest foreach (var v in validation) { var key = v.Key; - var val = v.Value?.Type == JTokenType.Boolean ? string.Empty : v.Value; - var jo = new JObject { { "type", key }, { "config", val } }; + var val = v.Value; + var jo = new JObject { { "type", key }, { "configuration", val } }; jarray.Add(jo); } diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 11e4b18e44..32070b3542 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -21,21 +21,21 @@ namespace Umbraco.Core.Manifest private readonly IRuntimeCacheProvider _cache; private readonly ILogger _logger; - private readonly ManifestValidatorCollection _validators; + private readonly ManifestValueValidatorCollection _validators; private string _path; /// /// Initializes a new instance of the class. /// - public ManifestParser(IRuntimeCacheProvider cache, ManifestValidatorCollection validators, ILogger logger) + public ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, ILogger logger) : this(cache, validators, "~/App_Plugins", logger) { } /// /// Initializes a new instance of the class. /// - private ManifestParser(IRuntimeCacheProvider cache, ManifestValidatorCollection validators, string path, ILogger logger) + private ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, string path, ILogger logger) { _cache = cache ?? throw new ArgumentNullException(nameof(cache)); _validators = validators ?? throw new ArgumentNullException(nameof(validators)); @@ -141,7 +141,7 @@ namespace Umbraco.Core.Manifest var manifest = JsonConvert.DeserializeObject(text, new DataEditorConverter(_logger), - new ManifestValidatorConverter(_validators)); + new ValueValidatorConverter(_validators)); // scripts and stylesheets are raw string, must process here for (var i = 0; i < manifest.Scripts.Length; i++) diff --git a/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs b/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs deleted file mode 100644 index d58a66e7b4..0000000000 --- a/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Serialization; - -namespace Umbraco.Core.Manifest -{ - /// - /// Implements a json read converter for . - /// - internal class ManifestValidatorConverter : JsonReadConverter - { - private readonly ManifestValidatorCollection _validators; - - /// - /// Initializes a new instance of the class. - /// - public ManifestValidatorConverter(ManifestValidatorCollection validators) - { - _validators = validators; - } - - protected override IValueValidator Create(Type objectType, string path, JObject jObject) - { - // all validators coming from manifests are ManifestPropertyValidator instances - return new ManifestValueValidator(_validators); - } - } -} diff --git a/src/Umbraco.Core/Manifest/ValueValidatorConverter.cs b/src/Umbraco.Core/Manifest/ValueValidatorConverter.cs new file mode 100644 index 0000000000..75b0b2573b --- /dev/null +++ b/src/Umbraco.Core/Manifest/ValueValidatorConverter.cs @@ -0,0 +1,34 @@ +using System; +using Newtonsoft.Json.Linq; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Serialization; + +namespace Umbraco.Core.Manifest +{ + /// + /// Implements a json read converter for . + /// + internal class ValueValidatorConverter : JsonReadConverter + { + private readonly ManifestValueValidatorCollection _validators; + + /// + /// Initializes a new instance of the class. + /// + public ValueValidatorConverter(ManifestValueValidatorCollection validators) + { + _validators = validators; + } + + protected override IValueValidator Create(Type objectType, string path, JObject jObject) + { + var type = jObject["type"].Value(); + if (string.IsNullOrWhiteSpace(type)) + throw new InvalidOperationException("Could not get the type of the validator."); + + return _validators.Create(type); + + // jObject["configuration"] is going to be deserialized in a Configuration property, if any + } + } +} diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 66f07b121b..484b761661 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -153,7 +153,7 @@ namespace Umbraco.Core.Migrations.Install private void CreateUser2UserGroupData() { - _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); + _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = Constants.Security.SuperId }); } private void CreateUserGroup2AppData() diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index 8b0e7d5327..9668864588 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -57,7 +57,7 @@ namespace Umbraco.Core.Models var configuration = Configuration; var json = JsonConvert.SerializeObject(configuration); _editor = value; - Configuration = _editor.ConfigurationEditor.FromDatabase(json); + Configuration = _editor.GetConfigurationEditor().FromDatabase(json); } } @@ -85,7 +85,7 @@ namespace Umbraco.Core.Models if (_hasConfiguration) return _configuration; - _configuration = _editor.ConfigurationEditor.FromDatabase(_configurationJson); + _configuration = _editor.GetConfigurationEditor().FromDatabase(_configurationJson); _hasConfiguration = true; _configurationJson = null; @@ -102,7 +102,7 @@ namespace Umbraco.Core.Models throw new ArgumentException("Configurations are kinda non-mutable. Do not reassign the same object.", nameof(value)); // validate configuration type - if (!_editor.ConfigurationEditor.IsConfiguration(value)) + if (!_editor.GetConfigurationEditor().IsConfiguration(value)) throw new ArgumentException($"Value of type {value.GetType().Name} cannot be a configuration for editor {_editor.Alias}, expecting.", nameof(value)); // extract database type from configuration object, if appropriate @@ -167,7 +167,7 @@ namespace Umbraco.Core.Models // else, create a Lazy de-serializer var capturedConfiguration = _configurationJson; var capturedEditor = _editor; - return new Lazy(() => capturedEditor.ConfigurationEditor.FromDatabase(capturedConfiguration)); + return new Lazy(() => capturedEditor.GetConfigurationEditor().FromDatabase(capturedConfiguration)); } } } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 62853ac26a..b7589d71ff 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -1,8 +1,12 @@ using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Text.RegularExpressions; +using Umbraco.Core.Composing; using Umbraco.Core.Models.Entities; using Umbraco.Core.Strings; @@ -393,32 +397,10 @@ namespace Umbraco.Core.Models /// public bool IsPropertyValueValid(object value) { - var stringValue = Mandatory || !string.IsNullOrWhiteSpace(ValidationRegExp) - ? value?.ToString() - : null; - - // validate mandatory property value - if (Mandatory && string.IsNullOrWhiteSpace(stringValue)) - return false; - - // validate regular expression if appropriate (have a regex and a string value) - if (!string.IsNullOrWhiteSpace(ValidationRegExp) && !string.IsNullOrWhiteSpace(stringValue)) - { - try - { - return new Regex(ValidationRegExp).IsMatch(stringValue); - } - catch - { - throw new Exception($"Invalid validation expression on property {Alias}."); - } - } - - // fixme - todo - // ensure that the property value complies with the value storage type, ie can be saved - // plug PropertyEditor validation - when it's a thing - - return true; + var editor = Current.PropertyEditors[_propertyEditorAlias]; // fixme inject? + var configuration = Current.Services.DataTypeService.GetDataType(_dataTypeId).Configuration; // fixme inject? + var valueEditor = editor.GetValueEditor(configuration); + return !valueEditor.Validate(value, Mandatory, ValidationRegExp).Any(); } /// diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 44335b2f74..2ec9f43a53 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -37,10 +37,10 @@ namespace Umbraco.Core.Models /// /// Determines whether this user belongs to the administrators group. /// + /// The 'super' user does not automatically belongs to the administrators group. public static bool IsAdmin(this IUser user) { if (user == null) throw new ArgumentNullException(nameof(user)); - // fixme should super always be admin? return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); } diff --git a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs index d978d86556..c75b0496f3 100644 --- a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs +++ b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs @@ -1,17 +1,31 @@ using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models.Validation { /// - /// A custom validation attribute which adds additional metadata to the property to indicate that - /// the value is required to be persisted. + /// Specifies that a data field value is required in order to persist an object. /// /// - /// In Umbraco, we persist content even if it is invalid, however there are some properties that are absolutely required - /// in order to be persisted such as the Name of the content item. This attribute is re-usable to check for these types of - /// properties over any sort of model. + /// There are two levels of validation in Umbraco. (1) value validation is performed by + /// instances; it can prevent a content item from being published, but not from being saved. (2) required validation + /// of properties marked with ; it does prevent an object from being saved + /// and is used for properties that are absolutely mandatory, such as the name of a content item. /// public class RequiredForPersistenceAttribute : RequiredAttribute { + /// + /// Determines whether an object has all required values for persistence. + /// + internal static bool HasRequiredValuesForPersistence(object model) + { + return model.GetType().GetProperties().All(x => + { + var a = x.GetCustomAttribute(); + return a == null || a.IsValid(x.GetValue(model)); + }); + } } } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index bfa32bd06e..ae48d51a52 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -167,13 +167,7 @@ namespace Umbraco.Core.PropertyEditors return json?.PropertyName ?? property.Name; } - var dictionary = ObjectExtensions.ToObjectDictionary(configuration, FieldNamer); - - if (configuration is ConfigurationWithAdditionalData withAdditionalData) - foreach (var kv in withAdditionalData.GetAdditionalValues()) - dictionary[kv.Key] = kv.Value; - - return dictionary; + return ObjectExtensions.ToObjectDictionary(configuration, FieldNamer); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationWithAdditionalData.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationWithAdditionalData.cs deleted file mode 100644 index f9c2afec95..0000000000 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationWithAdditionalData.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.PropertyEditors -{ - /// - /// Provides a base class for configurations that support additional data. - /// - public abstract class ConfigurationWithAdditionalData - { - private Dictionary _values; - - // note: this class should NOT define ANY property, so that configuration - // classes that inherit from it, can do what they want with properties - - /// - /// Gets an additional value. - /// - /// No value exists for the key. - public object GetAdditionalValue(string key) - { - if (_values != null && _values.TryGetValue(key, out var obj)) - return obj; - throw new KeyNotFoundException($"No value exists for key \"{key}\"."); - } - - /// - /// Sets an additional value. - /// - public void SetAdditionalValue(string key, object value) - { - if (value == null) - { - if (_values == null) return; - _values.Remove(key); - } - else - { - if (_values == null) _values = new Dictionary(); - _values[key] = value; - } - } - - /// - /// Removes an additional value. - /// - public void RemoveAdditionalValue(string key) - { - _values?.Remove(key); - } - - /// - /// Determines whether the configuration contains a additional value. - /// - /// - /// - public bool ContainsAdditionalKey(string key) - => _values != null && _values.ContainsKey(key); - - /// - /// Tries to get an additional value. - /// - public bool TryGetAdditionalValue(string key, out object obj) - { - obj = null; - return _values != null && _values.TryGetValue(key, out obj); - } - - /// - /// Determines whether the configuration has additional values. - /// - /// - public bool HasAdditionalValues() - => _values != null && _values.Count > 0; - - /// - /// Gets additional values. - /// - public IEnumerable> GetAdditionalValues() - => _values ?? Enumerable.Empty>(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index 732250da00..5aba2a5e0e 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -20,7 +20,6 @@ namespace Umbraco.Core.PropertyEditors { private IDictionary _defaultConfiguration; private IDataValueEditor _valueEditorAssigned; - private IConfigurationEditor _configurationEditorAssigned; /// /// Initializes a new instance of the class. @@ -82,50 +81,68 @@ namespace Umbraco.Core.PropertyEditors /// /// - /// If an instance of a value editor is assigned to the property, - /// then this instance is returned when getting the property value. Otherwise, a - /// new instance is created by CreateValueEditor. + /// 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 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. - /// The property is *not* marked with json ObjectCreationHandling = ObjectCreationHandling.Replace, - /// so by default the deserializer will first try to read it before assigning it, which is why - /// all deserialization *should* set the property before anything (see manifest deserializer). /// - [JsonProperty("editor")] - public IDataValueEditor ValueEditor - { - // create a new value editor each time - the property editor can be a - // singleton, but the value editor will get a configuration which depends - // on the datatype, so it cannot be a singleton really - get => CreateValueEditor(); - set => _valueEditorAssigned = value; - } + // fixme point of that one? shouldn't we always configure? + public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor(); /// /// - /// If an instance of a configuration editor is assigned to the property, - /// then this instance is returned when getting the property value. Otherwise, a - /// new instance is created by CreateConfigurationEditor. - /// The instance created by CreateConfigurationEditor is not cached, i.e. + /// 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 although the configuration editor could - /// technically be a singleton too, we'd rather not keep configuration editor - /// cached. - /// The property is *not* marked with json ObjectCreationHandling = ObjectCreationHandling.Replace, - /// so by default the deserializer will first try to read it before assigning it, which is why - /// all deserialization *should* set the property before anything (see manifest deserializer). + /// 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. /// - [JsonProperty("config")] - public IConfigurationEditor ConfigurationEditor + public IDataValueEditor GetValueEditor(object configuration) { - get => CreateConfigurationEditor(); - set => _configurationEditorAssigned = value; + // 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; + + var editor = CreateValueEditor(); + ((DataValueEditor) editor).Configuration = configuration; // fixme casting is bad + return editor; } + /// + /// Gets or sets an explicit value editor. + /// + /// Used for manifest data editors. + [JsonProperty("editor")] + public IDataValueEditor ExplicitValueEditor { get; set; } + + /// + /// + /// 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(); + + /// + /// Gets or sets an explicit configuration editor. + /// + /// Used for manifest data editors. + [JsonProperty("config")] + public IConfigurationEditor ExplicitConfigurationEditor { get; set; } + /// [JsonProperty("defaultConfig")] public IDictionary DefaultConfiguration @@ -133,7 +150,7 @@ namespace Umbraco.Core.PropertyEditors // for property value editors, get the ConfigurationEditor.DefaultConfiguration // else fallback to a default, empty dictionary - get => _defaultConfiguration ?? ((Type & EditorType.PropertyValue) > 0 ? ConfigurationEditor.DefaultConfiguration : (_defaultConfiguration = new Dictionary())); + get => _defaultConfiguration ?? ((Type & EditorType.PropertyValue) > 0 ? GetConfigurationEditor().DefaultConfiguration : (_defaultConfiguration = new Dictionary())); set => _defaultConfiguration = value; } @@ -158,37 +175,9 @@ namespace Umbraco.Core.PropertyEditors /// protected virtual IConfigurationEditor CreateConfigurationEditor() { - // handle assigned editor - if (_configurationEditorAssigned != null) - return _configurationEditorAssigned; - - // else return an empty one return new ConfigurationEditor(); } - // fixme why are we implementing equality here? - - protected bool Equals(DataEditor other) - { - return string.Equals(Alias, other.Alias); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((DataEditor) obj); - } - - public override int GetHashCode() - { - // an internal setter is required for de-serialization from manifests - // but we are never going to change the alias once the editor exists - // ReSharper disable once NonReadonlyMemberInGetHashCode - return Alias.GetHashCode(); - } - /// /// Provides a summary of the PropertyEditor for use with the . /// diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs index d7b57c7560..9a9a105ca8 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs @@ -88,7 +88,7 @@ namespace Umbraco.Core.PropertyEditors public string Name { get; } /// - /// Gets the view to use to render the editor. fixme - but that's for the VALUE really? + /// Gets the view to use to render the editor. /// public string View { get; } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index c0221ebcfe..6ae55d94cb 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Globalization; +using System.Linq; using System.Xml.Linq; using Newtonsoft.Json; using Umbraco.Core.Composing; @@ -116,35 +118,48 @@ namespace Umbraco.Core.PropertyEditors [JsonProperty("valueType")] public string ValueType { get; set; } + /// + public IEnumerable Validate(object value, bool required, string format) + { + List results = null; + var r = Validators.SelectMany(v => v.Validate(value, ValueType, Configuration)).ToList(); + if (r.Any()) { results = r; } + + // mandatory and regex validators cannot be part of valueEditor.Validators because they + // depend on values that are not part of the configuration, .Mandatory and .ValidationRegEx, + // so they have to be explicitely invoked here. + + if (required) + { + r = RequiredValidator.ValidateRequired(value, ValueType).ToList(); + if (r.Any()) { if (results == null) results = r; else results.AddRange(r); } + } + + var stringValue = value?.ToString(); + if (!string.IsNullOrWhiteSpace(format) && !string.IsNullOrWhiteSpace(stringValue)) + { + r = FormatValidator.ValidateFormat(value, ValueType, format).ToList(); + if (r.Any()) { if (results == null) results = r; else results.AddRange(r); } + } + + return results ?? Enumerable.Empty(); + } + /// /// A collection of validators for the pre value editor /// [JsonProperty("validation")] public List Validators { get; private set; } - // fixme - need to explain and understand these two + what is "overridable pre-values" + /// + /// Gets the validator used to validate the special property type -level "required". + /// + public virtual IValueRequiredValidator RequiredValidator => new RequiredValidator(); /// - /// Returns the validator used for the required field validation which is specified on the PropertyType + /// Gets the validator used to validate the special property type -level "format". /// - /// - /// This will become legacy as soon as we implement overridable pre-values. - /// - /// The default validator used is the RequiredValueValidator but this can be overridden by property editors - /// if they need to do some custom validation, or if the value being validated is a json object. - /// - public virtual ManifestValidator RequiredValidator => new RequiredManifestValueValidator(); - - /// - /// Returns the validator used for the regular expression field validation which is specified on the PropertyType - /// - /// - /// This will become legacy as soon as we implement overridable pre-values. - /// - /// The default validator used is the RegexValueValidator but this can be overridden by property editors - /// if they need to do some custom validation, or if the value being validated is a json object. - /// - public virtual ManifestValidator RegexValidator => new RegexValidator(); + public virtual IValueFormatValidator FormatValidator => new RegexValidator(); /// /// If this is is true than the editor will be displayed full width without a label @@ -229,7 +244,7 @@ namespace Umbraco.Core.PropertyEditors /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the /// value to the DB will fail when it tries to validate the value type. /// - public virtual object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public virtual object FromEditor(ContentPropertyData editorValue, object currentValue) { //if it's json but it's empty json, then return null if (ValueType.InvariantEquals(ValueTypes.Json) && editorValue.Value != null && editorValue.Value.ToString().DetectIsEmptyJson()) @@ -257,7 +272,7 @@ namespace Umbraco.Core.PropertyEditors /// The object returned will automatically be serialized into json notation. For most property editors /// the value returned is probably just a string but in some cases a json structure will be returned. /// - public virtual object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public virtual object ToEditor(Property property, IDataTypeService dataTypeService) { if (property.GetValue() == null) return string.Empty; diff --git a/src/Umbraco.Core/PropertyEditors/IDataEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataEditor.cs index 48a1428358..8137101826 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataEditor.cs @@ -44,9 +44,14 @@ namespace Umbraco.Core.PropertyEditors bool IsDeprecated { get; } /// - /// Gets the value editor. + /// Gets a value editor. /// - IDataValueEditor ValueEditor { get; } // fixme should be a method - but, deserialization? + IDataValueEditor GetValueEditor(); // fixme - should be configured?! + + /// + /// Gets a configured value editor. + /// + IDataValueEditor GetValueEditor(object configuration); /// /// Gets the configuration for the value editor. @@ -54,9 +59,11 @@ namespace Umbraco.Core.PropertyEditors IDictionary DefaultConfiguration { get; } /// - /// Gets the editor to edit the value editor configuration. + /// Gets an editor to edit the value editor configuration. /// - /// Is expected to throw if the editor does not support being configured, e.g. for most parameter editors. - IConfigurationEditor ConfigurationEditor { get; } // fixme should be a method - but, deserialization? + /// + /// Is expected to throw if the editor does not support being configured, e.g. for most parameter editors. + /// + IConfigurationEditor GetConfigurationEditor(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs index 07a684486a..5b419d0ec8 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; @@ -33,19 +34,37 @@ namespace Umbraco.Core.PropertyEditors /// bool HideLabel { get; } + /// + /// Validates a property value. + /// + /// The property value. + /// A value indicating whether the property value is required. + /// A specific format (regex) that the property value must respect. + IEnumerable Validate(object value, bool required, string format); + /// /// Gets the validators to use to validate the edited value. /// + /// + /// Use this property to add validators, not to validate. Use instead. + /// fixme replace with AddValidator? WithValidator? + /// List Validators { get; } - // fixme what are these? - ManifestValidator RequiredValidator { get; } - ManifestValidator RegexValidator { get; } + /// + /// Converts a value posted by the editor to a property value. + /// + object FromEditor(ContentPropertyData editorValue, object currentValue); - // fixme services should be injected! - // fixme document - object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue); - object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService); + // fixme - editing - services should be injected + + /// + /// Converts a property value to a value for the editor. + /// + object ToEditor(Property property, IDataTypeService dataTypeService); + + // fixme - editing - document or remove these + // why property vs propertyType? services should be injected! etc... IEnumerable ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published); XNode ConvertDbToXml(PropertyType propertyType, object value, IDataTypeService dataTypeService); string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService); diff --git a/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs b/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs new file mode 100644 index 0000000000..559ea08bb7 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Defines a value validator that can be referenced in a manifest. + /// + /// If the manifest can be configured, then it should expose a Configuration property. + public interface IManifestValueValidator : IValueValidator + { + /// + /// Gets the name of the validator. + /// + string ValidationName { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs new file mode 100644 index 0000000000..3e4aea4385 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Defines a value format validator. + /// + public interface IValueFormatValidator + { + /// + /// Validates a value. + /// + /// The value to validate. + /// The value type. + /// A format definition. + /// Validation results. + /// + /// The is expected to be a valid regular expression. + /// This is used to validate values against the property type validation regular expression. + /// + IEnumerable ValidateFormat(object value, string valueType, string format); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs new file mode 100644 index 0000000000..f8e62788c8 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Defines a required value validator. + /// + public interface IValueRequiredValidator + { + /// + /// Validates a value. + /// + /// The value to validate. + /// The value type. + /// Validation results. + /// + /// This is used to validate values when the property type specifies that a value is required. + /// + IEnumerable ValidateRequired(object value, string valueType); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/IValueValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueValidator.cs index 0a82e44dd0..5c9bae71b5 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueValidator.cs @@ -12,8 +12,8 @@ namespace Umbraco.Core.PropertyEditors /// Validates a value. /// /// The value to validate. - /// The expected . - /// The datatype configuration. + /// The value type. + /// A datatype configuration. /// Validation results. /// /// The value can be a string, a Json structure (JObject, JArray...)... corresponding to what was posted by an editor. diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValidator.cs b/src/Umbraco.Core/PropertyEditors/ManifestValidator.cs deleted file mode 100644 index a0d4252fe3..0000000000 --- a/src/Umbraco.Core/PropertyEditors/ManifestValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Umbraco.Core.PropertyEditors -{ - /// - /// Represents a value validator that implements a named validation for manifets. - /// - public abstract class ManifestValidator - { - /// - /// Gets the validation name of this validator. - /// - public abstract string ValidationName { get; } - - /// - /// Validates a value. - /// - /// The value to validate. - /// The expected . - /// The datatype configuration. - /// The validator configuration, defined in the manifest. - /// Validation results. - /// - /// The value can be a string, a Json structure (JObject, JArray...)... corresponding to what was posted by an editor. - /// - public abstract IEnumerable Validate(object value, string valueType, object dataTypeConfiguration, object validatorConfiguration); - } -} diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValidatorCollection.cs b/src/Umbraco.Core/PropertyEditors/ManifestValidatorCollection.cs deleted file mode 100644 index 84fd35d1d0..0000000000 --- a/src/Umbraco.Core/PropertyEditors/ManifestValidatorCollection.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Composing; - -namespace Umbraco.Core.PropertyEditors -{ - public class ManifestValidatorCollection : BuilderCollectionBase - { - public ManifestValidatorCollection(IEnumerable items) - : base(items) - { } - - public ManifestValidator this[string name] => this.FirstOrDefault(x => x.ValidationName.InvariantEquals(name)); - } -} diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValidatorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/ManifestValidatorCollectionBuilder.cs deleted file mode 100644 index 87c3844971..0000000000 --- a/src/Umbraco.Core/PropertyEditors/ManifestValidatorCollectionBuilder.cs +++ /dev/null @@ -1,14 +0,0 @@ -using LightInject; -using Umbraco.Core.Composing; - -namespace Umbraco.Core.PropertyEditors -{ - internal class ManifestValidatorCollectionBuilder : LazyCollectionBuilderBase - { - public ManifestValidatorCollectionBuilder(IServiceContainer container) - : base(container) - { } - - protected override ManifestValidatorCollectionBuilder This => this; - } -} diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidator.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidator.cs deleted file mode 100644 index 708cd3191f..0000000000 --- a/src/Umbraco.Core/PropertyEditors/ManifestValueValidator.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Newtonsoft.Json; - -namespace Umbraco.Core.PropertyEditors -{ - /// - /// Represents a validator referenced in a package manifest. - /// - internal class ManifestValueValidator : IValueValidator - { - private readonly ManifestValidatorCollection _validators; - private ManifestValidator _validator; - - /// - /// Initializes a new instance of the class. - /// - public ManifestValueValidator(ManifestValidatorCollection validators) - { - _validators = validators; - } - - /// - /// Gets or sets the name of the validation. - /// - [JsonProperty("type", Required = Required.Always)] - public string ValidationName { get; set; } - - /// - /// The configuration defined for this validator in the manifest. - /// - /// - /// This has nothing to do with datatype configuration. - /// The value is deserialized Json, can be a string or a Json thing (JObject...). - /// - [JsonProperty("config")] - public object Config { get; set; } - - /// - public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) - { - if (_validator == null) - { - _validator = _validators[ValidationName]; - if (_validator == null) - throw new InvalidOperationException($"No manifest validator exists for validation name \"{ValidationName}\"."); - } - - // validates the value, using the manifest validator - return _validator.Validate(value, valueType, Config, dataTypeConfiguration); - } - } -} diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs new file mode 100644 index 0000000000..d16ed4bd62 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + public class ManifestValueValidatorCollection : BuilderCollectionBase + { + public ManifestValueValidatorCollection(IEnumerable items) + : base(items) + { } + + public IManifestValueValidator Create(string name) + { + var v = this.FirstOrDefault(x => x.ValidationName.InvariantEquals(name)); + if (v == null) + throw new InvalidOperationException($"Could not find a validator named \"{name}\"."); + + // FIXME + // we cannot return this instance, need to clone it? + return (IManifestValueValidator) Activator.CreateInstance(v.GetType()); // ouch + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs new file mode 100644 index 0000000000..d616ecf715 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs @@ -0,0 +1,14 @@ +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + internal class ManifestValueValidatorCollectionBuilder : LazyCollectionBuilderBase + { + public ManifestValueValidatorCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override ManifestValueValidatorCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs b/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs deleted file mode 100644 index a89a4ec97f..0000000000 --- a/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Umbraco.Core.Models; - -namespace Umbraco.Core.PropertyEditors -{ - /// - /// A validator that validates that the value is not null or empty (if it is a string) - /// - internal sealed class RequiredManifestValueValidator : ManifestValidator - { - /// - public override string ValidationName => "Required"; - - public override IEnumerable Validate(object value, string valueType, object dataTypeConfiguration, object validatorConfiguration) - { - //TODO: localize these! - - if (value == null) - { - yield return new ValidationResult("Value cannot be null", new[] {"value"}); - } - else - { - var asString = value.ToString(); - - if (valueType.InvariantEquals(ValueTypes.Json)) - { - if (asString.DetectIsEmptyJson()) - { - yield return new ValidationResult("Value cannot be empty", new[] { "value" }); - } - } - - if (asString.IsNullOrWhiteSpace()) - { - yield return new ValidationResult("Value cannot be empty", new[] { "value" }); - } - } - - - } - } -} diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs index ba9b37abb9..86db995566 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs @@ -6,16 +6,10 @@ namespace Umbraco.Core.PropertyEditors.Validators /// /// A validator that validates that the value is a valid decimal /// - internal sealed class DecimalValidator : ManifestValidator, IValueValidator + internal sealed class DecimalValidator : IManifestValueValidator { /// - public override string ValidationName => "Decimal"; - - /// - public override IEnumerable Validate(object value, string valueType, object dataTypeConfiguration, object validatorConfiguration) - { - return Validate(value, valueType, dataTypeConfiguration); - } + public string ValidationName => "Decimal"; /// public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DelimitedManifestValueValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs similarity index 80% rename from src/Umbraco.Core/PropertyEditors/Validators/DelimitedManifestValueValidator.cs rename to src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs index 4b7405fc53..3891d7952b 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DelimitedManifestValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs @@ -10,20 +10,25 @@ namespace Umbraco.Core.PropertyEditors.Validators /// /// A validator that validates a delimited set of values against a common regex /// - internal sealed class DelimitedManifestValueValidator : ManifestValidator + internal sealed class DelimitedValueValidator : IManifestValueValidator { /// - public override string ValidationName => "Delimited"; + public string ValidationName => "Delimited"; + + /// + /// Gets or sets the configuration, when parsed as . + /// + public JObject Configuration { get; set; } /// - public override IEnumerable Validate(object value, string valueType, object dataTypeConfiguration, object validatorConfiguration) + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { //TODO: localize these! if (value != null) { var delimiter = ","; Regex regex = null; - if (validatorConfiguration is JObject jobject) + if (Configuration is JObject jobject) { if (jobject["delimiter"] != null) { @@ -56,7 +61,6 @@ namespace Umbraco.Core.PropertyEditors.Validators } } } - } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs index d97eacbbb7..762b6dd7dd 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs @@ -6,16 +6,10 @@ namespace Umbraco.Core.PropertyEditors.Validators /// /// A validator that validates an email address /// - internal sealed class EmailValidator : ManifestValidator, IValueValidator + internal sealed class EmailValidator : IManifestValueValidator { /// - public override string ValidationName => "Email"; - - /// - public override IEnumerable Validate(object value, string valueType, object dataTypeConfiguration, object validatorConfiguration) - { - return Validate(value, valueType, dataTypeConfiguration); - } + public string ValidationName => "Email"; /// public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) diff --git a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs index 2db3fa68c2..335ddf7724 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs @@ -6,16 +6,12 @@ namespace Umbraco.Core.PropertyEditors.Validators /// /// A validator that validates that the value is a valid integer /// - internal sealed class IntegerValidator : ManifestValidator, IValueValidator + internal sealed class IntegerValidator : IManifestValueValidator { /// - public override string ValidationName => "Integer"; - - public override IEnumerable Validate(object value, string valueType, object dataTypeConfiguration, object validatorConfiguration) - { - return Validate(value, valueType, dataTypeConfiguration); - } + public string ValidationName => "Integer"; + /// public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { if (value != null && value.ToString() != string.Empty) diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs index ead56641a7..0727c0d24f 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs @@ -2,59 +2,71 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; +using Umbraco.Core.Exceptions; namespace Umbraco.Core.PropertyEditors.Validators { /// - /// A validator that validates that the value against a Regex expression + /// A validator that validates that the value against a regular expression. /// - internal sealed class RegexValidator : ManifestValidator, IValueValidator + internal sealed class RegexValidator : IValueFormatValidator, IManifestValueValidator { - private readonly string _regex; + private string _regex; - /// - public override string ValidationName => "Regex"; + /// + public string ValidationName => "Regex"; /// - /// Normally used when configured as a ManifestValueValidator + /// Initializes a new instance of the class. /// + /// Use this constructor when the validator is used as an , + /// and the regular expression is supplied at validation time. This constructor is also used when + /// the validator is used as an and the regular expression + /// is supplied via the method. public RegexValidator() { } /// - /// Normally used when configured as an IPropertyValidator + /// Initializes a new instance of the class. /// - /// + /// Use this constructor when the validator is used as an , + /// and the regular expression must be supplied when the validator is created. public RegexValidator(string regex) { - _regex = regex ?? throw new ArgumentNullException(nameof(regex)); + if (string.IsNullOrWhiteSpace(regex)) + throw new ArgumentNullOrEmptyException(nameof(regex)); + _regex = regex; } - /// - public override IEnumerable Validate(object value, string valueType, object dataTypeConfiguration, object validatorConfiguration) + /// + /// Gets or sets the configuration, when parsed as . + /// + public string Configuration { - //TODO: localize these! - if (validatorConfiguration is string regexSource && !string.IsNullOrWhiteSpace(regexSource) && value != null) + get => _regex; + set { - var asString = value.ToString(); - - var regex = new Regex(regexSource); - - if (regex.IsMatch(asString) == false) - { - yield return new ValidationResult("Value is invalid, it does not match the correct pattern", new[] { "value" }); - } + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentNullOrEmptyException(nameof(value)); + _regex = value; } } - /// + /// public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { if (_regex == null) - { - throw new InvalidOperationException("This validator is not configured as a " + typeof(IValueValidator)); - } - return Validate(value, valueType, dataTypeConfiguration, _regex); + throw new InvalidOperationException("The validator has not been configured."); + + return ValidateFormat(value, valueType, _regex); + } + + /// + public IEnumerable ValidateFormat(object value, string valueType, string format) + { + if (string.IsNullOrWhiteSpace(format)) throw new ArgumentNullOrEmptyException(nameof(format)); + if (value == null || !new Regex(format).IsMatch(value.ToString())) + yield return new ValidationResult("Value is invalid, it does not match the correct pattern", new[] { "value" }); } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs new file mode 100644 index 0000000000..bc3cf66caa --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Umbraco.Core.PropertyEditors.Validators +{ + /// + /// A validator that validates that the value is not null or empty (if it is a string) + /// + internal sealed class RequiredValidator : IValueRequiredValidator, IManifestValueValidator + { + /// + public string ValidationName => "Required"; + + /// + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) + { + return ValidateRequired(value, valueType); + } + + /// + public IEnumerable ValidateRequired(object value, string valueType) + { + if (value == null) + { + yield return new ValidationResult("Value cannot be null", new[] {"value"}); + yield break; + } + + if (valueType.InvariantEquals(ValueTypes.Json)) + { + if (value.ToString().DetectIsEmptyJson()) + yield return new ValidationResult("Value cannot be empty", new[] { "value" }); + yield break; + } + + if (value.ToString().IsNullOrWhiteSpace()) + { + yield return new ValidationResult("Value cannot be empty", new[] { "value" }); + } + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs index d9a56df9e2..243dccaf0f 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public override bool IsConverter(PublishedPropertyType propertyType) { return _propertyEditors.TryGet(propertyType.EditorAlias, out var editor) - && editor.ValueEditor.ValueType.InvariantEquals(ValueTypes.Json); + && editor.GetValueEditor().ValueType.InvariantEquals(ValueTypes.Json); } public override Type GetPropertyValueType(PublishedPropertyType propertyType) diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs index 6ef0cef08a..6a5a3f4dc0 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs @@ -59,10 +59,10 @@ namespace Umbraco.Core.Runtime composition.Container.RegisterSingleton(); // register our predefined validators - composition.Container.RegisterCollectionBuilder() - .Add() + composition.Container.RegisterCollectionBuilder() + .Add() .Add() - .Add() + .Add() .Add() .Add() .Add(); diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index 681b91f772..fa299ed54d 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -447,7 +447,7 @@ namespace Umbraco.Core.Services var propertyEditor = Current.PropertyEditors[propertyType.PropertyEditorAlias]; return propertyEditor == null ? Array.Empty() - : propertyEditor.ValueEditor.ConvertDbToXml(property, dataTypeService, localizationService, published); + : propertyEditor.GetValueEditor().ConvertDbToXml(property, dataTypeService, localizationService, published); } // exports an IContent item descendants. diff --git a/src/Umbraco.Core/Services/Implement/PackagingService.cs b/src/Umbraco.Core/Services/Implement/PackagingService.cs index 67c6249357..ea698fc8c9 100644 --- a/src/Umbraco.Core/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Core/Services/Implement/PackagingService.cs @@ -918,7 +918,7 @@ namespace Umbraco.Core.Services.Implement var configurationAttributeValue = dataTypeElement.Attribute("Configuration")?.Value; if (!string.IsNullOrWhiteSpace(configurationAttributeValue)) - dataType.Configuration = editor.ConfigurationEditor.FromDatabase(configurationAttributeValue); + dataType.Configuration = editor.GetConfigurationEditor().FromDatabase(configurationAttributeValue); dataTypes.Add(dataType); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 154c0a273a..94db77d6fe 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -333,7 +333,6 @@ - @@ -341,6 +340,9 @@ + + + @@ -515,7 +517,7 @@ - + @@ -1186,15 +1188,13 @@ - + - - @@ -1207,10 +1207,10 @@ - + - - + + diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 65c7427e38..607d34c56d 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; namespace Umbraco.Tests.Manifest { @@ -21,7 +22,12 @@ namespace Umbraco.Tests.Manifest [SetUp] public void Setup() { - _parser = new ManifestParser(NullCacheProvider.Instance, new ManifestValidatorCollection(Enumerable.Empty()), Mock.Of()); + var validators = new IManifestValueValidator[] + { + new RequiredValidator(), + new RegexValidator() + }; + _parser = new ManifestParser(NullCacheProvider.Instance, new ManifestValueValidatorCollection(validators), Mock.Of()); } [Test] @@ -170,7 +176,7 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.AreEqual("Test 1", editor.Name); Assert.IsFalse((editor.Type & EditorType.MacroParameter) > 0); - var valueEditor = editor.ValueEditor; + var valueEditor = editor.GetValueEditor(); Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/MyEditor.html", valueEditor.View); Assert.AreEqual("int", valueEditor.ValueType); Assert.IsTrue(valueEditor.HideLabel); @@ -182,21 +188,20 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 var validators = valueEditor.Validators; Assert.AreEqual(2, validators.Count); var validator = validators[0]; - var v = validator as ManifestValueValidator; - Assert.IsNotNull(v); - Assert.AreEqual("required", v.ValidationName); - Assert.AreEqual("", v.Config); + var v1 = validator as RequiredValidator; + Assert.IsNotNull(v1); + Assert.AreEqual("Required", v1.ValidationName); validator = validators[1]; - v = validator as ManifestValueValidator; - Assert.IsNotNull(v); - Assert.AreEqual("Regex", v.ValidationName); - Assert.AreEqual("\\d*", v.Config); + var v2 = validator as RegexValidator; + Assert.IsNotNull(v2); + Assert.AreEqual("Regex", v2.ValidationName); + Assert.AreEqual("\\d*", v2.Configuration); // this is not part of the manifest - var preValues = editor.ConfigurationEditor.DefaultConfiguration; + var preValues = editor.GetConfigurationEditor().DefaultConfiguration; Assert.IsEmpty(preValues); - var preValueEditor = editor.ConfigurationEditor; + var preValueEditor = editor.GetConfigurationEditor(); Assert.IsNotNull(preValueEditor); Assert.IsNotNull(preValueEditor.Fields); Assert.AreEqual(2, preValueEditor.Fields.Count); @@ -208,10 +213,9 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 var fvalidators = f.Validators; Assert.IsNotNull(fvalidators); Assert.AreEqual(1, fvalidators.Count); - var fv = fvalidators[0] as ManifestValueValidator; + var fv = fvalidators[0] as RequiredValidator; Assert.IsNotNull(fv); - Assert.AreEqual("required", fv.ValidationName); - Assert.AreEqual("", fv.Config); + Assert.AreEqual("Required", fv.ValidationName); f = preValueEditor.Fields[1]; Assert.AreEqual("key2", f.Key); @@ -257,13 +261,13 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.IsTrue(config.ContainsKey("key1")); Assert.AreEqual("some config val", config["key1"]); - var valueEditor = editor.ValueEditor; + var valueEditor = editor.GetValueEditor(); Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html", valueEditor.View); editor = manifest.ParameterEditors[2]; Assert.Throws(() => { - var _ = editor.ValueEditor; + var _ = editor.GetValueEditor(); }); } diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs index 599cf1af10..23a9eafe76 100644 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs @@ -23,7 +23,7 @@ namespace Umbraco.Tests.Models.Mapping var manifestBuilder = new ManifestParser( CacheHelper.CreateDisabledCacheHelper().RuntimeCache, - new ManifestValidatorCollection(Enumerable.Empty()), + new ManifestValueValidatorCollection(Enumerable.Empty()), Logger) { Path = TestHelper.CurrentAssemblyDirectory diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index b797b0c4be..890d917ad3 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -1,13 +1,18 @@ using System; +using LightInject; +using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Models { - // fixme - need to reorg our tests! - // fixme - test IsDirty on properties - [TestFixture] public class VariationTests { @@ -16,6 +21,42 @@ namespace Umbraco.Tests.Models { // annoying, but content type wants short string helper ;( SettingsForTests.Reset(); + + // well, this is also annoying, but... + // validating a value is performed by its data editor, + // based upon the configuration in the data type, so we + // need to be able to retrieve them all... + + Current.Reset(); + var container = Mock.Of(); + Current.Container = container; + + var dataEditors = new DataEditorCollection(new IDataEditor[] + { + new DataEditor(Mock.Of()) { Alias = "editor", ExplicitValueEditor = new DataValueEditor("view") } + }); + var propertyEditors = new PropertyEditorCollection(dataEditors); + + var dataType = Mock.Of(); + Mock.Get(dataType) + .Setup(x => x.Configuration) + .Returns(null); + + var dataTypeService = Mock.Of(); + Mock.Get(dataTypeService) + .Setup(x => x.GetDataType(It.IsAny())) + .Returns(x => dataType); + + var serviceContext = new ServiceContext(dataTypeService: dataTypeService); + + Mock.Get(container) + .Setup(x => x.GetInstance(It.IsAny())) + .Returns(x => + { + if (x == typeof(PropertyEditorCollection)) return propertyEditors; + if (x == typeof(ServiceContext)) return serviceContext; + throw new Exception("oops"); + }); } [Test] diff --git a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs index fb39ffb87b..80c0868dfa 100644 --- a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -63,7 +63,9 @@ namespace Umbraco.Tests.PropertyEditors var prop = new Property(1, new PropertyType(dataType)); prop.SetValue("1234,4567,8910"); - var result = dataType.Editor.ValueEditor.ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService); + var valueEditor = dataType.Editor.GetValueEditor(); + ((DataValueEditor) valueEditor).Configuration = dataType.Configuration; + var result = valueEditor.ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService); Assert.AreEqual("Value 1,Value 2,Value 3", result); } @@ -90,7 +92,7 @@ namespace Umbraco.Tests.PropertyEditors var prop = new Property(1, new PropertyType(dataType)); prop.SetValue("1234"); - var result = dataType.Editor.ValueEditor.ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService); + var result = dataType.Editor.GetValueEditor().ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService); Assert.AreEqual("Value 2", result); } diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index 98ea6253f3..ff2ee0aebd 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -48,7 +48,7 @@ namespace Umbraco.Tests.PropertyEditors ValueType = ValueTypes.String }; - var result = valueEditor.ConvertDbToEditor(prop, prop.PropertyType, new Mock().Object); + var result = valueEditor.ToEditor(prop, new Mock().Object); Assert.AreEqual(isOk, !(result is string)); } @@ -138,7 +138,7 @@ namespace Umbraco.Tests.PropertyEditors ValueType = valueType }; - var result = valueEditor.ConvertDbToEditor(prop, prop.PropertyType, new Mock().Object); + var result = valueEditor.ToEditor(prop, new Mock().Object); Assert.AreEqual(expected, result); } @@ -154,7 +154,7 @@ namespace Umbraco.Tests.PropertyEditors var prop = new Property(1, new PropertyType("test", ValueStorageType.Decimal)); prop.SetValue(value); - var result = valueEditor.ConvertDbToEditor(prop, prop.PropertyType, new Mock().Object); + var result = valueEditor.ToEditor(prop, new Mock().Object); Assert.AreEqual("12.34", result); } @@ -169,7 +169,7 @@ namespace Umbraco.Tests.PropertyEditors var prop = new Property(1, new PropertyType("test", ValueStorageType.Decimal)); prop.SetValue(string.Empty); - var result = valueEditor.ConvertDbToEditor(prop, prop.PropertyType, new Mock().Object); + var result = valueEditor.ToEditor(prop, new Mock().Object); Assert.AreEqual(string.Empty, result); } @@ -185,7 +185,7 @@ namespace Umbraco.Tests.PropertyEditors var prop = new Property(1, new PropertyType("test", ValueStorageType.Date)); prop.SetValue(now); - var result = valueEditor.ConvertDbToEditor(prop, prop.PropertyType, new Mock().Object); + var result = valueEditor.ToEditor(prop, new Mock().Object); Assert.AreEqual(now.ToIsoString(), result); } } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 3694e13877..5dd11b2812 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1848,7 +1848,7 @@ namespace Umbraco.Tests.Services editorGroup.StartContentId = content1.Id; ServiceContext.UserService.Save(editorGroup); - var admin = ServiceContext.UserService.GetUserById(0); + var admin = ServiceContext.UserService.GetUserById(Constants.Security.SuperId); admin.StartContentIds = new[] {content1.Id}; ServiceContext.UserService.Save(admin); @@ -1865,7 +1865,7 @@ namespace Umbraco.Tests.Services })); Assert.IsTrue(ServiceContext.PublicAccessService.AddRule(content1, "test2", "test2").Success); - var user = ServiceContext.UserService.GetUserById(0); + var user = ServiceContext.UserService.GetUserById(Constants.Security.SuperId); var userGroup = ServiceContext.UserService.GetUserGroupByAlias(user.Groups.First().Alias); Assert.IsNotNull(ServiceContext.NotificationService.CreateNotification(user, content1, "X")); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 5a6f253151..7dd8061bf7 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -302,7 +302,7 @@ namespace Umbraco.Tests.Testing Container.RegisterSingleton(); // somehow property editor ends up wanting this - Container.RegisterCollectionBuilder(); + Container.RegisterCollectionBuilder(); Container.RegisterSingleton(); // note - don't register collections, use builders diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 17f7610a33..51996955b1 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -10,7 +10,9 @@ using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.ControllerTesting; @@ -96,6 +98,28 @@ namespace Umbraco.Tests.Web.Controllers } } + private void MockForGetPagedUsers() + { + Mock.Get(Current.SqlContext) + .Setup(x => x.Query()) + .Returns(new Query(Current.SqlContext)); + + var syntax = new SqlCeSyntaxProvider(); + + Mock.Get(Current.SqlContext) + .Setup(x => x.SqlSyntax) + .Returns(syntax); + + var mappers = new MapperCollection(new [] + { + new UserMapper() + }); + + Mock.Get(Current.SqlContext) + .Setup(x => x.Mappers) + .Returns(mappers); + } + [Test] public async System.Threading.Tasks.Task GetPagedUsers_Empty() { @@ -110,6 +134,8 @@ namespace Umbraco.Tests.Web.Controllers return usersController; } + MockForGetPagedUsers(); + var runner = new TestRunner(Factory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); @@ -140,6 +166,8 @@ namespace Umbraco.Tests.Web.Controllers return usersController; } + MockForGetPagedUsers(); + var runner = new TestRunner(Factory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 16e2d3e36b..b819046a9a 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -226,7 +226,7 @@ namespace Umbraco.Web.Composing public static ParameterEditorCollection ParameterEditors => CoreCurrent.ParameterEditors; - internal static ManifestValidatorCollection ManifestValidators => CoreCurrent.ManifestValidators; + internal static ManifestValueValidatorCollection ManifestValidators => CoreCurrent.ManifestValidators; internal static PackageActionCollection PackageActions => CoreCurrent.PackageActions; diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index c7569aa4eb..f8bf6b083c 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -24,6 +24,8 @@ using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Persistence.Querying; using Umbraco.Web.PublishedCache; using Umbraco.Core.Events; +using Umbraco.Core.Models.Validation; +using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors @@ -546,7 +548,7 @@ namespace Umbraco.Web.Editors // a message indicating this if (ModelState.IsValid == false) { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) + if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem) && IsCreatingAction(contentItem.Action)) { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the modelstate to the outgoing object and throw a validation message diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index e4194b4b6c..a020de1468 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -82,7 +82,7 @@ namespace Umbraco.Web.Editors // get the value editor // nothing to save/map if it is readonly - var valueEditor = propertyDto.PropertyEditor.ValueEditor; + var valueEditor = propertyDto.PropertyEditor.GetValueEditor(); if (valueEditor.IsReadOnly) continue; // get the property @@ -102,7 +102,7 @@ namespace Umbraco.Web.Editors }; // let the editor convert the value that was received, deal with files, etc - var value = valueEditor.ConvertEditorToDb(data, property.GetValue()); + var value = valueEditor.FromEditor(data, property.GetValue()); // set the value - tags are special var tagAttribute = propertyDto.PropertyEditor.GetTagAttribute(); diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index e7a72fb462..10a05069e9 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -131,8 +131,8 @@ namespace Umbraco.Web.Editors { Editor = dataTypeDiff.EditorAlias, Validation = new PropertyTypeValidation(), - View = editor.ValueEditor.View, - Config = editor.ConfigurationEditor.ToConfigurationEditor(configuration) + View = editor.GetValueEditor().View, + Config = editor.GetConfigurationEditor().ToConfigurationEditor(configuration) }; } diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index 227db6db31..030d59de8f 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -152,8 +152,8 @@ namespace Umbraco.Web.Editors ModelState.AddModelError("Alias", "A content type, media type or member type with this alias already exists"); } - //now let the external validators execute - ValidationHelper.ValidateEditorModelWithResolver(ModelState, contentTypeSave); + // execute the externam validators + EditorValidator.Validate(ModelState, contentTypeSave); if (ModelState.IsValid == false) { diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index ad33113720..1007107b57 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -211,7 +211,7 @@ namespace Umbraco.Web.Editors // and map to an actual configuration object var currentConfiguration = dataType.PersistedDataType.Configuration; var configurationDictionary = dataType.ConfigurationFields.ToDictionary(x => x.Key, x => x.Value); - var configuration = dataType.PropertyEditor.ConfigurationEditor.FromConfigurationEditor(configurationDictionary, currentConfiguration); + var configuration = dataType.PropertyEditor.GetConfigurationEditor().FromConfigurationEditor(configurationDictionary, currentConfiguration); dataType.PersistedDataType.Configuration = configuration; @@ -320,7 +320,7 @@ namespace Umbraco.Web.Editors { var propertyEditor = propertyEditors.SingleOrDefault(x => x.Alias == dataType.Alias); if (propertyEditor != null) - dataType.HasPrevalues = propertyEditor.ConfigurationEditor.Fields.Any(); ; + dataType.HasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); ; } var grouped = dataTypes @@ -347,7 +347,7 @@ namespace Umbraco.Web.Editors var propertyEditors = Current.PropertyEditors; foreach (var propertyEditor in propertyEditors) { - var hasPrevalues = propertyEditor.ConfigurationEditor.Fields.Any(); + var hasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); var basic = Mapper.Map(propertyEditor); basic.HasPrevalues = hasPrevalues; datatypes.Add(basic); diff --git a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs index 98342b9a6d..f8f2b5ac23 100644 --- a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs @@ -5,8 +5,11 @@ using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using AutoMapper; +using LightInject; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.WebApi; @@ -18,20 +21,23 @@ namespace Umbraco.Web.Editors /// internal sealed class DataTypeValidateAttribute : ActionFilterAttribute { + // LightInject can inject dependencies into properties + + [Inject] + public IDataTypeService DataTypeService { get; set; } + + [Inject] + public PropertyEditorCollection PropertyEditors { get; set; } + public override void OnActionExecuting(HttpActionContext actionContext) { - // injecting in attributes is not easy. - // eventually, actionContext should give access to the service factory - // but for the time being, have to rely on the global locator - var dataTypeService = Current.Services.DataTypeService; - var dataType = (DataTypeSave) actionContext.ActionArguments["dataType"]; dataType.Name = dataType.Name.CleanForXss('[', ']', '(', ')', ':'); dataType.Alias = dataType.Alias == null ? dataType.Name : dataType.Alias.CleanForXss('[', ']', '(', ')', ':'); // get the property editor, ensuring that it exits - if (!Current.PropertyEditors.TryGet(dataType.EditorAlias, out var propertyEditor)) + if (!PropertyEditors.TryGet(dataType.EditorAlias, out var propertyEditor)) { var message = $"Property editor \"{dataType.EditorAlias}\" was not found."; actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); @@ -46,7 +52,7 @@ namespace Umbraco.Web.Editors switch (dataType.Action) { case ContentSaveAction.Save: - persisted = dataTypeService.GetDataType(Convert.ToInt32(dataType.Id)); + persisted = DataTypeService.GetDataType(Convert.ToInt32(dataType.Id)); if (persisted == null) { var message = $"Data type with id {dataType.Id} was not found."; @@ -73,14 +79,15 @@ namespace Umbraco.Web.Editors // validate the configuration // which is posted as a set of fields with key (string) and value (object) + var configurationEditor = propertyEditor.GetConfigurationEditor(); foreach (var field in dataType.ConfigurationFields) { - var value = field.Value; - var editorField = propertyEditor.ConfigurationEditor.Fields.SingleOrDefault(x => x.Key == field.Key); + var editorField = configurationEditor.Fields.SingleOrDefault(x => x.Key == field.Key); if (editorField == null) continue; + // run each IValueValidator (with null valueType and dataTypeConfiguration: not relevant here) - fixme - editing foreach (var validator in editorField.Validators) - foreach (var result in validator.Validate(value, null, null)) + foreach (var result in validator.Validate(field.Value, null, null)) actionContext.ModelState.AddValidationError(result, "Properties", field.Key); } diff --git a/src/Umbraco.Web/Editors/EditorValidator.cs b/src/Umbraco.Web/Editors/EditorValidator.cs index d2b86116b8..2010fae943 100644 --- a/src/Umbraco.Web/Editors/EditorValidator.cs +++ b/src/Umbraco.Web/Editors/EditorValidator.cs @@ -1,21 +1,29 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Web.Http.ModelBinding; +using Umbraco.Web.Composing; namespace Umbraco.Web.Editors { - internal abstract class EditorValidator : IEditorValidator + /// + /// Provides a method to validate an object using validation. + /// + internal static class EditorValidator { - public Type ModelType + /// + /// Validates an object. + /// + public static void Validate(ModelStateDictionary modelState, object model) { - get { return typeof (T); } - } + var modelType = model.GetType(); - protected abstract IEnumerable PerformValidate(T model); + var validationResults = Current.EditorValidators // fixme inject + .Where(x => x.ModelType == modelType) + .SelectMany(x => x.Validate(model)) + .Where(x => !string.IsNullOrWhiteSpace(x.ErrorMessage) && x.MemberNames.Any()); - public IEnumerable Validate(object model) - { - return PerformValidate((T) model); + foreach (var r in validationResults) + foreach (var m in r.MemberNames) + modelState.AddModelError(m, r.ErrorMessage); } } } diff --git a/src/Umbraco.Web/Editors/EditorValidatorCollection.cs b/src/Umbraco.Web/Editors/EditorValidatorCollection.cs index 535d1133a6..6fc6bb5de2 100644 --- a/src/Umbraco.Web/Editors/EditorValidatorCollection.cs +++ b/src/Umbraco.Web/Editors/EditorValidatorCollection.cs @@ -11,14 +11,5 @@ namespace Umbraco.Web.Editors public EditorValidatorCollection(IEnumerable items) : base(items) { } - - public IEnumerable Validate(object model) - { - var modelType = model.GetType(); - return this - .Where(x => x.ModelType == modelType) - .WhereNotNull() - .SelectMany(x => x.Validate(model)); - } } } diff --git a/src/Umbraco.Web/Editors/EditorValidatorOfT.cs b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs new file mode 100644 index 0000000000..4ca008cf0d --- /dev/null +++ b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Umbraco.Web.Editors +{ + /// + /// Provides a base class for implementations. + /// + /// The validated object type. + internal abstract class EditorValidator : IEditorValidator + { + public Type ModelType => typeof (T); + + public IEnumerable Validate(object model) => Validate((T) model); + + protected abstract IEnumerable Validate(T model); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/IEditorValidator.cs b/src/Umbraco.Web/Editors/IEditorValidator.cs index bf6aa3c643..bec978788c 100644 --- a/src/Umbraco.Web/Editors/IEditorValidator.cs +++ b/src/Umbraco.Web/Editors/IEditorValidator.cs @@ -5,9 +5,32 @@ using Umbraco.Core.Composing; namespace Umbraco.Web.Editors { + // note - about IEditorValidator + // + // interface: IEditorValidator + // base class: EditorValidator + // static validation: EditorValidator.Validate() + // composition: via EditorValidationCollection and builder + // initialized with all IEditorValidator instances + // + // validation is used exclusively in ContentTypeControllerBase + // the whole thing is internal at the moment, never released + // and, there are no IEditorValidator implementation in Core + // so... this all mechanism is basically useless + + /// + /// Provides a general object validator. + /// internal interface IEditorValidator : IDiscoverable { + /// + /// Gets the object type validated by this validator. + /// Type ModelType { get; } + + /// + /// Validates an object. + /// IEnumerable Validate(object model); } } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index e77b5497b2..5287b06a00 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -36,6 +36,7 @@ using Notification = Umbraco.Web.Models.ContentEditing.Notification; using Umbraco.Core.Persistence; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models.Editors; +using Umbraco.Core.Models.Validation; namespace Umbraco.Web.Editors { @@ -481,7 +482,7 @@ namespace Umbraco.Web.Editors // a message indicating this if (ModelState.IsValid == false) { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) + if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem) && (contentItem.Action == ContentSaveAction.SaveNew)) { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 0ab42b9d7f..3bb1e60be2 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -205,8 +205,9 @@ namespace Umbraco.Web.Editors if (!Security.CurrentUser.IsSuper()) { - // only super can see super - filterQuery.Where(x => !x.IsSuper()); + // only super can see super - but don't use IsSuper, cannot be mapped to SQL - fixme NOW + //filterQuery.Where(x => !x.IsSuper()); + filterQuery.Where(x => x.Id != Constants.Security.SuperId); } if (filter.IsNullOrWhiteSpace() == false) diff --git a/src/Umbraco.Web/Editors/ValidationHelper.cs b/src/Umbraco.Web/Editors/ValidationHelper.cs deleted file mode 100644 index 09140d1ee1..0000000000 --- a/src/Umbraco.Web/Editors/ValidationHelper.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Web.Http.ModelBinding; -using Umbraco.Core; -using Umbraco.Core.Models.Validation; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Editors -{ - internal class ValidationHelper - { - internal static void ValidateEditorModelWithResolver(ModelStateDictionary modelState, object model) - { - var validationResult = Current.EditorValidators.Validate(model); - foreach (var vr in validationResult - .WhereNotNull() - .Where(x => x.ErrorMessage.IsNullOrWhiteSpace() == false) - .Where(x => x.MemberNames.Any())) - { - foreach (var memberName in vr.MemberNames) - { - modelState.AddModelError(memberName, vr.ErrorMessage); - } - } - } - - /// - /// This will check if any properties of the model are attributed with the RequiredForPersistenceAttribute attribute and if they are it will - /// check if that property validates, if it doesn't it means that the current model cannot be persisted because it doesn't have the necessary information - /// to be saved. - /// - /// - /// - /// - /// This is normally used for things like content creating when the name is empty since we cannot actually create a content item when the name is empty. - /// This is similar but different from the standard Required validator since we still allow content to be saved when validation fails but there are some - /// content fields that are absolutely mandatory for creating/saving. - /// - internal static bool ModelHasRequiredForPersistenceErrors(object model) - { - var requiredForPersistenceProperties = TypeDescriptor.GetProperties(model).Cast() - .Where(x => x.Attributes.Cast().Any(a => a is RequiredForPersistenceAttribute)); - - var validator = new RequiredForPersistenceAttribute(); - return requiredForPersistenceProperties.Any(p => !validator.IsValid(p.GetValue(model))); - } - } -} diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 73f474c46d..dff38938e0 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -126,7 +126,7 @@ namespace Umbraco.Web.Models.Mapping Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype", Label = localizedText.Localize("content/documentType"), Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), - View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].ValueEditor.View + View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View }, new ContentPropertyDisplay { @@ -134,7 +134,7 @@ namespace Umbraco.Web.Models.Mapping Label = localizedText.Localize("content/releaseDate"), Value = display.ReleaseDate?.ToIsoString(), //Not editible for people without publish permission (U4-287) - View = display.AllowedActions.Contains(ActionPublish.Instance.Letter.ToString(CultureInfo.InvariantCulture)) ? "datepicker" : Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].ValueEditor.View, + View = display.AllowedActions.Contains(ActionPublish.Instance.Letter.ToString(CultureInfo.InvariantCulture)) ? "datepicker" : Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View, Config = new Dictionary { {"offsetTime", "1"} @@ -147,7 +147,7 @@ namespace Umbraco.Web.Models.Mapping Label = localizedText.Localize("content/unpublishDate"), Value = display.ExpireDate.HasValue ? display.ExpireDate.Value.ToIsoString() : null, //Not editible for people without publish permission (U4-287) - View = display.AllowedActions.Contains(ActionPublish.Instance.Letter.ToString(CultureInfo.InvariantCulture)) ? "datepicker" : Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].ValueEditor.View, + View = display.AllowedActions.Contains(ActionPublish.Instance.Letter.ToString(CultureInfo.InvariantCulture)) ? "datepicker" : Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View, Config = new Dictionary { {"offsetTime", "1"} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index 6d703c1f6e..b4f0436660 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -15,11 +15,13 @@ namespace Umbraco.Web.Models.Mapping internal class ContentPropertyBasicConverter : ITypeConverter where TDestination : ContentPropertyBasic, new() { - protected Lazy DataTypeService { get; } + private readonly Lazy _dataTypeService; + + protected IDataTypeService DataTypeService => _dataTypeService.Value; public ContentPropertyBasicConverter(Lazy dataTypeService) { - DataTypeService = dataTypeService; + _dataTypeService = dataTypeService; } /// @@ -40,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping var result = new TDestination { Id = property.Id, - Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService.Value), + Value = editor.GetValueEditor().ToEditor(property, DataTypeService), Alias = property.Alias, PropertyEditor = editor, Editor = editor.Alias diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index 367fc89b1b..99540fc6ea 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -23,18 +23,16 @@ namespace Umbraco.Web.Models.Mapping { var display = base.Convert(originalProp, dest, context); - var dataTypeService = DataTypeService.Value; - var config = dataTypeService.GetDataType(originalProp.PropertyType.DataTypeId).Configuration; + var config = DataTypeService.GetDataType(originalProp.PropertyType.DataTypeId).Configuration; - //configure the editor for display with the pre-values - var valEditor = display.PropertyEditor.ValueEditor; - // fixme - the value editor REQUIRES the configuration to operate - // at the moment, only for richtext and nested, where it's used to set HideLabel - // but, this is the ONLY place where it's assigned? it is also the only place where - // .HideLabel is used - and basically all the rest kinda never depends on config, - // but... it should? - var ve = (DataValueEditor) valEditor; - ve.Configuration = config; + // fixme - IDataValueEditor configuration - general issue + // GetValueEditor() returns a non-configured IDataValueEditor + // - for richtext and nested, configuration determines HideLabel, so we need to configure the value editor + // - could configuration also determines ValueType, everywhere? + // - does it make any sense to use a IDataValueEditor without configuring it? + + // configure the editor for display with configuration + var valEditor = display.PropertyEditor.GetValueEditor(config); //set the display properties after mapping display.Alias = originalProp.Alias; @@ -56,7 +54,7 @@ namespace Umbraco.Web.Models.Mapping else { //let the property editor format the pre-values - display.Config = display.PropertyEditor.ConfigurationEditor.ToValueEditor(config); + display.Config = display.PropertyEditor.GetConfigurationEditor().ToValueEditor(config); display.View = valEditor.View; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs index 13b3be853b..c1cad75674 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -19,13 +19,11 @@ namespace Umbraco.Web.Models.Mapping { var propertyDto = base.Convert(originalProperty, dest, context); - var dataTypeService = DataTypeService.Value; - propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; propertyDto.Description = originalProperty.PropertyType.Description; propertyDto.Label = originalProperty.PropertyType.Name; - propertyDto.DataType = dataTypeService.GetDataType(originalProperty.PropertyType.DataTypeId); + propertyDto.DataType = DataTypeService.GetDataType(originalProperty.PropertyType.DataTypeId); return propertyDto; } diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs index 3e70f36839..9cdbd6101e 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; -using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; @@ -26,8 +24,8 @@ namespace Umbraco.Web.Models.Mapping { if (configuration.TryGetValue(field.Key, out var value)) field.Value = value; - else // fixme should this be fatal? - Current.Logger.Warn($"Could not find persisted pre-value for field \"{field.Key}\"."); + else // weird - just leave the field without a value - but warn + Current.Logger.Warn($"Could not find a value for configuration field \"{field.Key}\"."); } } @@ -36,26 +34,17 @@ namespace Umbraco.Web.Models.Mapping /// public IEnumerable Resolve(IDataType dataType) { - if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || !Current.PropertyEditors.TryGet(dataType.EditorAlias, out var e) || !(e is DataEditor editor)) + // in v7 it was apparently fine to have an empty .EditorAlias here, in which case we would map onto + // an empty fields list, which made no sense since there would be nothing to map to - and besides, + // a datatype without an editor alias is a serious issue - v8 wants an editor here + + if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || !Current.PropertyEditors.TryGet(dataType.EditorAlias, out var editor)) throw new InvalidOperationException($"Could not find a property editor with alias \"{dataType.EditorAlias}\"."); - var configuration = dataType.Configuration; - Dictionary configurationDictionary = null; - var fields = Array.Empty(); + var configurationEditor = editor.GetConfigurationEditor(); + var fields = configurationEditor.Fields.Select(Mapper.Map).ToArray(); + var configurationDictionary = configurationEditor.ToConfigurationEditor(dataType.Configuration); - // if we have a property editor, - // map the configuration editor field to display, - // and convert configuration to editor - if (editor != null) - { - var configurationEditor = editor.ConfigurationEditor; - fields = configurationEditor.Fields.Select(Mapper.Map).ToArray(); - configurationDictionary = configurationEditor.ToConfigurationEditor(configuration); - } - - if (configurationDictionary == null) - configurationDictionary = new Dictionary(); - MapConfigurationFields(fields, configurationDictionary); return fields; diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs index fa1146adae..92a0be1346 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Models.Mapping var configurationDisplayResolver = new DataTypeConfigurationFieldDisplayResolver(); var databaseTypeResolver = new DatabaseTypeResolver(); - CreateMap(); + CreateMap(); // map the standard properties, not the values CreateMap() @@ -36,7 +36,7 @@ namespace Umbraco.Web.Models.Mapping Constants.DataTypes.DefaultMembersListView }; - CreateMap() + CreateMap() .ForMember(dest => dest.Udi, opt => opt.Ignore()) .ForMember(dest => dest.HasPrevalues, opt => opt.Ignore()) .ForMember(dest => dest.IsSystemDataType, opt => opt.Ignore()) @@ -105,7 +105,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Editor, opt => opt.MapFrom(src => propertyEditors[src.EditorAlias])); //Converts a property editor to a new list of pre-value fields - used when creating a new data type or changing a data type with new pre-vals - CreateMap>() + CreateMap>() .ConvertUsing(src => { // this is a new data type, initialize default configuration @@ -113,7 +113,7 @@ namespace Umbraco.Web.Models.Mapping // get the configuration fields and map to UI, // get the configuration default values and map to UI - var configurationEditor = src.ConfigurationEditor; + var configurationEditor = src.GetConfigurationEditor(); var fields = configurationEditor.Fields.Select(Mapper.Map).ToArray(); diff --git a/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs b/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs index 540217d493..8cf32339f9 100644 --- a/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs @@ -13,13 +13,11 @@ namespace Umbraco.Web.Models.Mapping { public ValueStorageType Resolve(DataTypeSave source) { - var propertyEditor = Current.PropertyEditors[source.EditorAlias]; - if (propertyEditor == null) - { - throw new InvalidOperationException("Could not find property editor with id " + source.EditorAlias); - } + if (!Current.PropertyEditors.TryGet(source.EditorAlias, out var editor)) + throw new InvalidOperationException($"Could not find property editor \"{source.EditorAlias}\"."); - var valueType = propertyEditor.ValueEditor.ValueType; + // fixme - what about source.PropertyEditor? can we get the configuration here? 'cos it may change the storage type?! + var valueType = editor.GetValueEditor().ValueType; return ValueTypes.ToStorageType(valueType); } } diff --git a/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs index 8cc68ea77f..2e97697578 100644 --- a/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.Models.Mapping Current.Logger.Warn("Could not resolve a parameter editor with alias " + property.EditorAlias + ", a textbox will be rendered in it's place"); } - parameter.View = paramEditor.ValueEditor.View; + parameter.View = paramEditor.GetValueEditor().View; //set the config parameter.Configuration = paramEditor.DefaultConfiguration; }); diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs index 70329a0d48..6d3ae36759 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs @@ -96,7 +96,7 @@ namespace Umbraco.Web.Models.Mapping Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("content/mediatype"), Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), - View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].ValueEditor.View + View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View } }; diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs index 8c79ccc97c..24f5b5ebfd 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs @@ -179,7 +179,7 @@ namespace Umbraco.Web.Models.Mapping Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("content/membertype"), Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), - View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].ValueEditor.View + View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View }, GetLoginProperty(memberService, member, display, localizedText), new ContentPropertyDisplay diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs index 772aa23744..9a1aeda845 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs @@ -212,8 +212,8 @@ namespace Umbraco.Web.Models.Mapping Editor = p.PropertyEditorAlias, Validation = new PropertyTypeValidation {Mandatory = p.Mandatory, Pattern = p.ValidationRegExp}, Label = p.Name, - View = propertyEditor.ValueEditor.View, - Config = propertyEditor.ConfigurationEditor.ToConfigurationEditor(configuration), + View = propertyEditor.GetValueEditor().View, + Config = propertyEditor.GetConfigurationEditor().ToConfigurationEditor(configuration), //Value = "", GroupId = groupId, Inherited = inherited, diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index f3c9df26a8..696837c669 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.Models.Mapping //store the current props to append to the newly inserted ones var currProps = genericProps.Properties.ToArray(); - var labelEditor = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].ValueEditor.View; + var labelEditor = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View; var contentProps = new List { @@ -162,7 +162,7 @@ namespace Umbraco.Web.Models.Mapping IsActive = true }; - var listViewConfig = editor.ConfigurationEditor.ToConfigurationEditor(dt.Configuration); + var listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration); //add the entity type to the config listViewConfig["entityType"] = entityType; @@ -180,7 +180,7 @@ namespace Umbraco.Web.Models.Mapping Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", Label = "", Value = null, - View = editor.ValueEditor.View, + View = editor.GetValueEditor().View, HideLabel = true, Config = listViewConfig }); diff --git a/src/Umbraco.Web/Models/PublishedProperty.cs b/src/Umbraco.Web/Models/PublishedProperty.cs index 535b704e7b..b3bad63609 100644 --- a/src/Umbraco.Web/Models/PublishedProperty.cs +++ b/src/Umbraco.Web/Models/PublishedProperty.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.Models // nothing ensures that the two methods are consistent. if (e != null) - v = e.ValueEditor.ConvertDbToString(p.PropertyType, v, dataTypeService); + v = e.GetValueEditor().ConvertDbToString(p.PropertyType, v, dataTypeService); } return map(x, v); diff --git a/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs index b47cd6a0e5..728b4474a8 100644 --- a/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors Validators.Add(new DateTimeValidator()); } - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService) { var date = property.GetValue().TryConvertTo(); if (date.Success == false || date.Result == null) diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs index a4a682d95f..011e46475c 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -40,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors /// Other places (FileUploadPropertyEditor...) do NOT deal with multiple files, and our logic for reusing /// folders would NOT work, etc. /// - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { currentValue = currentValue ?? string.Empty; diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index ca7705aaa0..eb1af95def 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.PropertyEditors /// This is called to merge in the prevalue crops with the value that is saved - similar to the property value converter for the front-end /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService) { ImageCropperValue value; try @@ -45,7 +45,7 @@ namespace Umbraco.Web.PropertyEditors value = new ImageCropperValue { Src = property.GetValue().ToString() }; } - var dataType = dataTypeService.GetDataType(propertyType.DataTypeId); + var dataType = dataTypeService.GetDataType(property.PropertyType.DataTypeId); if (dataType?.Configuration != null) value.ApplyConfiguration(dataType.ConfigurationAs()); @@ -62,7 +62,7 @@ namespace Umbraco.Web.PropertyEditors /// editorValue.Value is used to figure out editorFile and, if it has been cleared, remove the old file - but /// it is editorValue.AdditionalData["files"] that is used to determine the actual file that has been uploaded. /// - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { // get the current path var currentPath = string.Empty; diff --git a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs index 66fdfec7ac..a30a48a241 100644 --- a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - // fixme - if deprecated, what's the alternative? + // fixme - MacroContainerPropertyEditor is deprecated, but what's the alternative? [DataEditor(Constants.PropertyEditors.Aliases.MacroContainer, "(Obsolete) Macro Picker", "macrocontainer", ValueType = ValueTypes.Text, Group="rich content", Icon="icon-settings-alt", IsDeprecated = true)] public class MacroContainerPropertyEditor : DataEditor { diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs index df2174ed09..76c334f254 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.PropertyEditors /// /// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end /// - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { var asArray = editorValue.Value as JArray; if (asArray == null) @@ -84,7 +84,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The legacy property editor saved this data as new line delimited! strange but we have to maintain that. /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService) { return property.GetValue() == null ? new JObject[] {} diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 3fbbab1510..95121e8b02 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -74,9 +74,10 @@ namespace Umbraco.Web.PropertyEditors if (value == null) throw new ArgumentNullException(nameof(value)); if (!(value is NestedContentConfiguration configuration)) - throw new ArgumentException($"Expected a {typeof(RichTextConfiguration).Name} instance, but got {value.GetType().Name}.", nameof(value)); - HideLabel = configuration.HideLabel.TryConvertTo().Result; + throw new ArgumentException($"Expected a {typeof(NestedContentConfiguration).Name} instance, but got {value.GetType().Name}.", nameof(value)); base.Configuration = value; + + HideLabel = configuration.HideLabel.TryConvertTo().Result; } } @@ -115,7 +116,9 @@ namespace Umbraco.Web.PropertyEditors { // convert the value, and store the converted value var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - var convValue = propEditor.ValueEditor.ConvertDbToString(propType, propValues[propAlias]?.ToString(), dataTypeService); + var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; + var valEditor = propEditor.GetValueEditor(tempConfig); + var convValue = valEditor.ConvertDbToString(propType, propValues[propAlias]?.ToString(), dataTypeService); propValues[propAlias] = convValue; } catch (InvalidOperationException) @@ -136,7 +139,7 @@ namespace Umbraco.Web.PropertyEditors // note: there is NO variant support here - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService) { if (property.GetValue() == null || string.IsNullOrWhiteSpace(property.GetValue().ToString())) return string.Empty; @@ -173,7 +176,9 @@ namespace Umbraco.Web.PropertyEditors // convert that temp property, and store the converted value var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - var convValue = propEditor.ValueEditor.ConvertDbToEditor(tempProp, propType, dataTypeService); + var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; + var valEditor = propEditor.GetValueEditor(tempConfig); + var convValue = valEditor.ToEditor(tempProp, dataTypeService); propValues[propAlias] = convValue == null ? null : JToken.FromObject(convValue); } catch (InvalidOperationException) @@ -190,7 +195,7 @@ namespace Umbraco.Web.PropertyEditors return value; } - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString())) return null; @@ -240,7 +245,7 @@ namespace Umbraco.Web.PropertyEditors var contentPropData = new ContentPropertyData(propValues[propKey], propConfiguration); // Get the property editor to do it's conversion - var newValue = propEditor.ValueEditor.ConvertEditorToDb(contentPropData, propValues[propKey]); + var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, propValues[propKey]); // Store the value back propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue); @@ -289,9 +294,9 @@ namespace Umbraco.Web.PropertyEditors var config = dataTypeService.GetDataType(propType.DataTypeId).Configuration; var propertyEditor = _propertyEditors[propType.PropertyEditorAlias]; - foreach (var validator in propertyEditor.ValueEditor.Validators) + foreach (var validator in propertyEditor.GetValueEditor().Validators) { - foreach (var result in validator.Validate(propValues[propKey], propertyEditor.ValueEditor.ValueType, config)) + foreach (var result in validator.Validate(propValues[propKey], propertyEditor.GetValueEditor().ValueType, config)) { result.ErrorMessage = "Item " + (i + 1) + " '" + propType.Name + "' " + result.ErrorMessage; yield return result; diff --git a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs index a05f04224a..803c8b5994 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs @@ -72,9 +72,9 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService) { - var delimited = base.ConvertDbToEditor(property, propertyType, dataTypeService).ToString(); + var delimited = base.ToEditor(property, dataTypeService).ToString(); return delimited.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } @@ -85,7 +85,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - public override object ConvertEditorToDb(Core.Models.Editors.ContentPropertyData editorValue, object currentValue) + public override object FromEditor(Core.Models.Editors.ContentPropertyData editorValue, object currentValue) { var json = editorValue.Value as JArray; if (json == null) diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index ec499d9f01..56fd24bb8a 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -50,10 +50,10 @@ namespace Umbraco.Web.PropertyEditors throw new ArgumentNullException(nameof(value)); if (!(value is RichTextConfiguration configuration)) throw new ArgumentException($"Expected a {typeof(RichTextConfiguration).Name} instance, but got {value.GetType().Name}.", nameof(value)); - HideLabel = configuration.HideLabel; base.Configuration = value; - } + HideLabel = configuration.HideLabel; + } } /// @@ -63,7 +63,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService) { if (property.GetValue() == null) return null; @@ -78,7 +78,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - public override object ConvertEditorToDb(Core.Models.Editors.ContentPropertyData editorValue, object currentValue) + public override object FromEditor(Core.Models.Editors.ContentPropertyData editorValue, object currentValue) { if (editorValue.Value == null) return null; diff --git a/src/Umbraco.Web/PropertyEditors/TagConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/TagConfigurationEditor.cs index aa823ea5a9..3a68c878b6 100644 --- a/src/Umbraco.Web/PropertyEditors/TagConfigurationEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagConfigurationEditor.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; namespace Umbraco.Web.PropertyEditors { @@ -12,9 +13,9 @@ namespace Umbraco.Web.PropertyEditors /// public class TagConfigurationEditor : ConfigurationEditor { - public TagConfigurationEditor(ManifestValidatorCollection validators) + public TagConfigurationEditor(ManifestValueValidatorCollection validators) { - Fields.Add(new ConfigurationField(new ManifestValueValidator(validators) { ValidationName = "Required" }) + Fields.Add(new ConfigurationField(new RequiredValidator()) { Description = "Define a tag group", Key = "group", @@ -23,7 +24,7 @@ namespace Umbraco.Web.PropertyEditors View = "requiredfield" }); - Fields.Add(new ConfigurationField(new ManifestValueValidator(validators) {ValidationName = "Required"}) + Fields.Add(new ConfigurationField(new RequiredValidator()) { Description = "Select whether to store the tags in cache as CSV (default) or as JSON. The only benefits of storage as JSON is that you are able to have commas in a tag value but this will require parsing the json in your views or using a property value converter", Key = "storageType", diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index e863c662a7..88a8c92ba3 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -17,9 +17,9 @@ namespace Umbraco.Web.PropertyEditors [DataEditor(Constants.PropertyEditors.Aliases.Tags, "Tags", "tags", Icon="icon-tags")] public class TagsPropertyEditor : DataEditor { - private readonly ManifestValidatorCollection _validators; + private readonly ManifestValueValidatorCollection _validators; - public TagsPropertyEditor(ManifestValidatorCollection validators, ILogger logger) + public TagsPropertyEditor(ManifestValueValidatorCollection validators, ILogger logger) : base(logger) { _validators = validators; @@ -36,7 +36,7 @@ namespace Umbraco.Web.PropertyEditors { } /// - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { return editorValue.Value is JArray json ? json.Select(x => x.Value()) @@ -44,41 +44,29 @@ namespace Umbraco.Web.PropertyEditors } /// - public override ManifestValidator RequiredValidator => new RequiredTagsValueValidator(); + public override IValueRequiredValidator RequiredValidator => new RequiredJsonValueValidator(); /// - /// Custom validator to validate a required value against an empty json value + /// Custom validator to validate a required value against an empty json value. /// /// - /// This is required because the Tags property editor is not of type 'JSON', it's just string so the underlying - /// validator does not validate against an empty json string + /// This validator is required because the default RequiredValidator uses ValueType to + /// determine whether a property value is JSON, and for tags the ValueType is string although + /// the underlying data is JSON. Yes, this makes little sense. /// - private class RequiredTagsValueValidator : ManifestValidator + private class RequiredJsonValueValidator : IValueRequiredValidator { /// - public override string ValidationName => "Required"; - - /// - public override IEnumerable Validate(object value, string valueType, object dataTypeConfiguration, object validatorConfiguration) + public IEnumerable ValidateRequired(object value, string valueType) { if (value == null) { - yield return new ValidationResult("Value cannot be null", new[] { "value" }); + yield return new ValidationResult("Value cannot be null", new[] {"value"}); + yield break; } - else - { - var asString = value.ToString(); - if (asString.DetectIsEmptyJson()) - { - yield return new ValidationResult("Value cannot be empty", new[] { "value" }); - } - - if (asString.IsNullOrWhiteSpace()) - { - yield return new ValidationResult("Value cannot be empty", new[] { "value" }); - } - } + if (value.ToString().DetectIsEmptyJson()) + yield return new ValidationResult("Value cannot be empty", new[] { "value" }); } } } diff --git a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs index 6f6598576a..8c056f5ff7 100644 --- a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs @@ -25,7 +25,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The object returned will always be a string and if the database type is not a valid string type an exception is thrown /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService) { if (property.GetValue() == null) return string.Empty; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 224a35f3c3..bf1fd2473b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -757,7 +757,7 @@ namespace Umbraco.Web.PublishedCache.NuCache using (_contentStore.GetWriter(_scopeProvider)) using (_mediaStore.GetWriter(_scopeProvider)) { - // fixme - datatype lock + // fixme - need to add a datatype lock // this is triggering datatypes reload in the factory, and right after we create some // content types by loading them ... there's a race condition here, which would require // some locking on datatypes diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6e12f51097..86545139a6 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -133,6 +133,7 @@ + @@ -812,7 +813,6 @@ - diff --git a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs index aab19481b9..02396fc92f 100644 --- a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http.Controllers; -using System.Web.Http.ModelBinding; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -117,64 +114,23 @@ namespace Umbraco.Web.WebApi.Filters foreach (var p in postedItem.ContentDto.Properties) { var editor = p.PropertyEditor; + if (editor == null) { - var message = string.Format("The property editor with alias: {0} was not found for property with id {1}", p.DataType.EditorAlias, p.Id); + var message = $"Could not find property editor \"{p.DataType.EditorAlias}\" for property with id {p.Id}."; Current.Logger.Warn>(message); //actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); //return false; continue; } - //get the posted value for this property + // get the posted value var postedValue = postedItem.Properties.Single(x => x.Alias == p.Alias).Value; - //get the pre-values for this property - var preValues = p.DataType.Configuration; - - // fixme what does this mean? - //TODO: when we figure out how to 'override' certain pre-value properties we'll either need to: - // * Combine the preValues with the overridden values stored with the document type property (but how to combine?) - // * Or, pass in the overridden values stored with the doc type property separately - - foreach (var result in editor.ValueEditor.Validators.SelectMany(v => v.Validate(postedValue, editor.ValueEditor.ValueType, preValues))) - { - actionContext.ModelState.AddPropertyError(result, p.Alias); - } - - //Now we need to validate the property based on the PropertyType validation (i.e. regex and required) - // NOTE: These will become legacy once we have pre-value overrides. - if (p.IsRequired) - { - foreach (var result in p.PropertyEditor.ValueEditor.RequiredValidator.Validate(postedValue, editor.ValueEditor.ValueType, preValues, null)) - { - actionContext.ModelState.AddPropertyError(result, p.Alias); - } - } - - if (p.ValidationRegExp.IsNullOrWhiteSpace() == false) - { - - //We only want to execute the regex statement if: - // * the value is null or empty AND it is required OR - // * the value is not null or empty - //See: http://issues.umbraco.org/issue/U4-4669 - - var asString = postedValue as string; - - if ( - //Value is not null or empty - (postedValue != null && asString.IsNullOrWhiteSpace() == false) - //It's required - || (p.IsRequired)) - { - foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, null, preValues, p.ValidationRegExp)) - { - actionContext.ModelState.AddPropertyError(result, p.Alias); - } - } - - } + // validate + var valueEditor = editor.GetValueEditor(p.DataType.Configuration); + foreach (var r in valueEditor.Validate(postedValue, p.IsRequired, p.ValidationRegExp)) + actionContext.ModelState.AddPropertyError(r, p.Alias); } return actionContext.ModelState.IsValid;