Merge pull request #7459 from umbraco/netcore/feature/AB4519-move-property-editors-to-infrastructure

Netcore: Move property editors to infrastructure (1st part)
This commit is contained in:
Bjarke Berg
2020-01-15 10:33:08 +01:00
committed by GitHub
56 changed files with 55 additions and 116 deletions

View File

@@ -1,49 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// A property editor to allow multiple checkbox selection of pre-defined items.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.CheckBoxList,
"Checkbox list",
"checkboxlist",
Icon = "icon-bulleted-list",
Group = Constants.PropertyEditors.Groups.Lists)]
public class CheckBoxListPropertyEditor : DataEditor
{
private readonly ILocalizedTextService _textService;
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizationService _localizationService;
private readonly IShortStringHelper _shortStringHelper;
private readonly IIOHelper _ioHelper;
private readonly ILocalizedTextService _localizedTextService;
/// <summary>
/// The constructor will setup the property editor based on the attribute if one is found
/// </summary>
public CheckBoxListPropertyEditor(ILogger logger, ILocalizedTextService textService, IDataTypeService dataTypeService, ILocalizationService localizationService, IShortStringHelper shortStringHelper, IIOHelper ioHelper, ILocalizedTextService localizedTextService)
: base(logger, dataTypeService, localizationService,localizedTextService, shortStringHelper)
{
_textService = textService;
_dataTypeService = dataTypeService;
_localizationService = localizationService;
_shortStringHelper = shortStringHelper;
_ioHelper = ioHelper;
_localizedTextService = localizedTextService;
}
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new ValueListConfigurationEditor(_textService, _ioHelper);
/// <inheritdoc />
protected override IDataValueEditor CreateValueEditor() => new MultipleValueEditor(Logger, _dataTypeService, _localizationService, _localizedTextService, _shortStringHelper, Attribute);
}
}

View File

@@ -1,175 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
internal class ColorPickerConfigurationEditor : ConfigurationEditor<ColorPickerConfiguration>
{
public ColorPickerConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
var items = Fields.First(x => x.Key == "items");
// customize the items field
items.View = "views/propertyeditors/colorpicker/colorpicker.prevalues.html";
items.Description = "Add, remove or sort colors";
items.Name = "Colors";
items.Validators.Add(new ColorListValidator());
}
public override Dictionary<string, object> ToConfigurationEditor(ColorPickerConfiguration configuration)
{
var configuredItems = configuration?.Items; // ordered
object editorItems;
if (configuredItems == null)
{
editorItems = new object();
}
else
{
var d = new Dictionary<string, object>();
editorItems = d;
var sortOrder = 0;
foreach (var item in configuredItems)
d[item.Id.ToString()] = GetItemValue(item, configuration.UseLabel, sortOrder++);
}
var useLabel = configuration?.UseLabel ?? false;
return new Dictionary<string, object>
{
{ "items", editorItems },
{ "useLabel", useLabel }
};
}
private object GetItemValue(ValueListConfiguration.ValueListItem item, bool useLabel, int sortOrder)
{
// in: ValueListItem, Id = <id>, Value = <color> | { "value": "<color>", "label": "<label>" }
// (depending on useLabel)
// out: { "value": "<color>", "label": "<label>", "sortOrder": <sortOrder> }
var v = new ItemValue
{
Color = item.Value,
Label = item.Value,
SortOrder = sortOrder
};
if (item.Value.DetectIsJson())
{
try
{
var o = JsonConvert.DeserializeObject<ItemValue>(item.Value);
o.SortOrder = sortOrder;
return o;
}
catch
{
// parsing Json failed, don't do anything, get the value (sure?)
return new ItemValue { Color = item.Value, Label = item.Value, SortOrder = sortOrder };
}
}
return new ItemValue { Color = item.Value, Label = item.Value, SortOrder = sortOrder };
}
// represents an item we are exchanging with the editor
private class ItemValue
{
[JsonProperty("value")]
public string Color { get; set; }
[JsonProperty("label")]
public string Label { get; set; }
[JsonProperty("sortOrder")]
public int SortOrder { get; set; }
}
// send: { "items": { "<id>": { "value": "<color>", "label": "<label>", "sortOrder": <sortOrder> } , ... }, "useLabel": <bool> }
// recv: { "items": ..., "useLabel": <bool> }
public override ColorPickerConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, ColorPickerConfiguration configuration)
{
var output = new ColorPickerConfiguration();
if (!editorValues.TryGetValue("items", out var jjj) || !(jjj is JArray jItems))
return output; // oops
// handle useLabel
if (editorValues.TryGetValue("useLabel", out var useLabelObj))
{
var convertBool = useLabelObj.TryConvertTo<bool>();
if (convertBool.Success)
output.UseLabel = convertBool.Result;
}
// auto-assigning our ids, get next id from existing values
var nextId = 1;
if (configuration?.Items != null && configuration.Items.Count > 0)
nextId = configuration.Items.Max(x => x.Id) + 1;
// create ValueListItem instances - ordered (items get submitted in the sorted order)
foreach (var item in jItems.OfType<JObject>())
{
// in: { "value": "<color>", "id": <id>, "label": "<label>" }
// out: ValueListItem, Id = <id>, Value = <color> | { "value": "<color>", "label": "<label>" }
// (depending on useLabel)
var value = item.Property("value")?.Value?.Value<string>();
if (string.IsNullOrWhiteSpace(value)) continue;
var id = item.Property("id")?.Value?.Value<int>() ?? 0;
if (id >= nextId) nextId = id + 1;
var label = item.Property("label")?.Value?.Value<string>();
value = JsonConvert.SerializeObject(new { value, label });
output.Items.Add(new ValueListConfiguration.ValueListItem { Id = id, Value = value });
}
// ensure ids
foreach (var item in output.Items)
if (item.Id == 0)
item.Id = nextId++;
return output;
}
internal class ColorListValidator : IValueValidator
{
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)
{
if (!(value is JArray json)) yield break;
//validate each item which is a json object
for (var index = 0; index < json.Count; index++)
{
var i = json[index];
if (!(i is JObject jItem) || jItem["value"] == null) continue;
//NOTE: we will be removing empty values when persisting so no need to validate
var asString = jItem["value"].ToString();
if (asString.IsNullOrWhiteSpace()) continue;
if (Regex.IsMatch(asString, "^([0-9a-f]{3}|[0-9a-f]{6})$", RegexOptions.IgnoreCase) == false)
{
yield return new ValidationResult("The value " + asString + " is not a valid hex color", new[]
{
//we'll make the server field the index number of the value so it can be wired up to the view
"item_" + index.ToInvariantString()
});
}
}
}
}
}
}

View File

@@ -1,30 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
[DataEditor(
Constants.PropertyEditors.Aliases.ColorPicker,
"Color Picker",
"colorpicker",
Icon = "icon-colorpicker",
Group = Constants.PropertyEditors.Groups.Pickers)]
public class ColorPickerPropertyEditor : DataEditor
{
private readonly IIOHelper _ioHelper;
public ColorPickerPropertyEditor(ILogger logger, IDataTypeService dataTypeService, ILocalizationService localizationService, IIOHelper ioHelper, IShortStringHelper shortStringHelper, ILocalizedTextService localizedTextService)
: base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper)
{
_ioHelper = ioHelper;
}
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new ColorPickerConfigurationEditor(_ioHelper);
}
}

View File

@@ -1,19 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
public class ContentPickerConfiguration : IIgnoreUserStartNodesConfig
{
[ConfigurationField("showOpenButton", "Show open button", "boolean", Description = "Opens the node in a dialog")]
public bool ShowOpenButton { get; set; }
[ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor
public Udi StartNodeId { get; set; }
[ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes,
"Ignore User Start Nodes", "boolean",
Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
public bool IgnoreUserStartNodes { get; set; }
}
}

View File

@@ -1,32 +0,0 @@
using System.Collections.Generic;
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
internal class ContentPickerConfigurationEditor : ConfigurationEditor<ContentPickerConfiguration>
{
public ContentPickerConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
// configure fields
// this is not part of ContentPickerConfiguration,
// but is required to configure the UI editor (when editing the configuration)
Field(nameof(ContentPickerConfiguration.StartNodeId))
.Config = new Dictionary<string, object> { { "idType", "udi" } };
}
public override IDictionary<string, object> ToValueEditor(object configuration)
{
// get the configuration fields
var d = base.ToValueEditor(configuration);
// add extra fields
// not part of ContentPickerConfiguration but used to configure the UI editor
d["showEditButton"] = false;
d["showPathOnHover"] = false;
d["idType"] = "udi";
return d;
}
}
}

View File

@@ -1,69 +0,0 @@
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Content property editor that stores UDI
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.ContentPicker,
EditorType.PropertyValue | EditorType.MacroParameter,
"Content Picker",
"contentpicker",
ValueType = ValueTypes.String,
Group = Constants.PropertyEditors.Groups.Pickers)]
public class ContentPickerPropertyEditor : DataEditor
{
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizationService _localizationService;
private readonly ILocalizedTextService _localizedTextService;
private readonly IIOHelper _ioHelper;
public ContentPickerPropertyEditor(
IDataTypeService dataTypeService,
ILocalizationService localizationService,
ILocalizedTextService localizedTextService,
ILogger logger,
IIOHelper ioHelper,
IShortStringHelper shortStringHelper)
: base(logger, dataTypeService,localizationService,localizedTextService, shortStringHelper)
{
_dataTypeService = dataTypeService;
_localizationService = localizationService;
_localizedTextService = localizedTextService;
_ioHelper = ioHelper;
}
protected override IConfigurationEditor CreateConfigurationEditor()
{
return new ContentPickerConfigurationEditor(_ioHelper);
}
protected override IDataValueEditor CreateValueEditor() => new ContentPickerPropertyValueEditor(_dataTypeService, _localizationService, _localizedTextService, ShortStringHelper, Attribute);
internal class ContentPickerPropertyValueEditor : DataValueEditor, IDataValueReference
{
public ContentPickerPropertyValueEditor(IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, DataEditorAttribute attribute) : base(dataTypeService, localizationService, localizedTextService, shortStringHelper, attribute)
{
}
public IEnumerable<UmbracoEntityReference> GetReferences(object value)
{
var asString = value is string str ? str : value?.ToString();
if (string.IsNullOrEmpty(asString)) yield break;
if (UdiParser.TryParse(asString, out var udi))
yield return new UmbracoEntityReference(udi);
}
}
}
}

View File

@@ -1,28 +0,0 @@
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editor for the datetime value editor.
/// </summary>
public class DateTimeConfigurationEditor : ConfigurationEditor<DateTimeConfiguration>
{
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);
var format = d["format"].ToString();
d["pickTime"] = format.ContainsAny(new string[] { "H", "m", "s" });
return d;
}
public DateTimeConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
}
}
}

View File

@@ -1,45 +0,0 @@
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents a date and time property editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.DateTime,
"Date/Time",
"datepicker",
ValueType = ValueTypes.DateTime,
Icon = "icon-time")]
public class DateTimePropertyEditor : DataEditor
{
private readonly IIOHelper _ioHelper;
/// <summary>
/// Initializes a new instance of the <see cref="DateTimePropertyEditor"/> class.
/// </summary>
/// <param name="logger"></param>
public DateTimePropertyEditor(ILogger logger, IIOHelper ioHelper, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper)
: base(logger, dataTypeService, localizationService,localizedTextService, shortStringHelper)
{
_ioHelper = ioHelper;
}
/// <inheritdoc />
protected override IDataValueEditor CreateValueEditor()
{
var editor = base.CreateValueEditor();
editor.Validators.Add(new DateTimeValidator());
return editor;
}
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new DateTimeConfigurationEditor(_ioHelper);
}
}

View File

@@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Used to validate if the value is a valid date/time
/// </summary>
internal class DateTimeValidator : IValueValidator
{
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)
{
//don't validate if empty
if (value == null || value.ToString().IsNullOrWhiteSpace())
{
yield break;
}
DateTime dt;
if (DateTime.TryParse(value.ToString(), out dt) == false)
{
yield return new ValidationResult(string.Format("The string value {0} cannot be parsed into a DateTime", value),
new[]
{
//we only store a single value for this editor so the 'member' or 'field'
// we'll associate this error with will simply be called 'value'
"value"
});
}
}
}
}

View File

@@ -1,34 +0,0 @@
using System;
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// CUstom value editor so we can serialize with the correct date format (excluding time)
/// and includes the date validator
/// </summary>
internal class DateValueEditor : DataValueEditor
{
public DateValueEditor(IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, DataEditorAttribute attribute)
: base(dataTypeService, localizationService, localizedTextService, shortStringHelper, attribute)
{
Validators.Add(new DateTimeValidator());
}
public override object ToEditor(IProperty property, string culture= null, string segment = null)
{
var date = property.GetValue(culture, segment).TryConvertTo<DateTime?>();
if (date.Success == false || date.Result == null)
{
return String.Empty;
}
//Dates will be formatted as yyyy-MM-dd
return date.Result.Value.ToString("yyyy-MM-dd");
}
}
}

View File

@@ -1,38 +0,0 @@
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored.
/// </summary>
internal class DecimalConfigurationEditor : ConfigurationEditor
{
public DecimalConfigurationEditor()
{
Fields.Add(new ConfigurationField(new DecimalValidator())
{
Description = "Enter the minimum amount of number to be entered",
Key = "min",
View = "decimal",
Name = "Minimum"
});
Fields.Add(new ConfigurationField(new DecimalValidator())
{
Description = "Enter the intervals amount between each step of number to be entered",
Key = "step",
View = "decimal",
Name = "Step Size"
});
Fields.Add(new ConfigurationField(new DecimalValidator())
{
Description = "Enter the maximum amount of number to be entered",
Key = "max",
View = "decimal",
Name = "Maximum"
});
}
}
}

View File

@@ -1,44 +0,0 @@
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents a decimal property and parameter editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.Decimal,
EditorType.PropertyValue | EditorType.MacroParameter,
"Decimal",
"decimal",
ValueType = ValueTypes.Decimal)]
public class DecimalPropertyEditor : DataEditor
{
/// <summary>
/// Initializes a new instance of the <see cref="DecimalPropertyEditor"/> class.
/// </summary>
public DecimalPropertyEditor(ILogger logger,
IDataTypeService dataTypeService,
ILocalizationService localizationService,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper)
: base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper)
{ }
/// <inheritdoc />
protected override IDataValueEditor CreateValueEditor()
{
var editor = base.CreateValueEditor();
editor.Validators.Add(new DecimalValidator());
return editor;
}
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new DecimalConfigurationEditor();
}
}

View File

@@ -1,80 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
namespace Umbraco.Web.PropertyEditors
{
internal class DropDownFlexibleConfigurationEditor : ConfigurationEditor<DropDownFlexibleConfiguration>
{
public DropDownFlexibleConfigurationEditor(ILocalizedTextService textService, IIOHelper ioHelper): base(ioHelper)
{
var items = Fields.First(x => x.Key == "items");
// customize the items field
items.Name = textService.Localize("editdatatype/addPrevalue");
items.Validators.Add(new ValueListUniqueValueValidator());
}
public override DropDownFlexibleConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, DropDownFlexibleConfiguration configuration)
{
var output = new DropDownFlexibleConfiguration();
if (!editorValues.TryGetValue("items", out var jjj) || !(jjj is JArray jItems))
return output; // oops
// handle multiple
if (editorValues.TryGetValue("multiple", out var multipleObj))
{
var convertBool = multipleObj.TryConvertTo<bool>();
if (convertBool.Success)
{
output.Multiple = convertBool.Result;
}
}
// auto-assigning our ids, get next id from existing values
var nextId = 1;
if (configuration?.Items != null && configuration.Items.Count > 0)
nextId = configuration.Items.Max(x => x.Id) + 1;
// create ValueListItem instances - sortOrder is ignored here
foreach (var item in jItems.OfType<JObject>())
{
var value = item.Property("value")?.Value?.Value<string>();
if (string.IsNullOrWhiteSpace(value)) continue;
var id = item.Property("id")?.Value?.Value<int>() ?? 0;
if (id >= nextId) nextId = id + 1;
output.Items.Add(new ValueListConfiguration.ValueListItem { Id = id, Value = value });
}
// ensure ids
foreach (var item in output.Items)
if (item.Id == 0)
item.Id = nextId++;
return output;
}
public override Dictionary<string, object> ToConfigurationEditor(DropDownFlexibleConfiguration configuration)
{
// map to what the editor expects
var i = 1;
var items = configuration?.Items.ToDictionary(x => x.Id.ToString(), x => new { value = x.Value, sortOrder = i++ }) ?? new object();
var multiple = configuration?.Multiple ?? false;
return new Dictionary<string, object>
{
{ "items", items },
{ "multiple", multiple }
};
}
}
}

View File

@@ -1,42 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
[DataEditor(
Constants.PropertyEditors.Aliases.DropDownListFlexible,
"Dropdown",
"dropdownFlexible",
Group = Constants.PropertyEditors.Groups.Lists,
Icon = "icon-indent")]
public class DropDownFlexiblePropertyEditor : DataEditor
{
private readonly ILocalizedTextService _textService;
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizationService _localizationService;
private readonly IShortStringHelper _shortStringHelper;
private readonly IIOHelper _ioHelper;
public DropDownFlexiblePropertyEditor(ILocalizedTextService textService, ILogger logger, IDataTypeService dataTypeService, ILocalizationService localizationService, IShortStringHelper shortStringHelper, IIOHelper ioHelper)
: base(logger, dataTypeService, localizationService, textService, shortStringHelper)
{
_textService = textService;
_dataTypeService = dataTypeService;
_localizationService = localizationService;
_shortStringHelper = shortStringHelper;
_ioHelper = ioHelper;
}
protected override IDataValueEditor CreateValueEditor()
{
return new MultipleValueEditor(Logger, _dataTypeService, _localizationService, _textService, _shortStringHelper, Attribute);
}
protected override IConfigurationEditor CreateConfigurationEditor() => new DropDownFlexibleConfigurationEditor(_textService, _ioHelper);
}
}

View File

@@ -1,13 +0,0 @@
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration for the email address value editor.
/// </summary>
public class EmailAddressConfiguration
{
[ConfigurationField("IsRequired", "Required?", "boolean")]
public bool IsRequired { get; set; }
}
}

View File

@@ -1,15 +0,0 @@
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editor for the email address value editor.
/// </summary>
public class EmailAddressConfigurationEditor : ConfigurationEditor<EmailAddressConfiguration>
{
public EmailAddressConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
}
}
}

View File

@@ -1,50 +0,0 @@
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
[DataEditor(
Constants.PropertyEditors.Aliases.EmailAddress,
EditorType.PropertyValue | EditorType.MacroParameter,
"Email address",
"email",
Icon = "icon-message")]
public class EmailAddressPropertyEditor : DataEditor
{
private readonly IIOHelper _ioHelper;
/// <summary>
/// The constructor will setup the property editor based on the attribute if one is found
/// </summary>
public EmailAddressPropertyEditor(
ILogger logger,
IIOHelper ioHelper,
IDataTypeService dataTypeService,
ILocalizationService localizationService,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper)
: base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper)
{
_ioHelper = ioHelper;
}
protected override IDataValueEditor CreateValueEditor()
{
var editor = base.CreateValueEditor();
//add an email address validator
editor.Validators.Add(new EmailValidator());
return editor;
}
protected override IConfigurationEditor CreateConfigurationEditor()
{
return new EmailAddressConfigurationEditor(_ioHelper);
}
}
}

View File

@@ -1,186 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Media;
namespace Umbraco.Web.PropertyEditors
{
[DataEditor(
Constants.PropertyEditors.Aliases.UploadField,
"File upload",
"fileupload",
Group = Constants.PropertyEditors.Groups.Media,
Icon = "icon-download-alt")]
public class FileUploadPropertyEditor : DataEditor, IDataEditorWithMediaPath
{
private readonly IMediaFileSystem _mediaFileSystem;
private readonly IContentSection _contentSection;
private readonly UploadAutoFillProperties _uploadAutoFillProperties;
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizationService _localizationService;
private readonly ILocalizedTextService _localizedTextService;
public FileUploadPropertyEditor(ILogger logger, IMediaFileSystem mediaFileSystem, IContentSection contentSection, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper)
: base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper)
{
_mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem));
_contentSection = contentSection;
_dataTypeService = dataTypeService;
_localizationService = localizationService;
_localizedTextService = localizedTextService;
_uploadAutoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, contentSection);
}
/// <summary>
/// Creates the corresponding property value editor.
/// </summary>
/// <returns>The corresponding property value editor.</returns>
protected override IDataValueEditor CreateValueEditor()
{
var editor = new FileUploadPropertyValueEditor(Attribute, _mediaFileSystem, _dataTypeService, _localizationService, _localizedTextService, ShortStringHelper);
editor.Validators.Add(new UploadFileTypeValidator(_localizedTextService));
return editor;
}
public string GetMediaPath(object value) => value?.ToString();
/// <summary>
/// Gets a value indicating whether a property is an upload field.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>A value indicating whether a property is an upload field, and (optionally) has a non-empty value.</returns>
private static bool IsUploadField(IProperty property)
{
return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField;
}
/// <summary>
/// Ensures any files associated are removed
/// </summary>
/// <param name="deletedEntities"></param>
internal IEnumerable<string> ServiceDeleted(IEnumerable<ContentBase> deletedEntities)
{
return deletedEntities.SelectMany(x => x.Properties)
.Where(IsUploadField)
.SelectMany(GetFilePathsFromPropertyValues)
.Distinct();
}
/// <summary>
/// Look through all property values stored against the property and resolve any file paths stored
/// </summary>
/// <param name="prop"></param>
/// <returns></returns>
private IEnumerable<string> GetFilePathsFromPropertyValues(IProperty prop)
{
var propVals = prop.Values;
foreach (var propertyValue in propVals)
{
//check if the published value contains data and return it
var propVal = propertyValue.PublishedValue;
if (propVal != null && propVal is string str1 && !str1.IsNullOrWhiteSpace())
yield return _mediaFileSystem.GetRelativePath(str1);
//check if the edited value contains data and return it
propVal = propertyValue.EditedValue;
if (propVal != null && propVal is string str2 && !str2.IsNullOrWhiteSpace())
yield return _mediaFileSystem.GetRelativePath(str2);
}
}
/// <summary>
/// After a content has been copied, also copy uploaded files.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
internal void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs<IContent> args)
{
// get the upload field properties with a value
var properties = args.Original.Properties.Where(IsUploadField);
// copy files
var isUpdated = false;
foreach (var property in properties)
{
//copy each of the property values (variants, segments) to the destination
foreach (var propertyValue in property.Values)
{
var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment);
if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) continue;
var sourcePath = _mediaFileSystem.GetRelativePath(str);
var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath);
args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.Culture, propertyValue.Segment);
isUpdated = true;
}
}
// if updated, re-save the copy with the updated value
if (isUpdated)
sender.Save(args.Copy);
}
/// <summary>
/// After a media has been created, auto-fill the properties.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
internal void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs<IMedia> args)
{
AutoFillProperties(args.Entity);
}
/// <summary>
/// After a media has been saved, auto-fill the properties.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
internal void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs<IMedia> args)
{
foreach (var entity in args.SavedEntities)
AutoFillProperties(entity);
}
/// <summary>
/// After a content item has been saved, auto-fill the properties.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
internal void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs<IContent> args)
{
foreach (var entity in args.SavedEntities)
AutoFillProperties(entity);
}
/// <summary>
/// Auto-fill properties (or clear).
/// </summary>
private void AutoFillProperties(IContentBase model)
{
var properties = model.Properties.Where(IsUploadField);
foreach (var property in properties)
{
var autoFillConfig = _contentSection.GetConfig(property.Alias);
if (autoFillConfig == null) continue;
foreach (var pvalue in property.Values)
{
var svalue = property.GetValue(pvalue.Culture, pvalue.Segment) as string;
if (string.IsNullOrWhiteSpace(svalue))
_uploadAutoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment);
else
_uploadAutoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(svalue), pvalue.Culture, pvalue.Segment);
}
}
}
}
}

View File

@@ -1,120 +0,0 @@
using System;
using System.IO;
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// The value editor for the file upload property editor.
/// </summary>
internal class FileUploadPropertyValueEditor : DataValueEditor
{
private readonly IMediaFileSystem _mediaFileSystem;
public FileUploadPropertyValueEditor(DataEditorAttribute attribute, IMediaFileSystem mediaFileSystem, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper)
: base(dataTypeService, localizationService, localizedTextService, shortStringHelper, attribute)
{
_mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem));
}
/// <summary>
/// Converts the value received from the editor into the value can be stored in the database.
/// </summary>
/// <param name="editorValue">The value received from the editor.</param>
/// <param name="currentValue">The current value of the property</param>
/// <returns>The converted value.</returns>
/// <remarks>
/// <para>The <paramref name="currentValue"/> is used to re-use the folder, if possible.</para>
/// <para>The <paramref name="editorValue"/> is value passed in from the editor. We normally don't care what
/// the editorValue.Value is set to because we are more interested in the files collection associated with it,
/// however we do care about the value if we are clearing files. By default the editorValue.Value will just
/// be set to the name of the file - but again, we just ignore this and deal with the file collection in
/// editorValue.AdditionalData.ContainsKey("files")</para>
/// <para>We only process ONE file. We understand that the current value may contain more than one file,
/// and that more than one file may be uploaded, so we take care of them all, but we only store ONE file.
/// Other places (FileUploadPropertyEditor...) do NOT deal with multiple files, and our logic for reusing
/// folders would NOT work, etc.</para>
/// </remarks>
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
{
var currentPath = currentValue as string;
if (!currentPath.IsNullOrWhiteSpace())
currentPath = _mediaFileSystem.GetRelativePath(currentPath);
string editorFile = null;
if (editorValue.Value != null)
{
editorFile = editorValue.Value as string;
}
// ensure we have the required guids
var cuid = editorValue.ContentKey;
if (cuid == Guid.Empty) throw new Exception("Invalid content key.");
var puid = editorValue.PropertyTypeKey;
if (puid == Guid.Empty) throw new Exception("Invalid property type key.");
var uploads = editorValue.Files;
if (uploads == null) throw new Exception("Invalid files.");
var file = uploads.Length > 0 ? uploads[0] : null;
if (file == null) // not uploading a file
{
// if editorFile is empty then either there was nothing to begin with,
// or it has been cleared and we need to remove the file - else the
// value is unchanged.
if (string.IsNullOrWhiteSpace(editorFile) && string.IsNullOrWhiteSpace(currentPath) == false)
{
_mediaFileSystem.DeleteFile(currentPath);
return null; // clear
}
return currentValue; // unchanged
}
// process the file
var filepath = editorFile == null ? null : ProcessFile(editorValue, file, currentPath, cuid, puid);
// remove all temp files
foreach (var f in uploads)
File.Delete(f.TempFilePath);
// remove current file if replaced
if (currentPath != filepath && string.IsNullOrWhiteSpace(currentPath) == false)
_mediaFileSystem.DeleteFile(currentPath);
// update json and return
if (editorFile == null) return null;
return filepath == null ? string.Empty : _mediaFileSystem.GetUrl(filepath);
}
private string ProcessFile(ContentPropertyData editorValue, ContentPropertyFile file, string currentPath, Guid cuid, Guid puid)
{
// process the file
// no file, invalid file, reject change
if (UploadFileTypeValidator.IsValidFileExtension(file.FileName) == false)
return null;
// get the filepath
// in case we are using the old path scheme, try to re-use numbers (bah...)
var filepath = _mediaFileSystem.GetMediaPath(file.FileName, currentPath, cuid, puid); // fs-relative path
using (var filestream = File.OpenRead(file.TempFilePath))
{
// TODO: Here it would make sense to do the auto-fill properties stuff but the API doesn't allow us to do that right
// since we'd need to be able to return values for other properties from these methods
_mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite!
}
return filepath;
}
}
}

View File

@@ -1,29 +0,0 @@
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration for the grid value editor.
/// </summary>
public class GridConfiguration : IIgnoreUserStartNodesConfig
{
// TODO: Make these strongly typed, for now this works though
[ConfigurationField("items", "Grid", "views/propertyeditors/grid/grid.prevalues.html", Description = "Grid configuration")]
public JObject Items { get; set; }
// TODO: Make these strongly typed, for now this works though
[ConfigurationField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration", HideLabel = true)]
public JObject Rte { get; set; }
[ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes,
"Ignore User Start Nodes", "boolean",
Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
public bool IgnoreUserStartNodes { get; set; }
[ConfigurationField("mediaParentId", "Image Upload Folder", "MediaFolderPicker",
Description = "Choose the upload location of pasted images")]
public GuidUdi MediaParentId { get; set; }
}
}

View File

@@ -1,56 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Newtonsoft.Json;
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editor for the grid value editor.
/// </summary>
public class GridConfigurationEditor : ConfigurationEditor<GridConfiguration>
{
public GridConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
var items = Fields.First(x => x.Key == "items");
items.Validators.Add(new GridValidator());
}
}
public class GridValidator : IValueValidator
{
public IEnumerable<ValidationResult> Validate(object rawValue, string valueType, object dataTypeConfiguration)
{
if (rawValue == null)
yield break;
var model = JsonConvert.DeserializeObject<GridEditorModel>(rawValue.ToString());
if (model.Templates.Any(t => t.Sections.Sum(s => s.Grid) > model.Columns))
{
yield return new ValidationResult("Columns must be at least the same size as the largest layout", new[] { nameof(model.Columns) });
}
}
}
public class GridEditorModel
{
public GridEditorTemplateModel[] Templates { get; set; }
public int Columns { get; set; }
}
public class GridEditorTemplateModel
{
public GridEditorSectionModel[] Sections { get; set; }
}
public class GridEditorSectionModel
{
public int Grid { get; set; }
}
}

View File

@@ -1,25 +0,0 @@
using System.Collections.Generic;
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editor for the image cropper value editor.
/// </summary>
internal class ImageCropperConfigurationEditor : ConfigurationEditor<ImageCropperConfiguration>
{
/// <inheritdoc />
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);
if (!d.ContainsKey("focalPoint")) d["focalPoint"] = new { left = 0.5, top = 0.5 };
if (!d.ContainsKey("src")) d["src"] = "";
return d;
}
public ImageCropperConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
}
}
}

View File

@@ -1,276 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Media;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents an image cropper property editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.ImageCropper,
"Image Cropper",
"imagecropper",
ValueType = ValueTypes.Json,
HideLabel = false,
Group = Constants.PropertyEditors.Groups.Media,
Icon = "icon-crop")]
public class ImageCropperPropertyEditor : DataEditor, IDataEditorWithMediaPath
{
private readonly IMediaFileSystem _mediaFileSystem;
private readonly IContentSection _contentSettings;
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizationService _localizationService;
private readonly IIOHelper _ioHelper;
private readonly UploadAutoFillProperties _autoFillProperties;
/// <summary>
/// Initializes a new instance of the <see cref="ImageCropperPropertyEditor"/> class.
/// </summary>
public ImageCropperPropertyEditor(ILogger logger, IMediaFileSystem mediaFileSystem, IContentSection contentSettings, IDataTypeService dataTypeService, ILocalizationService localizationService, IIOHelper ioHelper, IShortStringHelper shortStringHelper, ILocalizedTextService localizedTextService)
: base(logger, dataTypeService, localizationService, localizedTextService,shortStringHelper)
{
_mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem));
_contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings));
_dataTypeService = dataTypeService;
_localizationService = localizationService;
_ioHelper = ioHelper;
// TODO: inject?
_autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, _contentSettings);
}
public string GetMediaPath(object value) => GetFileSrcFromPropertyValue(value, out _, false);
/// <summary>
/// Creates the corresponding property value editor.
/// </summary>
/// <returns>The corresponding property value editor.</returns>
protected override IDataValueEditor CreateValueEditor() => new ImageCropperPropertyValueEditor(Attribute, Logger, _mediaFileSystem, DataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper);
/// <summary>
/// Creates the corresponding preValue editor.
/// </summary>
/// <returns>The corresponding preValue editor.</returns>
protected override IConfigurationEditor CreateConfigurationEditor() => new ImageCropperConfigurationEditor(_ioHelper);
/// <summary>
/// Gets a value indicating whether a property is an image cropper field.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>A value indicating whether a property is an image cropper field, and (optionally) has a non-empty value.</returns>
private static bool IsCropperField(IProperty property)
{
return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper;
}
/// <summary>
/// Parses the property value into a json object.
/// </summary>
/// <param name="value">The property value.</param>
/// <param name="writeLog">A value indicating whether to log the error.</param>
/// <returns>The json object corresponding to the property value.</returns>
/// <remarks>In case of an error, optionally logs the error and returns null.</remarks>
private JObject GetJObject(string value, bool writeLog)
{
if (string.IsNullOrWhiteSpace(value))
return null;
try
{
return JsonConvert.DeserializeObject<JObject>(value);
}
catch (Exception ex)
{
if (writeLog)
Logger.Error<ImageCropperPropertyEditor>(ex, "Could not parse image cropper value '{Json}'", value);
return null;
}
}
/// <summary>
/// Ensures any files associated are removed
/// </summary>
/// <param name="deletedEntities"></param>
internal IEnumerable<string> ServiceDeleted(IEnumerable<ContentBase> deletedEntities)
{
return deletedEntities.SelectMany(x => x.Properties)
.Where(IsCropperField)
.SelectMany(GetFilePathsFromPropertyValues)
.Distinct();
}
/// <summary>
/// Look through all property values stored against the property and resolve any file paths stored
/// </summary>
/// <param name="prop"></param>
/// <returns></returns>
private IEnumerable<string> GetFilePathsFromPropertyValues(IProperty prop)
{
//parses out the src from a json string
foreach (var propertyValue in prop.Values)
{
//check if the published value contains data and return it
var src = GetFileSrcFromPropertyValue(propertyValue.PublishedValue, out var _);
if (src != null) yield return _mediaFileSystem.GetRelativePath(src);
//check if the edited value contains data and return it
src = GetFileSrcFromPropertyValue(propertyValue.EditedValue, out var _);
if (src != null) yield return _mediaFileSystem.GetRelativePath(src);
}
}
/// <summary>
/// Returns the "src" property from the json structure if the value is formatted correctly
/// </summary>
/// <param name="propVal"></param>
/// <param name="deserializedValue">The deserialized <see cref="JObject"/> value</param>
/// <param name="relative">Should the path returned be the application relative path</param>
/// <returns></returns>
private string GetFileSrcFromPropertyValue(object propVal, out JObject deserializedValue, bool relative = true)
{
deserializedValue = null;
if (propVal == null || !(propVal is string str)) return null;
if (!str.DetectIsJson()) return null;
deserializedValue = GetJObject(str, true);
if (deserializedValue?["src"] == null) return null;
var src = deserializedValue["src"].Value<string>();
return relative ? _mediaFileSystem.GetRelativePath(src) : src;
}
/// <summary>
/// After a content has been copied, also copy uploaded files.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs<IContent> args)
{
// get the image cropper field properties
var properties = args.Original.Properties.Where(IsCropperField);
// copy files
var isUpdated = false;
foreach (var property in properties)
{
//copy each of the property values (variants, segments) to the destination by using the edited value
foreach (var propertyValue in property.Values)
{
var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment);
var src = GetFileSrcFromPropertyValue(propVal, out var jo);
if (src == null) continue;
var sourcePath = _mediaFileSystem.GetRelativePath(src);
var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath);
jo["src"] = _mediaFileSystem.GetUrl(copyPath);
args.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.Culture, propertyValue.Segment);
isUpdated = true;
}
}
// if updated, re-save the copy with the updated value
if (isUpdated)
sender.Save(args.Copy);
}
/// <summary>
/// After a media has been created, auto-fill the properties.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs<IMedia> args)
{
AutoFillProperties(args.Entity);
}
/// <summary>
/// After a media has been saved, auto-fill the properties.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs<IMedia> args)
{
foreach (var entity in args.SavedEntities)
AutoFillProperties(entity);
}
/// <summary>
/// After a content item has been saved, auto-fill the properties.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs<IContent> args)
{
foreach (var entity in args.SavedEntities)
AutoFillProperties(entity);
}
/// <summary>
/// Auto-fill properties (or clear).
/// </summary>
private void AutoFillProperties(IContentBase model)
{
var properties = model.Properties.Where(IsCropperField);
foreach (var property in properties)
{
var autoFillConfig = _contentSettings.GetConfig(property.Alias);
if (autoFillConfig == null) continue;
foreach (var pvalue in property.Values)
{
var svalue = property.GetValue(pvalue.Culture, pvalue.Segment) as string;
if (string.IsNullOrWhiteSpace(svalue))
{
_autoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment);
}
else
{
var jo = GetJObject(svalue, false);
string src;
if (jo == null)
{
// so we have a non-empty string value that cannot be parsed into a json object
// see http://issues.umbraco.org/issue/U4-4756
// it can happen when an image is uploaded via the folder browser, in which case
// the property value will be the file source eg '/media/23454/hello.jpg' and we
// are fixing that anomaly here - does not make any sense at all but... bah...
var dt = _dataTypeService.GetDataType(property.PropertyType.DataTypeId);
var config = dt?.ConfigurationAs<ImageCropperConfiguration>();
src = svalue;
var json = new
{
src = svalue,
crops = config == null ? Array.Empty<ImageCropperConfiguration.Crop>() : config.Crops
};
property.SetValue(JsonConvert.SerializeObject(json), pvalue.Culture, pvalue.Segment);
}
else
{
src = jo["src"]?.Value<string>();
}
if (src == null)
_autoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment);
else
_autoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(src), pvalue.Culture, pvalue.Segment);
}
}
}
}
}
}

View File

@@ -1,187 +0,0 @@
using Newtonsoft.Json.Linq;
using System;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using File = System.IO.File;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// The value editor for the image cropper property editor.
/// </summary>
internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core vs web?
{
private readonly ILogger _logger;
private readonly IMediaFileSystem _mediaFileSystem;
public ImageCropperPropertyValueEditor(DataEditorAttribute attribute, ILogger logger, IMediaFileSystem mediaFileSystem, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper)
: base(dataTypeService, localizationService, localizedTextService, shortStringHelper, attribute)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem));
}
/// <summary>
/// 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
/// </summary>
public override object ToEditor(IProperty property, string culture = null, string segment = null)
{
var val = property.GetValue(culture, segment);
if (val == null) return null;
ImageCropperValue value;
try
{
value = JsonConvert.DeserializeObject<ImageCropperValue>(val.ToString());
}
catch
{
value = new ImageCropperValue { Src = val.ToString() };
}
var dataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId);
if (dataType?.Configuration != null)
value.ApplyConfiguration(dataType.ConfigurationAs<ImageCropperConfiguration>());
return value;
}
/// <summary>
/// Converts the value received from the editor into the value can be stored in the database.
/// </summary>
/// <param name="editorValue">The value received from the editor.</param>
/// <param name="currentValue">The current value of the property</param>
/// <returns>The converted value.</returns>
/// <remarks>
/// <para>The <paramref name="currentValue"/> is used to re-use the folder, if possible.</para>
/// <para>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.</para>
/// </remarks>
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
{
// get the current path
var currentPath = string.Empty;
try
{
var svalue = currentValue as string;
var currentJson = string.IsNullOrWhiteSpace(svalue) ? null : JObject.Parse(svalue);
if (currentJson != null && currentJson["src"] != null)
currentPath = currentJson["src"].Value<string>();
}
catch (Exception ex)
{
// for some reason the value is invalid so continue as if there was no value there
_logger.Warn<ImageCropperPropertyValueEditor>(ex, "Could not parse current db value to a JObject.");
}
if (string.IsNullOrWhiteSpace(currentPath) == false)
currentPath = _mediaFileSystem.GetRelativePath(currentPath);
// get the new json and path
JObject editorJson = null;
var editorFile = string.Empty;
if (editorValue.Value != null)
{
editorJson = editorValue.Value as JObject;
if (editorJson != null && editorJson["src"] != null)
editorFile = editorJson["src"].Value<string>();
}
// ensure we have the required guids
var cuid = editorValue.ContentKey;
if (cuid == Guid.Empty) throw new Exception("Invalid content key.");
var puid = editorValue.PropertyTypeKey;
if (puid == Guid.Empty) throw new Exception("Invalid property type key.");
// editorFile is empty whenever a new file is being uploaded
// or when the file is cleared (in which case editorJson is null)
// else editorFile contains the unchanged value
var uploads = editorValue.Files;
if (uploads == null) throw new Exception("Invalid files.");
var file = uploads.Length > 0 ? uploads[0] : null;
if (file == null) // not uploading a file
{
// if editorFile is empty then either there was nothing to begin with,
// or it has been cleared and we need to remove the file - else the
// value is unchanged.
if (string.IsNullOrWhiteSpace(editorFile) && string.IsNullOrWhiteSpace(currentPath) == false)
{
_mediaFileSystem.DeleteFile(currentPath);
return null; // clear
}
return editorJson?.ToString(); // unchanged
}
// process the file
var filepath = editorJson == null ? null : ProcessFile(editorValue, file, currentPath, cuid, puid);
// remove all temp files
foreach (var f in uploads)
File.Delete(f.TempFilePath);
// remove current file if replaced
if (currentPath != filepath && string.IsNullOrWhiteSpace(currentPath) == false)
_mediaFileSystem.DeleteFile(currentPath);
// update json and return
if (editorJson == null) return null;
editorJson["src"] = filepath == null ? string.Empty : _mediaFileSystem.GetUrl(filepath);
return editorJson.ToString();
}
private string ProcessFile(ContentPropertyData editorValue, ContentPropertyFile file, string currentPath, Guid cuid, Guid puid)
{
// process the file
// no file, invalid file, reject change
if (UploadFileTypeValidator.IsValidFileExtension(file.FileName) == false)
return null;
// get the filepath
// in case we are using the old path scheme, try to re-use numbers (bah...)
var filepath = _mediaFileSystem.GetMediaPath(file.FileName, currentPath, cuid, puid); // fs-relative path
using (var filestream = File.OpenRead(file.TempFilePath))
{
// TODO: Here it would make sense to do the auto-fill properties stuff but the API doesn't allow us to do that right
// since we'd need to be able to return values for other properties from these methods
_mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite!
}
return filepath;
}
public override string ConvertDbToString(IPropertyType propertyType, object value, IDataTypeService dataTypeService)
{
if (value == null || string.IsNullOrEmpty(value.ToString()))
return null;
// if we don't have a json structure, we will get it from the property type
var val = value.ToString();
if (val.DetectIsJson())
return val;
// more magic here ;-(
var configuration = DataTypeService.GetDataType(propertyType.DataTypeId).ConfigurationAs<ImageCropperConfiguration>();
var crops = configuration?.Crops ?? Array.Empty<ImageCropperConfiguration.Crop>();
return JsonConvert.SerializeObject(new
{
src = val,
crops = crops
});
}
}
}

View File

@@ -1,38 +0,0 @@
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored.
/// </summary>
internal class IntegerConfigurationEditor : ConfigurationEditor
{
public IntegerConfigurationEditor()
{
Fields.Add(new ConfigurationField(new IntegerValidator())
{
Description = "Enter the minimum amount of number to be entered",
Key = "min",
View = "number",
Name = "Minimum"
});
Fields.Add(new ConfigurationField(new IntegerValidator())
{
Description = "Enter the intervals amount between each step of number to be entered",
Key = "step",
View = "number",
Name = "Step Size"
});
Fields.Add(new ConfigurationField(new IntegerValidator())
{
Description = "Enter the maximum amount of number to be entered",
Key = "max",
View = "number",
Name = "Maximum"
});
}
}
}

View File

@@ -1,37 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents an integer property and parameter editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.Integer,
EditorType.PropertyValue | EditorType.MacroParameter,
"Numeric",
"integer",
ValueType = ValueTypes.Integer)]
public class IntegerPropertyEditor : DataEditor
{
public IntegerPropertyEditor(ILogger logger, IDataTypeService dataTypeService, ILocalizationService localizationService, IShortStringHelper shortStringHelper, ILocalizedTextService localizedTextService)
: base(logger, dataTypeService, localizationService,localizedTextService, shortStringHelper)
{ }
/// <inheritdoc />
protected override IDataValueEditor CreateValueEditor()
{
var editor = base.CreateValueEditor();
editor.Validators.Add(new IntegerValidator()); // ensure the value is validated
return editor;
}
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new IntegerConfigurationEditor();
}
}

View File

@@ -1,123 +0,0 @@
using Newtonsoft.Json;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration for the listview value editor.
/// </summary>
public class ListViewConfiguration
{
public ListViewConfiguration()
{
// initialize defaults
PageSize = 10;
OrderBy = "SortOrder";
OrderDirection = "asc";
BulkActionPermissions = new BulkActionPermissionSettings
{
AllowBulkPublish = true,
AllowBulkUnpublish = true,
AllowBulkCopy = true,
AllowBulkMove = true,
AllowBulkDelete = true
};
Layouts = new[]
{
new Layout { Name = "List", Icon = "icon-list", IsSystem = 1, Selected = true, Path = "views/propertyeditors/listview/layouts/list/list.html" },
new Layout { Name = "grid", Icon = "icon-thumbnails-small", IsSystem = 1, Selected = true, Path = "views/propertyeditors/listview/layouts/grid/grid.html" }
};
IncludeProperties = new []
{
new Property { Alias = "sortOrder", Header = "Sort order", IsSystem = 1 },
new Property { Alias = "updateDate", Header = "Last edited", IsSystem = 1 },
new Property { Alias = "owner", Header = "Created by", IsSystem = 1 }
};
}
[ConfigurationField("pageSize", "Page Size", "number", Description = "Number of items per page")]
public int PageSize { get; set; }
[ConfigurationField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html",
Description = "The default sort order for the list")]
public string OrderBy { get; set; }
[ConfigurationField("orderDirection", "Order Direction", "views/propertyeditors/listview/orderdirection.prevalues.html")]
public string OrderDirection { get; set; }
[ConfigurationField("includeProperties", "Columns Displayed", "views/propertyeditors/listview/includeproperties.prevalues.html",
Description = "The properties that will be displayed for each column")]
public Property[] IncludeProperties { get; set; }
[ConfigurationField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")]
public Layout[] Layouts { get; set; }
[ConfigurationField("bulkActionPermissions", "Bulk Action Permissions", "views/propertyeditors/listview/bulkactionpermissions.prevalues.html",
Description = "The bulk actions that are allowed from the list view")]
public BulkActionPermissionSettings BulkActionPermissions { get; set; } = new BulkActionPermissionSettings(); // TODO: managing defaults?
[ConfigurationField("icon", "Content app icon", "views/propertyeditors/listview/icon.prevalues.html", Description = "The icon of the listview content app")]
public string Icon { get; set; }
[ConfigurationField("tabName", "Content app name", "textstring", Description = "The name of the listview content app (default if empty: 'Child Items')")]
public string TabName { get; set; }
[ConfigurationField("showContentFirst", "Show Content App First", "boolean", Description = "Enable this to show the content app by default instead of the list view app")]
public bool ShowContentFirst { get; set; }
public class Property
{
[JsonProperty("alias")]
public string Alias { get; set; }
[JsonProperty("header")]
public string Header { get; set; }
[JsonProperty("nameTemplate")]
public string Template { get; set; }
[JsonProperty("isSystem")]
public int IsSystem { get; set; } // TODO: bool
}
public class Layout
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("path")]
public string Path { get; set; }
[JsonProperty("icon")]
public string Icon { get; set; }
[JsonProperty("isSystem")]
public int IsSystem { get; set; } // TODO: bool
[JsonProperty("selected")]
public bool Selected { get; set; }
}
public class BulkActionPermissionSettings
{
[JsonProperty("allowBulkPublish")]
public bool AllowBulkPublish { get; set; } = true;
[JsonProperty("allowBulkUnpublish")]
public bool AllowBulkUnpublish { get; set; } = true;
[JsonProperty("allowBulkCopy")]
public bool AllowBulkCopy { get; set; } = true;
[JsonProperty("allowBulkMove")]
public bool AllowBulkMove { get; set; } = true;
[JsonProperty("allowBulkDelete")]
public bool AllowBulkDelete { get; set; } = true;
}
}
}

View File

@@ -1,15 +0,0 @@
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editor for the listview value editor.
/// </summary>
public class ListViewConfigurationEditor : ConfigurationEditor<ListViewConfiguration>
{
public ListViewConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
}
}
}

View File

@@ -1,44 +0,0 @@
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents a list-view editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.ListView,
"List view",
"listview",
HideLabel = true,
Group = Constants.PropertyEditors.Groups.Lists,
Icon = Constants.Icons.ListView)]
public class ListViewPropertyEditor : DataEditor
{
private readonly IIOHelper _iioHelper;
/// <summary>
/// Initializes a new instance of the <see cref="ListViewPropertyEditor"/> class.
/// </summary>
/// <param name="logger"></param>
public ListViewPropertyEditor(
ILogger logger,
IIOHelper iioHelper,
IDataTypeService dataTypeService,
ILocalizationService localizationService,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper)
: base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper)
{
_iioHelper = iioHelper;
}
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new ListViewConfigurationEditor(_iioHelper);
}
}

View File

@@ -1,16 +0,0 @@
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration for the markdown value editor.
/// </summary>
public class MarkdownConfiguration
{
[ConfigurationField("preview", "Preview", "boolean", Description = "Display a live preview")]
public bool DisplayLivePreview { get; set; }
[ConfigurationField("defaultValue", "Default value", "textarea", Description = "If value is blank, the editor will show this")]
public string DefaultValue { get; set; }
}
}

View File

@@ -1,15 +0,0 @@
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editorfor the markdown value editor.
/// </summary>
internal class MarkdownConfigurationEditor : ConfigurationEditor<MarkdownConfiguration>
{
public MarkdownConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
}
}
}

View File

@@ -1,43 +0,0 @@
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents a markdown editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.MarkdownEditor,
"Markdown editor",
"markdowneditor",
ValueType = ValueTypes.Text,
Group = Constants.PropertyEditors.Groups.RichContent,
Icon = "icon-code")]
public class MarkdownPropertyEditor : DataEditor
{
private readonly IIOHelper _ioHelper;
/// <summary>
/// Initializes a new instance of the <see cref="MarkdownPropertyEditor"/> class.
/// </summary>
public MarkdownPropertyEditor(
ILogger logger,
IIOHelper ioHelper,
IDataTypeService dataTypeService,
ILocalizationService localizationService,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper)
: base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper)
{
_ioHelper = ioHelper;
}
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new MarkdownConfigurationEditor(_ioHelper);
}
}

View File

@@ -1,64 +0,0 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// A value editor to handle posted json array data and to return array data for the multiple selected csv items
/// </summary>
/// <remarks>
/// This is re-used by editors such as the multiple drop down list or check box list
/// </remarks>
internal class MultipleValueEditor : DataValueEditor
{
private readonly ILogger _logger;
internal MultipleValueEditor(ILogger logger, IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, DataEditorAttribute attribute)
: base(dataTypeService, localizationService, localizedTextService,shortStringHelper, attribute)
{
_logger = logger;
}
/// <summary>
/// Override so that we can return an array to the editor for multi-select values
/// </summary>
/// <param name="property"></param>
/// <param name="dataTypeService"></param>
/// <param name="culture"></param>
/// <param name="segment"></param>
/// <returns></returns>
public override object ToEditor(IProperty property, string culture = null, string segment = null)
{
var json = base.ToEditor(property, culture, segment).ToString();
return JsonConvert.DeserializeObject<string[]>(json) ?? Array.Empty<string>();
}
/// <summary>
/// When multiple values are selected a json array will be posted back so we need to format for storage in
/// the database which is a comma separated string value
/// </summary>
/// <param name="editorValue"></param>
/// <param name="currentValue"></param>
/// <returns></returns>
public override object FromEditor(Core.Models.Editors.ContentPropertyData editorValue, object currentValue)
{
var json = editorValue.Value as JArray;
if (json == null)
{
return null;
}
var values = json.Select(item => item.Value<string>()).ToArray();
return JsonConvert.SerializeObject(values);
}
}
}

View File

@@ -1,58 +0,0 @@
using System.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services.Implement;
namespace Umbraco.Web.PropertyEditors
{
internal sealed class PropertyEditorsComponent : IComponent
{
private readonly PropertyEditorCollection _propertyEditors;
public PropertyEditorsComponent(PropertyEditorCollection propertyEditors)
{
_propertyEditors = propertyEditors;
}
public void Initialize()
{
var fileUpload = _propertyEditors.OfType<FileUploadPropertyEditor>().FirstOrDefault();
if (fileUpload != null) Initialize(fileUpload);
var imageCropper = _propertyEditors.OfType<ImageCropperPropertyEditor>().FirstOrDefault();
if (imageCropper != null) Initialize(imageCropper);
// grid/examine moved to ExamineComponent
}
public void Terminate()
{ }
private static void Initialize(FileUploadPropertyEditor fileUpload)
{
MediaService.Saving += fileUpload.MediaServiceSaving;
ContentService.Copied += fileUpload.ContentServiceCopied;
MediaService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
ContentService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
MemberService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
}
private static void Initialize(ImageCropperPropertyEditor imageCropper)
{
MediaService.Saving += imageCropper.MediaServiceSaving;
ContentService.Copied += imageCropper.ContentServiceCopied;
MediaService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
ContentService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
MemberService.Deleted += (sender, args)
=> args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
}
}
}

View File

@@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
using Umbraco.Core.Services;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editor for the tag value editor.
/// </summary>
public class TagConfigurationEditor : ConfigurationEditor<TagConfiguration>
{
public TagConfigurationEditor(ManifestValueValidatorCollection validators, IIOHelper ioHelper, ILocalizedTextService localizedTextService) : base(ioHelper)
{
Field(nameof(TagConfiguration.Group)).Validators.Add(new RequiredValidator(localizedTextService));
Field(nameof(TagConfiguration.StorageType)).Validators.Add(new RequiredValidator(localizedTextService));
}
public override Dictionary<string, object> ToConfigurationEditor(TagConfiguration configuration)
{
var dictionary = base.ToConfigurationEditor(configuration);
// the front-end editor expects the string value of the storage type
if (!dictionary.TryGetValue("storageType", out var storageType))
storageType = TagsStorageType.Json; //default to Json
dictionary["storageType"] = storageType.ToString();
return dictionary;
}
public override TagConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, TagConfiguration configuration)
{
// the front-end editor returns the string value of the storage type
// pure Json could do with
// [JsonConverter(typeof(StringEnumConverter))]
// but here we're only deserializing to object and it's too late
editorValues["storageType"] = Enum.Parse(typeof(TagsStorageType), (string) editorValues["storageType"]);
return base.FromConfigurationEditor(editorValues, configuration);
}
}
}

View File

@@ -1,106 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents a tags property editor.
/// </summary>
[TagsPropertyEditor]
[DataEditor(
Constants.PropertyEditors.Aliases.Tags,
"Tags",
"tags",
Icon = "icon-tags")]
public class TagsPropertyEditor : DataEditor
{
private readonly ManifestValueValidatorCollection _validators;
private readonly IIOHelper _ioHelper;
public TagsPropertyEditor(
ManifestValueValidatorCollection validators,
ILogger logger,
IIOHelper ioHelper,
IDataTypeService dataTypeService,
ILocalizationService localizationService,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper)
: base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper)
{
_validators = validators;
_ioHelper = ioHelper;
}
protected override IDataValueEditor CreateValueEditor() => new TagPropertyValueEditor(DataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, Attribute);
protected override IConfigurationEditor CreateConfigurationEditor() => new TagConfigurationEditor(_validators, _ioHelper, LocalizedTextService);
internal class TagPropertyValueEditor : DataValueEditor
{
public TagPropertyValueEditor(IDataTypeService dataTypeService, ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, DataEditorAttribute attribute)
: base(dataTypeService, localizationService,localizedTextService, shortStringHelper, attribute)
{ }
/// <inheritdoc />
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
{
var value = editorValue?.Value?.ToString();
if (string.IsNullOrEmpty(value))
{
return null;
}
if (editorValue.Value is JArray json)
{
return json.Select(x => x.Value<string>());
}
if (string.IsNullOrWhiteSpace(value) == false)
{
return value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
}
return null;
}
/// <inheritdoc />
public override IValueRequiredValidator RequiredValidator => new RequiredJsonValueValidator();
/// <summary>
/// Custom validator to validate a required value against an empty json value.
/// </summary>
/// <remarks>
/// <para>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.</para>
/// </remarks>
private class RequiredJsonValueValidator : IValueRequiredValidator
{
/// <inheritdoc />
public IEnumerable<ValidationResult> ValidateRequired(object value, string valueType)
{
if (value == null)
{
yield return new ValidationResult("Value cannot be null", new[] {"value"});
yield break;
}
if (value.ToString().DetectIsEmptyJson())
yield return new ValidationResult("Value cannot be empty", new[] { "value" });
}
}
}
}
}

View File

@@ -1,16 +0,0 @@
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration for the textarea value editor.
/// </summary>
public class TextAreaConfiguration
{
[ConfigurationField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")]
public int? MaxChars { get; set; }
[ConfigurationField("rows", "Number of rows", "number", Description = "If empty - 10 rows would be set as the default value")]
public int? Rows { get; set; }
}
}

View File

@@ -1,15 +0,0 @@
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editor for the textarea value editor.
/// </summary>
public class TextAreaConfigurationEditor : ConfigurationEditor<TextAreaConfiguration>
{
public TextAreaConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
}
}
}

View File

@@ -1,48 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents a textarea property and parameter editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.TextArea,
EditorType.PropertyValue | EditorType.MacroParameter,
"Textarea",
"textarea",
ValueType = ValueTypes.Text,
Icon = "icon-application-window-alt")]
public class TextAreaPropertyEditor : DataEditor
{
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizationService _localizationService;
private readonly IIOHelper _ioHelper;
private readonly ILocalizedTextService _localizedTextService;
private readonly IShortStringHelper _shortStringHelper;
/// <summary>
/// Initializes a new instance of the <see cref="TextAreaPropertyEditor"/> class.
/// </summary>
public TextAreaPropertyEditor(ILogger logger, IDataTypeService dataTypeService, ILocalizationService localizationService, IIOHelper ioHelper, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper)
: base(logger, dataTypeService, localizationService, localizedTextService,shortStringHelper)
{
_dataTypeService = dataTypeService;
_localizationService = localizationService;
_ioHelper = ioHelper;
_localizedTextService = localizedTextService;
_shortStringHelper = shortStringHelper;
}
/// <inheritdoc />
protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor(_dataTypeService, _localizationService, Attribute, _localizedTextService, _shortStringHelper);
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new TextAreaConfigurationEditor(_ioHelper);
}
}

View File

@@ -1,51 +0,0 @@
using System;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Custom value editor which ensures that the value stored is just plain text and that
/// no magic json formatting occurs when translating it to and from the database values
/// </summary>
public class TextOnlyValueEditor : DataValueEditor
{
public TextOnlyValueEditor(IDataTypeService dataTypeService, ILocalizationService localizationService, DataEditorAttribute attribute, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper)
: base(dataTypeService, localizationService, localizedTextService, shortStringHelper, attribute)
{ }
/// <summary>
/// A method used to format the database value to a value that can be used by the editor
/// </summary>
/// <param name="property"></param>
/// <param name="dataTypeService"></param>
/// <param name="culture"></param>
/// <param name="segment"></param>
/// <returns></returns>
/// <remarks>
/// The object returned will always be a string and if the database type is not a valid string type an exception is thrown
/// </remarks>
public override object ToEditor(IProperty property, string culture = null, string segment = null)
{
var val = property.GetValue(culture, segment);
if (val == null) return string.Empty;
switch (ValueTypes.ToStorageType(ValueType))
{
case ValueStorageType.Ntext:
case ValueStorageType.Nvarchar:
return val.ToString();
case ValueStorageType.Integer:
case ValueStorageType.Decimal:
case ValueStorageType.Date:
default:
throw new InvalidOperationException("The " + typeof(TextOnlyValueEditor) + " can only be used with string based property editors");
}
}
}
}

View File

@@ -1,13 +0,0 @@
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration for the textbox value editor.
/// </summary>
public class TextboxConfiguration
{
[ConfigurationField("maxChars", "Maximum allowed characters", "textstringlimited", Description = "If empty, 500 character limit")]
public int? MaxChars { get; set; }
}
}

View File

@@ -1,15 +0,0 @@
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editor for the textbox value editor.
/// </summary>
public class TextboxConfigurationEditor : ConfigurationEditor<TextboxConfiguration>
{
public TextboxConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
}
}
}

View File

@@ -1,47 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents a textbox property and parameter editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.TextBox,
EditorType.PropertyValue | EditorType.MacroParameter,
"Textbox",
"textbox",
Group = Constants.PropertyEditors.Groups.Common)]
public class TextboxPropertyEditor : DataEditor
{
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizationService _localizationService;
private readonly IIOHelper _ioHelper;
private readonly IShortStringHelper _shortStringHelper;
private readonly ILocalizedTextService _localizedTextService;
/// <summary>
/// Initializes a new instance of the <see cref="TextboxPropertyEditor"/> class.
/// </summary>
public TextboxPropertyEditor(ILogger logger, IDataTypeService dataTypeService, ILocalizationService localizationService, IIOHelper ioHelper, IShortStringHelper shortStringHelper, ILocalizedTextService localizedTextService)
: base(logger, dataTypeService, localizationService,localizedTextService, shortStringHelper)
{
_dataTypeService = dataTypeService;
_localizationService = localizationService;
_ioHelper = ioHelper;
_shortStringHelper = shortStringHelper;
_localizedTextService = localizedTextService;
}
/// <inheritdoc/>
protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor(_dataTypeService, _localizationService, Attribute, _localizedTextService, _shortStringHelper);
/// <inheritdoc/>
protected override IConfigurationEditor CreateConfigurationEditor() => new TextboxConfigurationEditor(_ioHelper);
}
}

View File

@@ -1,16 +0,0 @@
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration for the boolean value editor.
/// </summary>
public class TrueFalseConfiguration
{
[ConfigurationField("default", "Default Value", "boolean")]
public string Default { get; set; } // TODO: well, true or false?!
[ConfigurationField("labelOn", "Write a label text", "textstring")]
public string Label { get; set; }
}
}

View File

@@ -1,15 +0,0 @@
using Umbraco.Core.IO;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the configuration editor for the boolean value editor.
/// </summary>
public class TrueFalseConfigurationEditor : ConfigurationEditor<TrueFalseConfiguration>
{
public TrueFalseConfigurationEditor(IIOHelper ioHelper) : base(ioHelper)
{
}
}
}

View File

@@ -1,39 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents a checkbox property and parameter editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.Boolean,
EditorType.PropertyValue | EditorType.MacroParameter,
"Checkbox",
"boolean",
ValueType = ValueTypes.Integer,
Group = Constants.PropertyEditors.Groups.Common,
Icon = "icon-checkbox")]
public class TrueFalsePropertyEditor : DataEditor
{
private readonly IIOHelper _ioHelper;
/// <summary>
/// Initializes a new instance of the <see cref="TrueFalsePropertyEditor"/> class.
/// </summary>
public TrueFalsePropertyEditor(ILogger logger, IDataTypeService dataTypeService, ILocalizationService localizationService, IIOHelper ioHelper, IShortStringHelper shortStringHelper, ILocalizedTextService localizedTextService)
: base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper)
{
_ioHelper = ioHelper;
}
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new TrueFalseConfigurationEditor(_ioHelper);
}
}

View File

@@ -1,62 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.Services;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Web.Composing;
namespace Umbraco.Web.PropertyEditors
{
internal class UploadFileTypeValidator : IValueValidator
{
private readonly ILocalizedTextService _localizedTextService;
public UploadFileTypeValidator(ILocalizedTextService localizedTextService)
{
_localizedTextService = localizedTextService;
}
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)
{
string selectedFiles = null;
if (value is JObject jobject && jobject["selectedFiles"] is JToken jToken)
{
selectedFiles = jToken.ToString();
}
else if (valueType?.InvariantEquals(ValueTypes.String) == true)
{
selectedFiles = value as string;
if (string.IsNullOrWhiteSpace(selectedFiles))
yield break;
}
var fileNames = selectedFiles?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (fileNames == null || !fileNames.Any())
yield break;
foreach (string filename in fileNames)
{
if (IsValidFileExtension(filename) == false)
{
//we only store a single value for this editor so the 'member' or 'field'
// we'll associate this error with will simply be called 'value'
yield return new ValidationResult(_localizedTextService.Localize("errors/dissallowedMediaType"), new[] { "value" });
}
}
}
internal static bool IsValidFileExtension(string fileName)
{
if (fileName.IndexOf('.') <= 0) return false;
var extension = new FileInfo(fileName).Extension.TrimStart(".");
return Current.Configs.Settings().Content.IsFileAllowedForUpload(extension);
}
}
}

View File

@@ -1,13 +0,0 @@
using System.Collections.Generic;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
public class UserPickerConfiguration : ConfigurationEditor
{
public override IDictionary<string, object> DefaultConfiguration => new Dictionary<string, object>
{
{"entityType", "User"}
};
}
}

View File

@@ -1,30 +0,0 @@
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Web.PropertyEditors
{
[DataEditor(
Constants.PropertyEditors.Aliases.UserPicker,
"User picker",
"entitypicker",
ValueType = ValueTypes.Integer,
Group = Constants.PropertyEditors.Groups.People,
Icon = Constants.Icons.User)]
public class UserPickerPropertyEditor : DataEditor
{
public UserPickerPropertyEditor(
ILogger logger,
IDataTypeService dataTypeService,
ILocalizationService localizationService,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper)
: base(logger, dataTypeService, localizationService, localizedTextService, shortStringHelper)
{ }
protected override IConfigurationEditor CreateConfigurationEditor() => new UserPickerConfiguration();
}
}

View File

@@ -1,99 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Pre-value editor used to create a list of items
/// </summary>
/// <remarks>
/// This pre-value editor is shared with editors like drop down, checkbox list, etc....
/// </remarks>
public class ValueListConfigurationEditor : ConfigurationEditor<ValueListConfiguration>
{
public ValueListConfigurationEditor(ILocalizedTextService textService, IIOHelper ioHelper) : base(ioHelper)
{
var items = Fields.First(x => x.Key == "items");
// customize the items field
items.Name = textService.Localize("editdatatype/addPrevalue");
items.Validators.Add(new ValueListUniqueValueValidator());
}
// editor...
//
// receives:
// "preValues":[
// {
// "label":"Add prevalue",
// "description":"Add and remove values for the list",
// "hideLabel":false,
// "view":"multivalues",
// "config":{},
// "key":"items",
// "value":{"169":{"value":"a","sortOrder":1},"170":{"value":"b","sortOrder":2},"171":{"value":"c","sortOrder":3}}
// }]
//
// posts ('d' being a new value):
// [{key: "items", value: [{value: "a", sortOrder: 1, id: "169"}, {value: "c", sortOrder: 3, id: "171"}, {value: "d"}]}]
//
// values go to DB with alias 0, 1, 2 + their ID + value
// the sort order that comes back makes no sense
/// <inheritdoc />
public override Dictionary<string, object> ToConfigurationEditor(ValueListConfiguration configuration)
{
if (configuration == null)
return new Dictionary<string, object>
{
{ "items", new object() }
};
// map to what the (still v7) editor expects
// {"item":{"169":{"value":"a","sortOrder":1},"170":{"value":"b","sortOrder":2},"171":{"value":"c","sortOrder":3}}}
var i = 1;
return new Dictionary<string, object>
{
{ "items", configuration.Items.ToDictionary(x => x.Id.ToString(), x => new { value = x.Value, sortOrder = i++ }) }
};
}
/// <inheritdoc />
public override ValueListConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, ValueListConfiguration configuration)
{
var output = new ValueListConfiguration();
if (!editorValues.TryGetValue("items", out var jjj) || !(jjj is JArray jItems))
return output; // oops
// auto-assigning our ids, get next id from existing values
var nextId = 1;
if (configuration?.Items != null && configuration.Items.Count > 0)
nextId = configuration.Items.Max(x => x.Id) + 1;
// create ValueListItem instances - sortOrder is ignored here
foreach (var item in jItems.OfType<JObject>())
{
var value = item.Property("value")?.Value?.Value<string>();
if (string.IsNullOrWhiteSpace(value)) continue;
var id = item.Property("id")?.Value?.Value<int>() ?? 0;
if (id >= nextId) nextId = id + 1;
output.Items.Add(new ValueListConfiguration.ValueListItem { Id = id, Value = value });
}
// ensure ids
foreach (var item in output.Items)
if (item.Id == 0)
item.Id = nextId++;
return output;
}
}
}

View File

@@ -1,41 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents a validator which ensures that all values in the list are unique.
/// </summary>
internal class ValueListUniqueValueValidator : IValueValidator
{
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)
{
// the value we get should be a JArray
// [ { "value": <value>, "sortOrder": 1 }, { ... }, ... ]
if (!(value is JArray json)) yield break;
// we ensure that values are unique
// (those are not empty - empty values are removed when persisting anyways)
var groupedValues = json.OfType<JObject>()
.Where(x => x["value"] != null)
.Select((x, index) => new { value = x["value"].ToString(), index})
.Where(x => x.value.IsNullOrWhiteSpace() == false)
.GroupBy(x => x.value);
foreach (var group in groupedValues.Where(x => x.Count() > 1))
{
yield return new ValidationResult($"The value \"{@group.Last().value}\" must be unique", new[]
{
// use the index number as server field so it can be wired up to the view
"item_" + @group.Last().index.ToInvariantString()
});
}
}
}
}

View File

@@ -466,23 +466,6 @@
<Compile Include="Mvc\FilteredControllerFactoryCollection.cs" />
<Compile Include="Mvc\FilteredControllerFactoryCollectionBuilder.cs" />
<Compile Include="Mvc\SurfaceControllerTypeCollection.cs" />
<Compile Include="PropertyEditors\ContentPickerPropertyEditor.cs" />
<Compile Include="PropertyEditors\ContentPickerConfiguration.cs" />
<Compile Include="PropertyEditors\ContentPickerConfigurationEditor.cs" />
<Compile Include="PropertyEditors\DateValueEditor.cs" />
<Compile Include="PropertyEditors\DecimalConfigurationEditor.cs" />
<Compile Include="PropertyEditors\DropDownFlexiblePropertyEditor.cs" />
<Compile Include="PropertyEditors\DropDownFlexibleConfigurationEditor.cs" />
<Compile Include="PropertyEditors\EmailAddressConfigurationEditor.cs" />
<Compile Include="PropertyEditors\EmailAddressConfiguration.cs" />
<Compile Include="PropertyEditors\GridConfiguration.cs" />
<Compile Include="PropertyEditors\GridConfigurationEditor.cs" />
<Compile Include="PropertyEditors\ImageCropperConfigurationEditor.cs" />
<Compile Include="PropertyEditors\IntegerConfigurationEditor.cs" />
<Compile Include="PropertyEditors\ListViewConfiguration.cs" />
<Compile Include="PropertyEditors\ListViewConfigurationEditor.cs" />
<Compile Include="PropertyEditors\MarkdownConfiguration.cs" />
<Compile Include="PropertyEditors\MarkdownConfigurationEditor.cs" />
<Compile Include="PropertyEditors\MediaPickerPropertyEditor.cs" />
<Compile Include="PropertyEditors\MediaPickerConfiguration.cs" />
<Compile Include="PropertyEditors\MediaPickerConfigurationEditor.cs" />
@@ -499,18 +482,8 @@
<Compile Include="PropertyEditors\NestedContentController.cs" />
<Compile Include="PropertyEditors\NestedContentPropertyEditor.cs" />
<Compile Include="PropertyEditors\ParameterEditors\MultipleContentPickerParameterEditor.cs" />
<Compile Include="PropertyEditors\PropertyEditorsComponent.cs" />
<Compile Include="PropertyEditors\RichTextConfiguration.cs" />
<Compile Include="PropertyEditors\SliderConfigurationEditor.cs" />
<Compile Include="PropertyEditors\TagConfigurationEditor.cs" />
<Compile Include="PropertyEditors\TextAreaConfiguration.cs" />
<Compile Include="PropertyEditors\TextAreaConfigurationEditor.cs" />
<Compile Include="PropertyEditors\TextboxConfiguration.cs" />
<Compile Include="PropertyEditors\TextboxConfigurationEditor.cs" />
<Compile Include="PropertyEditors\TextOnlyValueEditor.cs" />
<Compile Include="PropertyEditors\TrueFalseConfiguration.cs" />
<Compile Include="PropertyEditors\TrueFalseConfigurationEditor.cs" />
<Compile Include="PropertyEditors\UserPickerConfiguration.cs" />
<Compile Include="PropertyEditors\ValueConverters\FlexibleDropdownPropertyValueConverter.cs" />
<Compile Include="PropertyEditors\ValueConverters\NestedContentManyValueConverter.cs" />
<Compile Include="PropertyEditors\ValueConverters\NestedContentValueConverterBase.cs" />
@@ -518,7 +491,6 @@
<Compile Include="PropertyEditors\ValueConverters\MediaPickerValueConverter.cs" />
<Compile Include="PropertyEditors\ValueConverters\MemberPickerValueConverter.cs" />
<Compile Include="PropertyEditors\ValueConverters\MultiNodeTreePickerValueConverter.cs" />
<Compile Include="PropertyEditors\ValueListUniqueValueValidator.cs" />
<Compile Include="PublishedCache\IPublishedSnapshot.cs" />
<Compile Include="PublishedCache\IDefaultCultureAccessor.cs" />
<Compile Include="PublishedCache\NuCache\DataSource\BTree.ContentDataSerializer.cs" />
@@ -650,8 +622,6 @@
<Compile Include="Mvc\UmbracoRequireHttpsAttribute.cs" />
<Compile Include="Mvc\ValidateMvcAngularAntiForgeryTokenAttribute.cs" />
<Compile Include="OwinMiddlewareConfiguredEventArgs.cs" />
<Compile Include="PropertyEditors\DateTimeConfigurationEditor.cs" />
<Compile Include="PropertyEditors\DecimalPropertyEditor.cs" />
<Compile Include="Routing\RedirectTrackingComponent.cs" />
<Compile Include="Editors\RedirectUrlManagementController.cs" />
<Compile Include="Models\ContentEditing\RedirectUrlSearchResults.cs" />
@@ -809,12 +779,6 @@
<Compile Include="Models\Mapping\TagMapDefinition.cs" />
<Compile Include="Models\UpgradeCheckResponse.cs" />
<Compile Include="Models\PasswordChangedModel.cs" />
<Compile Include="PropertyEditors\ColorPickerConfigurationEditor.cs" />
<Compile Include="PropertyEditors\EmailAddressPropertyEditor.cs" />
<Compile Include="PropertyEditors\ImageCropperPropertyEditor.cs" />
<Compile Include="PropertyEditors\ImageCropperPropertyValueEditor.cs" />
<Compile Include="PropertyEditors\ListViewPropertyEditor.cs" />
<Compile Include="PropertyEditors\MarkdownPropertyEditor.cs" />
<Compile Include="PropertyEditors\MemberGroupPickerPropertyEditor.cs" />
<Compile Include="PropertyEditors\ParameterEditors\ContentTypeParameterEditor.cs" />
<Compile Include="PropertyEditors\ParameterEditors\MultipleContentTypeParameterEditor.cs" />
@@ -834,21 +798,11 @@
<Compile Include="Models\ContentEditing\PropertyEditorBasic.cs" />
<Compile Include="Models\Mapping\DataTypeMapDefinition.cs" />
<Compile Include="Models\Mapping\EntityMapDefinition.cs" />
<Compile Include="PropertyEditors\CheckBoxListPropertyEditor.cs" />
<Compile Include="PropertyEditors\ColorPickerPropertyEditor.cs" />
<Compile Include="PropertyEditors\DateTimePropertyEditor.cs" />
<Compile Include="PropertyEditors\DateTimeValidator.cs" />
<Compile Include="PropertyEditors\IntegerPropertyEditor.cs" />
<Compile Include="PropertyEditors\MultipleTextStringPropertyEditor.cs" />
<Compile Include="PropertyEditors\MultipleValueEditor.cs" />
<Compile Include="PropertyEditors\RadioButtonsPropertyEditor.cs" />
<Compile Include="PropertyEditors\RichTextPreValueController.cs" />
<Compile Include="PropertyEditors\RichTextConfigurationEditor.cs" />
<Compile Include="PropertyEditors\SliderPropertyEditor.cs" />
<Compile Include="PropertyEditors\TagsPropertyEditor.cs" />
<Compile Include="PropertyEditors\UploadFileTypeValidator.cs" />
<Compile Include="PropertyEditors\UserPickerPropertyEditor.cs" />
<Compile Include="PropertyEditors\ValueListConfigurationEditor.cs" />
<Compile Include="PublishedContentQuery.cs" />
<Compile Include="ImageCropperTemplateExtensions.cs" />
<Compile Include="Mvc\UmbracoVirtualNodeRouteHandler.cs" />
@@ -946,12 +900,7 @@
<Compile Include="Models\Mapping\SectionMapDefinition.cs" />
<Compile Include="Models\Mapping\TabsAndPropertiesMapper.cs" />
<Compile Include="Models\Mapping\UserMapDefinition.cs" />
<Compile Include="PropertyEditors\FileUploadPropertyEditor.cs" />
<Compile Include="PropertyEditors\FileUploadPropertyValueEditor.cs" />
<Compile Include="PropertyEditors\RichTextPropertyEditor.cs" />
<Compile Include="PropertyEditors\TextAreaPropertyEditor.cs" />
<Compile Include="PropertyEditors\TextboxPropertyEditor.cs" />
<Compile Include="PropertyEditors\TrueFalsePropertyEditor.cs" />
<Compile Include="Trees\MediaTreeController.cs" />
<Compile Include="Models\Trees\ActionMenuItem.cs" />
<Compile Include="Trees\ActionUrlMethod.cs" />