Undoes the comma encoding for tags and istead adds support to store the tags as json or csv (U4-4741)
This commit is contained in:
@@ -9,6 +9,8 @@ using System.Linq;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
@@ -539,7 +541,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
return ApplicationContext.Current.Services.ContentService.HasPublishedVersion(content.Id);
|
||||
}
|
||||
|
||||
|
||||
#region Tag methods
|
||||
|
||||
///// <summary>
|
||||
@@ -567,6 +569,21 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="tagGroup">The group/category to assign the tags, the default value is "default"</param>
|
||||
/// <returns></returns>
|
||||
public static void SetTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags, bool replaceTags, string tagGroup = "default")
|
||||
{
|
||||
content.SetTags(TagCacheStorageType.Csv, propertyTypeAlias, tags, replaceTags, tagGroup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets tags for the property - will add tags to the tags table and set the property value to be the comma delimited value of the tags.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item to assign the tags to</param>
|
||||
/// <param name="storageType">The tag storage type in cache (default is csv)</param>
|
||||
/// <param name="propertyTypeAlias">The property alias to assign the tags to</param>
|
||||
/// <param name="tags">The tags to assign</param>
|
||||
/// <param name="replaceTags">True to replace the tags on the current property with the tags specified or false to merge them with the currently assigned ones</param>
|
||||
/// <param name="tagGroup">The group/category to assign the tags, the default value is "default"</param>
|
||||
/// <returns></returns>
|
||||
public static void SetTags(this IContentBase content, TagCacheStorageType storageType, string propertyTypeAlias, IEnumerable<string> tags, bool replaceTags, string tagGroup = "default")
|
||||
{
|
||||
var property = content.Properties[propertyTypeAlias];
|
||||
if (property == null)
|
||||
@@ -583,15 +600,39 @@ namespace Umbraco.Core.Models
|
||||
//ensure the property value is set to the same thing
|
||||
if (replaceTags)
|
||||
{
|
||||
property.Value = string.Join(",", trimmedTags);
|
||||
switch (storageType)
|
||||
{
|
||||
case TagCacheStorageType.Csv:
|
||||
property.Value = string.Join(",", trimmedTags);
|
||||
break;
|
||||
case TagCacheStorageType.Json:
|
||||
//json array
|
||||
property.Value = JsonConvert.SerializeObject(trimmedTags);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var currTags = property.Value.ToString().Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)
|
||||
switch (storageType)
|
||||
{
|
||||
case TagCacheStorageType.Csv:
|
||||
var currTags = property.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim());
|
||||
property.Value = string.Join(",", trimmedTags.Union(currTags));
|
||||
property.Value = string.Join(",", trimmedTags.Union(currTags));
|
||||
break;
|
||||
case TagCacheStorageType.Json:
|
||||
var currJson = JsonConvert.DeserializeObject<JArray>(property.Value.ToString());
|
||||
//need to append the new ones
|
||||
foreach (var tag in trimmedTags)
|
||||
{
|
||||
currJson.Add(tag);
|
||||
}
|
||||
//json array
|
||||
property.Value = JsonConvert.SerializeObject(currJson);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -664,7 +705,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
return ApplicationContext.Current.Services.PackagingService.Export(media, true, raiseEvents: false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates the xml representation for the <see cref="IContent"/> object
|
||||
/// </summary>
|
||||
@@ -687,10 +728,10 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
return ((PackagingService)(ApplicationContext.Current.Services.PackagingService)).Export(member);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
9
src/Umbraco.Core/Models/PropertyTagBehavior.cs
Normal file
9
src/Umbraco.Core/Models/PropertyTagBehavior.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
internal enum PropertyTagBehavior
|
||||
{
|
||||
Replace,
|
||||
Remove,
|
||||
Merge
|
||||
}
|
||||
}
|
||||
@@ -33,11 +33,4 @@ namespace Umbraco.Core.Models
|
||||
public IEnumerable<Tuple<string, string>> Tags { get; set; }
|
||||
|
||||
}
|
||||
|
||||
internal enum PropertyTagBehavior
|
||||
{
|
||||
Replace,
|
||||
Remove,
|
||||
Merge
|
||||
}
|
||||
}
|
||||
8
src/Umbraco.Core/Models/TagCacheStorageType.cs
Normal file
8
src/Umbraco.Core/Models/TagCacheStorageType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public enum TagCacheStorageType
|
||||
{
|
||||
Csv,
|
||||
Json
|
||||
}
|
||||
}
|
||||
@@ -185,10 +185,6 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public IEnumerable<TaggedEntity> GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null)
|
||||
{
|
||||
//ensure that we html encode any comma's so they are found!
|
||||
// http://issues.umbraco.org/issue/U4-4741
|
||||
var replaced = tag.Replace(",", ",");
|
||||
|
||||
var nodeObjectType = GetNodeObjectType(objectType);
|
||||
|
||||
var sql = new Sql()
|
||||
@@ -203,7 +199,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
.Where<NodeDto>(dto => dto.NodeObjectType == nodeObjectType)
|
||||
.Where<TagDto>(dto => dto.Tag == replaced);
|
||||
.Where<TagDto>(dto => dto.Tag == tag);
|
||||
|
||||
if (tagGroup.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
@@ -289,7 +285,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
if (group.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
sql = sql.Where<TagDto>(dto => dto.Group == group);
|
||||
}
|
||||
}
|
||||
|
||||
var factory = new TagFactory();
|
||||
|
||||
@@ -333,7 +329,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//NOTE: There's some very clever logic in the umbraco.cms.businesslogic.Tags.Tag to insert tags where they don't exist,
|
||||
// and assign where they don't exist which we've borrowed here. The queries are pretty zany but work, otherwise we'll end up
|
||||
// with quite a few additional queries.
|
||||
|
||||
|
||||
//do all this in one transaction
|
||||
using (var trans = Database.GetTransaction())
|
||||
{
|
||||
@@ -401,7 +397,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public void RemoveTagsFromProperty(int contentId, int propertyTypeId, IEnumerable<ITag> tags)
|
||||
{
|
||||
var tagSetSql = GetTagSet(tags);
|
||||
|
||||
|
||||
var deleteSql = string.Concat("DELETE FROM cmsTagRelationship WHERE nodeId = ",
|
||||
contentId,
|
||||
" AND propertyTypeId = ",
|
||||
@@ -446,11 +442,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
var array = tagsToInsert
|
||||
.Select(tag =>
|
||||
string.Format("select '{0}' as Tag, '{1}' as [Group]",
|
||||
PetaPocoExtensions.EscapeAtSymbols(
|
||||
tag.Text
|
||||
.Replace("'", "''") //NOTE: I'm not sure about this apostrophe replacement but it's been like that for a long time
|
||||
.Replace(",", ",")), //NOTE: We need to replace commas with html encoded ones: http://issues.umbraco.org/issue/U4-4741
|
||||
tag.Group))
|
||||
PetaPocoExtensions.EscapeAtSymbols(tag.Text.Replace("'", "''")), tag.Group))
|
||||
.ToArray();
|
||||
return "(" + string.Join(" union ", array).Replace(" ", " ") + ") as TagSet";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
@@ -30,6 +31,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
Delimiter = ",";
|
||||
ReplaceTags = true;
|
||||
TagGroup = "default";
|
||||
StorageType = TagCacheStorageType.Csv;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,6 +39,11 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// </summary>
|
||||
public TagValueType ValueType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines how to store the tags in cache (CSV or Json)
|
||||
/// </summary>
|
||||
public TagCacheStorageType StorageType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines a custom delimiter, the default is a comma
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Umbraco.Core.Models.Editors;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Editors;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
@@ -30,8 +31,14 @@ namespace Umbraco.Core.PropertyEditors
|
||||
Delimiter = tagsAttribute.Delimiter;
|
||||
ReplaceTags = tagsAttribute.ReplaceTags;
|
||||
TagGroup = tagsAttribute.TagGroup;
|
||||
StorageType = TagCacheStorageType.Csv;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines how to store the tags in cache (CSV or Json)
|
||||
/// </summary>
|
||||
public virtual TagCacheStorageType StorageType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines a custom delimiter, the default is a comma
|
||||
/// </summary>
|
||||
|
||||
@@ -349,12 +349,14 @@
|
||||
<Compile Include="Models\MemberGroup.cs" />
|
||||
<Compile Include="Models\MemberTypePropertyProfileAccess.cs" />
|
||||
<Compile Include="Models\Notification.cs" />
|
||||
<Compile Include="Models\PropertyTagBehavior.cs" />
|
||||
<Compile Include="Models\PublishedContent\IPublishedContentExtended.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedPropertyBase.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentModelFactoryImpl.cs" />
|
||||
<Compile Include="Models\PublishedContent\IPublishedContentModelFactory.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentModel.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentModelFactoryResolver.cs" />
|
||||
<Compile Include="Models\TagCacheStorageType.cs" />
|
||||
<Compile Include="Models\TaggableObjectTypes.cs" />
|
||||
<Compile Include="Models\TemplateNode.cs" />
|
||||
<Compile Include="Models\UmbracoEntityExtensions.cs" />
|
||||
|
||||
@@ -12,15 +12,21 @@ angular.module("umbraco")
|
||||
//load current value
|
||||
$scope.currentTags = [];
|
||||
if ($scope.model.value) {
|
||||
$scope.currentTags = $scope.model.value.split(",");
|
||||
if ($scope.model.config.storageType && $scope.model.config.storageType === "Json") {
|
||||
//it's a json array already
|
||||
$scope.currentTags = $scope.model.value;
|
||||
}
|
||||
else {
|
||||
//it is csv
|
||||
$scope.currentTags = $scope.model.value.split(",");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Helper method to add a tag on enter or on typeahead select
|
||||
function addTag(tagToAdd) {
|
||||
if (tagToAdd.length > 0) {
|
||||
if ($scope.currentTags.indexOf(tagToAdd) < 0) {
|
||||
//we need to html encode any tag containing commas: http://issues.umbraco.org/issue/U4-4741
|
||||
tagToAdd = tagToAdd.replace(/\,/g, ",");
|
||||
if ($scope.currentTags.indexOf(tagToAdd) < 0) {
|
||||
$scope.currentTags.push(tagToAdd);
|
||||
}
|
||||
}
|
||||
@@ -49,16 +55,24 @@ angular.module("umbraco")
|
||||
}
|
||||
};
|
||||
|
||||
//sync model on submit (needed since we convert an array to string)
|
||||
//sync model on submit, always push up a json array
|
||||
$scope.$on("formSubmitting", function (ev, args) {
|
||||
$scope.model.value = $scope.currentTags.join();
|
||||
$scope.model.value = $scope.currentTags;
|
||||
});
|
||||
|
||||
//vice versa
|
||||
$scope.model.onValueChanged = function (newVal, oldVal) {
|
||||
//update the display val again if it has changed from the server
|
||||
$scope.model.val = newVal;
|
||||
$scope.currentTags = $scope.model.value.split(",");
|
||||
$scope.model.value = newVal;
|
||||
|
||||
if ($scope.model.config.storageType && $scope.model.config.storageType === "Json") {
|
||||
//it's a json array already
|
||||
$scope.currentTags = $scope.model.value;
|
||||
}
|
||||
else {
|
||||
//it is csv
|
||||
$scope.currentTags = $scope.model.value.split(",");
|
||||
}
|
||||
};
|
||||
|
||||
//configure the tags data source
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div >
|
||||
|
||||
<select name="dropDownList"
|
||||
class="umb-editor umb-dropdown"
|
||||
ng-model="model.value">
|
||||
<option>Csv</option>
|
||||
<option>Json</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
@@ -43,15 +43,15 @@ namespace Umbraco.Web.Editors
|
||||
LogHelper.Error<TagExtractor>("Could not create custom " + attribute.TagPropertyDefinitionType + " tag definition", ex);
|
||||
throw;
|
||||
}
|
||||
SetPropertyTags(content, property, convertedPropertyValue, def.Delimiter, def.ReplaceTags, def.TagGroup, attribute.ValueType);
|
||||
SetPropertyTags(content, property, convertedPropertyValue, def.Delimiter, def.ReplaceTags, def.TagGroup, attribute.ValueType, def.StorageType);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPropertyTags(content, property, convertedPropertyValue, attribute.Delimiter, attribute.ReplaceTags, attribute.TagGroup, attribute.ValueType);
|
||||
SetPropertyTags(content, property, convertedPropertyValue, attribute.Delimiter, attribute.ReplaceTags, attribute.TagGroup, attribute.ValueType, attribute.StorageType);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetPropertyTags(IContentBase content, Property property, object convertedPropertyValue, string delimiter, bool replaceTags, string tagGroup, TagValueType valueType)
|
||||
public static void SetPropertyTags(IContentBase content, Property property, object convertedPropertyValue, string delimiter, bool replaceTags, string tagGroup, TagValueType valueType, TagCacheStorageType storageType)
|
||||
{
|
||||
if (convertedPropertyValue == null)
|
||||
{
|
||||
@@ -62,14 +62,14 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
case TagValueType.FromDelimitedValue:
|
||||
var tags = convertedPropertyValue.ToString().Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
|
||||
content.SetTags(property.Alias, tags, replaceTags, tagGroup);
|
||||
content.SetTags(storageType, property.Alias, tags, replaceTags, tagGroup);
|
||||
break;
|
||||
case TagValueType.CustomTagList:
|
||||
//for this to work the object value must be IENumerable<string>
|
||||
var stringList = convertedPropertyValue as IEnumerable<string>;
|
||||
if (stringList != null)
|
||||
{
|
||||
content.SetTags(property.Alias, stringList, replaceTags, tagGroup);
|
||||
content.SetTags(storageType, property.Alias, stringList, replaceTags, tagGroup);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using Umbraco.Core.Models.Editors;
|
||||
using System;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Editors;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to dynamically change the tag group based on the pre-values
|
||||
/// Used to dynamically change the tag group and storage type based on the pre-values
|
||||
/// </summary>
|
||||
internal class TagPropertyEditorTagDefinition : TagPropertyDefinition
|
||||
{
|
||||
@@ -21,5 +24,16 @@ namespace Umbraco.Web.PropertyEditors
|
||||
return preVals.ContainsKey("group") ? preVals["group"].Value : "default";
|
||||
}
|
||||
}
|
||||
|
||||
public override TagCacheStorageType StorageType
|
||||
{
|
||||
get
|
||||
{
|
||||
var preVals = PropertySaving.PreValues.FormatAsDictionary();
|
||||
return preVals.ContainsKey("storageType")
|
||||
? Enum<TagCacheStorageType>.Parse(preVals["storageType"].Value)
|
||||
: TagCacheStorageType.Csv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Editors;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
[SupportTags(typeof(TagPropertyEditorTagDefinition))]
|
||||
[SupportTags(typeof(TagPropertyEditorTagDefinition), ValueType = TagValueType.CustomTagList)]
|
||||
[PropertyEditor(Constants.PropertyEditors.TagsAlias, "Tags", "tags")]
|
||||
public class TagsPropertyEditor : PropertyEditor
|
||||
{
|
||||
@@ -12,7 +17,8 @@ namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
_defaultPreVals = new Dictionary<string, object>
|
||||
{
|
||||
{"group", "default"}
|
||||
{"group", "default"},
|
||||
{"storageType", TagCacheStorageType.Csv.ToString()}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,11 +33,36 @@ namespace Umbraco.Web.PropertyEditors
|
||||
set { _defaultPreVals = value; }
|
||||
}
|
||||
|
||||
protected override PropertyValueEditor CreateValueEditor()
|
||||
{
|
||||
return new TagPropertyValueEditor(base.CreateValueEditor());
|
||||
}
|
||||
|
||||
protected override PreValueEditor CreatePreValueEditor()
|
||||
{
|
||||
return new TagPreValueEditor();
|
||||
}
|
||||
|
||||
internal class TagPropertyValueEditor : PropertyValueEditorWrapper
|
||||
{
|
||||
public TagPropertyValueEditor(PropertyValueEditor wrapped)
|
||||
: base(wrapped)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This needs to return IEnumerable{string}
|
||||
/// </summary>
|
||||
/// <param name="editorValue"></param>
|
||||
/// <param name="currentValue"></param>
|
||||
/// <returns></returns>
|
||||
public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
|
||||
{
|
||||
var json = editorValue.Value as JArray;
|
||||
return json == null ? null : json.Select(x => x.Value<string>());
|
||||
}
|
||||
}
|
||||
|
||||
internal class TagPreValueEditor : PreValueEditor
|
||||
{
|
||||
public TagPreValueEditor()
|
||||
@@ -43,11 +74,26 @@ namespace Umbraco.Web.PropertyEditors
|
||||
Name = "Tag group",
|
||||
View = "requiredfield"
|
||||
});
|
||||
|
||||
Fields.Add(new PreValueField(new ManifestPropertyValidator {Type = "Required"})
|
||||
{
|
||||
Description = "Select whether to store the tags in cache as CSV (default) or as JSON. The only benefits of storage as JSON is that you are able to have commas in a tag value but this will require parsing the json in your views or using a property value converter",
|
||||
Key = "storageType",
|
||||
Name = "Storage Type",
|
||||
View = "views/propertyeditors/tags/tags.prevalues.html"
|
||||
});
|
||||
}
|
||||
|
||||
public override IDictionary<string, object> ConvertDbToEditor(IDictionary<string, object> defaultPreVals, Core.Models.PreValueCollection persistedPreVals)
|
||||
public override IDictionary<string, object> ConvertDbToEditor(IDictionary<string, object> defaultPreVals, PreValueCollection persistedPreVals)
|
||||
{
|
||||
var result = base.ConvertDbToEditor(defaultPreVals, persistedPreVals);
|
||||
|
||||
//This is required because we've added this pre-value so old installs that don't have it will need to have a default.
|
||||
if (result.ContainsKey("storageType") == false || result["storageType"] == null || result["storageType"].ToString().IsNullOrWhiteSpace())
|
||||
{
|
||||
result["storageType"] = TagCacheStorageType.Csv.ToString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user