Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions src/Umbraco.Core/Models/PropertyTagsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,15 @@ public static class PropertyTagsExtensions

IDataEditor? editor = propertyEditors[property.PropertyType?.PropertyEditorAlias];
TagsPropertyEditorAttribute? tagAttribute = editor?.GetTagAttribute();
if (tagAttribute == null)
{
return null;
}

var configurationObject = property.PropertyType is null
? null
: dataTypeService.GetDataType(property.PropertyType.DataTypeId)?.Configuration;
TagConfiguration? configuration = ConfigurationEditor.ConfigurationAs<TagConfiguration>(configurationObject);
TagConfiguration? configuration = configurationObject as TagConfiguration;

if (configuration is not null && configuration.Delimiter == default)
{
configuration.Delimiter = tagAttribute.Delimiter;
configuration.Delimiter = tagAttribute?.Delimiter ?? ',';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rasmusjp maybe use the comma from the constant class here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public static readonly char[] Comma = { ',' };

but since it is an array we would need to select first char.

}

return configuration;
Expand Down
18 changes: 18 additions & 0 deletions src/Umbraco.Core/PropertyEditors/IDataValueTags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Umbraco.Cms.Core.Models;

namespace Umbraco.Cms.Core.PropertyEditors;

/// <summary>
/// Resolve tags from <see cref="IDataValueEditor" /> values
/// </summary>
public interface IDataValueTags
{
/// <summary>
/// Returns any tags contained in the value
/// </summary>
/// <param name="value"></param>
/// <param name="dataTypeConfiguration"></param>
/// <param name="languageId"></param>
/// <returns></returns>
IEnumerable<ITag> GetTags(object? value, object? dataTypeConfiguration, int? languageId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Umbraco.Extensions;
/// <summary>
/// Provides extension methods for the <see cref="IDataEditor" /> interface to manage tags.
/// </summary>
[Obsolete]
public static class PropertyEditorTagsExtensions
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// Marks property editors that support tags.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
[Obsolete("Implement a custom IDataValueEditor with the IDataValueTags interface instead")]
public class TagsPropertyEditorAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,29 +274,59 @@ protected void SetEntityTags(IContentBase entity, ITagRepository tagRepo, IJsonS
{
foreach (IProperty property in entity.Properties)
{
TagConfiguration? tagConfiguration = property.GetTagConfiguration(PropertyEditors, DataTypeService);
if (tagConfiguration == null)
if (PropertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor) is false)
{
continue; // not a tags property
continue;
}

if (editor.GetValueEditor() is not IDataValueTags tagsProvider)
{
// support for legacy tag editors, everything from here down to the last continue can be removed when TagsPropertyEditorAttribute is removed
TagConfiguration? tagConfiguration = property.GetTagConfiguration(PropertyEditors, DataTypeService);
if (tagConfiguration == null)
{
continue;
}

if (property.PropertyType.VariesByCulture())
{
var tags = new List<ITag>();
foreach (IPropertyValue pvalue in property.Values)
{
IEnumerable<string> tagsValue = property.GetTagsValue(PropertyEditors, DataTypeService, serializer, pvalue.Culture);
var languageId = LanguageRepository.GetIdByIsoCode(pvalue.Culture);
IEnumerable<Tag> cultureTags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x, LanguageId = languageId });
tags.AddRange(cultureTags);
}

tagRepo.Assign(entity.Id, property.PropertyTypeId, tags);
}
else
{
IEnumerable<string> tagsValue = property.GetTagsValue(PropertyEditors, DataTypeService, serializer); // strings
IEnumerable<Tag> tags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x });
tagRepo.Assign(entity.Id, property.PropertyTypeId, tags);
}

continue; // not implementing IDataValueTags, continue
}

object? configuration = DataTypeService.GetDataType(property.PropertyType.DataTypeId)?.Configuration;

if (property.PropertyType.VariesByCulture())
{
var tags = new List<ITag>();
foreach (IPropertyValue pvalue in property.Values)
{
IEnumerable<string> tagsValue = property.GetTagsValue(PropertyEditors, DataTypeService, serializer, pvalue.Culture);
var languageId = LanguageRepository.GetIdByIsoCode(pvalue.Culture);
IEnumerable<Tag> cultureTags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x, LanguageId = languageId });
tags.AddRange(cultureTags);
tags.AddRange(tagsProvider.GetTags(pvalue.EditedValue, configuration, languageId));
}

tagRepo.Assign(entity.Id, property.PropertyTypeId, tags);
}
else
{
IEnumerable<string> tagsValue = property.GetTagsValue(PropertyEditors, DataTypeService, serializer); // strings
IEnumerable<Tag> tags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x });
IEnumerable<ITag> tags = tagsProvider.GetTags(property.GetValue(), configuration, null);
tagRepo.Assign(entity.Id, property.PropertyTypeId, tags);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace Umbraco.Cms.Core.PropertyEditors;

internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataValueReference
internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataValueReference, IDataValueTags
{
private BlockEditorValues? _blockEditorValues;
private readonly IDataTypeService _dataTypeService;
Expand Down Expand Up @@ -77,6 +77,40 @@ public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
return result;
}

/// <inheritdoc />
public IEnumerable<ITag> GetTags(object? value, object? dataTypeConfiguration, int? languageId)
{
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();

BlockEditorData? blockEditorData = BlockEditorValues.DeserializeAndClean(rawJson);
if (blockEditorData == null)
{
return Enumerable.Empty<ITag>();
}

var result = new List<ITag>();
// loop through all content and settings data
foreach (BlockItemData row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData))
{
foreach (KeyValuePair<string, BlockItemData.BlockPropertyValue> prop in row.PropertyValues)
{
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];

IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
if (valueEditor is not IDataValueTags tagsProvider)
{
continue;
}

object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration;

result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId));
}
}

return result;
Comment thread
Zeegaan marked this conversation as resolved.
}

#region Convert database // editor

// note: there is NO variant support here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private static bool IsSystemPropertyKey(string propKey) =>
protected override IDataValueEditor CreateValueEditor()
=> DataValueEditorFactory.Create<NestedContentPropertyValueEditor>(Attribute!);

internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference
internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference, IDataValueTags
{
private readonly IDataTypeService _dataTypeService;
private readonly ILogger<NestedContentPropertyEditor> _logger;
Expand Down Expand Up @@ -149,6 +149,36 @@ public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
return result;
}

/// <inheritdoc />
public IEnumerable<ITag> GetTags(object? value, object? dataTypeConfiguration, int? languageId)
{
IReadOnlyList<NestedContentValues.NestedContentRowValue> rows =
_nestedContentValues.GetPropertyValues(value);

var result = new List<ITag>();

foreach (NestedContentValues.NestedContentRowValue row in rows.ToList())
{
foreach (KeyValuePair<string, NestedContentValues.NestedContentPropertyValue> prop in row.PropertyValues
.ToList())
{
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];

IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
if (valueEditor is not IDataValueTags tagsProvider)
{
continue;
}

object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration;

result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId));
}
}

return result;
}
Comment thread
Zeegaan marked this conversation as resolved.

#region DB to String

public override string ConvertDbToString(IPropertyType propertyType, object? propertyValue)
Expand Down
79 changes: 74 additions & 5 deletions src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
Expand Down Expand Up @@ -68,18 +69,67 @@ protected override IDataValueEditor CreateValueEditor() =>
protected override IConfigurationEditor CreateConfigurationEditor() =>
new TagConfigurationEditor(_validators, _ioHelper, _localizedTextService, _editorConfigurationParser);

internal class TagPropertyValueEditor : DataValueEditor
internal class TagPropertyValueEditor : DataValueEditor, IDataValueTags
{
private readonly IDataTypeService _dataTypeService;

public TagPropertyValueEditor(
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
DataEditorAttribute attribute)
DataEditorAttribute attribute,
IDataTypeService dataTypeService)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_dataTypeService = dataTypeService;
}

/// <inheritdoc />
public IEnumerable<ITag> GetTags(object? value, object? dataTypeConfiguration, int? languageId)
{
var strValue = value?.ToString();
if (string.IsNullOrWhiteSpace(strValue)) return Enumerable.Empty<ITag>();

var tagConfiguration = ConfigurationEditor.ConfigurationAs<TagConfiguration>(dataTypeConfiguration) ?? new TagConfiguration();

if (tagConfiguration.Delimiter == default)
tagConfiguration.Delimiter = ',';

IEnumerable<string> tags;

switch (tagConfiguration.StorageType)
{
case TagsStorageType.Csv:
tags = strValue.Split(new[] { tagConfiguration.Delimiter }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
break;

case TagsStorageType.Json:
try
{
tags = JsonConvert.DeserializeObject<string[]>(strValue)?.Select(x => x.Trim()) ?? Enumerable.Empty<string>();
}
catch (JsonException)
{
//cannot parse, malformed
tags = Enumerable.Empty<string>();
}

break;

default:
throw new NotSupportedException($"Value \"{tagConfiguration.StorageType}\" is not a valid TagsStorageType.");
}

return tags.Select(x => new Tag
{
Group = tagConfiguration.Group,
Text = x,
LanguageId = languageId,
});
}


/// <inheritdoc />
public override IValueRequiredValidator RequiredValidator => new RequiredJsonValueValidator();

Expand All @@ -93,14 +143,33 @@ public TagPropertyValueEditor(
return null;
}

var tagConfiguration = editorValue.DataTypeConfiguration as TagConfiguration ?? new TagConfiguration();
if (tagConfiguration.Delimiter == default)
tagConfiguration.Delimiter = ',';

string[] trimmedTags = Array.Empty<string>();

if (editorValue.Value is JArray json)
{
return json.HasValues ? json.Select(x => x.Value<string>()) : null;
trimmedTags = json.HasValues ? json.Select(x => x.Value<string>()).OfType<string>().ToArray() : Array.Empty<string>();
}
else if (string.IsNullOrWhiteSpace(value) == false)
{
trimmedTags = value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
}

if (string.IsNullOrWhiteSpace(value) == false)
if (trimmedTags.Length == 0)
{
return value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
return null;
}

switch (tagConfiguration.StorageType)
{
case TagsStorageType.Csv:
return string.Join(tagConfiguration.Delimiter.ToString(), trimmedTags).NullOrWhiteSpaceAsNull();

case TagsStorageType.Json:
return trimmedTags.Length == 0 ? null : JsonConvert.SerializeObject(trimmedTags);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ internal void MapPropertyValuesForPersistence<TPersisted, TSaved>(

// set the value - tags are special
TagsPropertyEditorAttribute? tagAttribute = propertyDto.PropertyEditor.GetTagAttribute();
if (tagAttribute != null)
// when TagsPropertyEditorAttribute is removed this whole if can also be removed
// since the call to sovePropertyValue is all that's needed now
if (tagAttribute is not null && valueEditor is not IDataValueTags)
{
TagConfiguration? tagConfiguration =
ConfigurationEditor.ConfigurationAs<TagConfiguration>(propertyDto.DataType?.Configuration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@
* Used to highlight unsupported properties for the user, changes unsupported properties into a unsupported-property.
*/
var notSupportedProperties = [
"Umbraco.Tags",
"Umbraco.UploadField",
"Umbraco.ImageCropper",
"Umbraco.NestedContent"
Expand Down Expand Up @@ -654,7 +653,7 @@
blockObject.__scope.$evalAsync();
});
});

observer.observe(labelElement[0], {characterData: true, subtree:true});

blockObject.__watchers.push(() => {
Expand All @@ -671,9 +670,9 @@
$index: this.index + 1,
... this.data
};

this.__labelScope = Object.assign(this.__labelScope, labelVars);

$compile(labelElement.contents())(this.__labelScope);
}.bind(blockObject)
} else {
Expand Down
Loading