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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user