/// <summary>
 /// Constructor specifies the defaults and sets the ContentPropertyData being used to set the tag values which
 /// can be used to dynamically adjust the tags definition for this property.
 /// </summary>
 /// <param name="propertySaving"></param>
 /// <param name="tagsAttribute"></param>
 protected TagPropertyDefinition(ContentPropertyData propertySaving, SupportTagsAttribute tagsAttribute)
 {
     PropertySaving = propertySaving;
     TagsAttribute = tagsAttribute;
     Delimiter = tagsAttribute.Delimiter;
     ReplaceTags = tagsAttribute.ReplaceTags;
     TagGroup = tagsAttribute.TagGroup;
 }
            public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
            {
                if (editorValue.Value == null || string.IsNullOrEmpty(editorValue.Value.ToString()))
                {
                    return "";
                }

                return SecurityHelper.Encrypt(editorValue.Value.ToString(), SecurityHelper.GetKey());
            }
 /// <summary>
 /// Sets the tag values on the content property based on the property editor's tags attribute
 /// </summary>
 /// <param name="content"></param>
 /// <param name="property"></param>
 /// <param name="propertyData"></param>
 /// <param name="convertedPropertyValue"></param>
 /// <param name="attribute"></param>
 public static void SetPropertyTags(IContentBase content, Property property, ContentPropertyData propertyData, object convertedPropertyValue, SupportTagsAttribute attribute)
 {
     //check for a custom definition
     if (attribute.TagPropertyDefinitionType != null)
     {
         //try to create it
         TagPropertyDefinition def;
         try
         {
             def = (TagPropertyDefinition) Activator.CreateInstance(attribute.TagPropertyDefinitionType, propertyData, attribute);
         }
         catch (Exception ex)
         {
             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);
     }
     else
     {
         SetPropertyTags(content, property, convertedPropertyValue, attribute.Delimiter, attribute.ReplaceTags, attribute.TagGroup, attribute.ValueType);
     }
 }
            public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
            {
                if (editorValue.Value == null)
                    return string.Empty;

                try
                {
                    var value = JsonConvert.DeserializeObject<VortoValue>(editorValue.Value.ToString());

                    var dtd = VortoHelper.GetTargetDataTypeDefinition(value.DtdGuid);
                    var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtd.Id);
                    var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);

                    var keys = value.Values.Keys.ToArray();
                    foreach (var key in keys)
                    {
                        var propData = new ContentPropertyData(value.Values[key], preValues, new Dictionary<string, object>());
                        var newValue = propEditor.ValueEditor.ConvertEditorToDb(propData, value.Values[key]);
                        value.Values[key] = (newValue == null) ? null : JToken.FromObject(newValue);
                    }

                    return JsonConvert.SerializeObject(value);
                }
                catch (Exception ex)
                {
                    LogHelper.Error<VortoPropertyValueEditor>("Error converting DB value to Editor", ex);
                }

                return base.ConvertEditorToDb(editorValue, currentValue);
            }
            public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
            {
                if(editorValue.Value == null || editorValue.Value.ToString() == "")
                    return string.Empty;

                // attempt to deserialize the current property value as an Archetype
                var currentArchetype = currentValue != null ? ArchetypeHelper.Instance.DeserializeJsonToArchetype(currentValue.ToString(), editorValue.PreValues) : null;
                var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(editorValue.Value.ToString(), editorValue.PreValues);

                // get all files uploaded via the file manager (if any)
                var uploadedFiles = editorValue.AdditionalData.ContainsKey("files") ? editorValue.AdditionalData["files"] as IEnumerable<ContentItemFile> : null;
                foreach (var fieldset in archetype.Fieldsets)
                {
                    // assign an id to the fieldset if it has none (e.g. newly created fieldset)
                    fieldset.Id = fieldset.Id == Guid.Empty ? Guid.NewGuid() : fieldset.Id;
                    // find the corresponding fieldset in the current Archetype value (if any)
                    var currentFieldset = currentArchetype != null ? currentArchetype.Fieldsets.FirstOrDefault(f => f.Id == fieldset.Id) : null;
                    foreach (var propDef in fieldset.Properties)
                    {
                        try
                        {
                            // find the corresponding property in the current Archetype value (if any)
                            var currentProperty = currentFieldset != null ? currentFieldset.Properties.FirstOrDefault(p => p.Alias == propDef.Alias) : null;
                            var dtd = ArchetypeHelper.Instance.GetDataTypeByGuid(Guid.Parse(propDef.DataTypeGuid));
                            var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtd.Id);

                            var additionalData = new Dictionary<string, object>();

                            // figure out if we need to pass a files collection in the additional data to the property value editor
                            if(uploadedFiles != null)
                            {
                                if(dtd.PropertyEditorAlias == Constants.PropertyEditorAlias)
                                {
                                    // it's a nested Archetype - just pass all uploaded files to the value editor
                                    additionalData["files"] = uploadedFiles.ToList();
                                }
                                else if (propDef.EditorState != null && propDef.EditorState.FileNames != null && propDef.EditorState.FileNames.Any())
                                {
                                    // pass the uploaded files that belongs to this property (if any) to the value editor
                                    var propertyFiles = propDef.EditorState.FileNames.Select(f => uploadedFiles.FirstOrDefault(u => u.FileName == f)).Where(f => f != null).ToList();
                                    if(propertyFiles.Any())
                                    {
                                        additionalData["files"] = propertyFiles;
                                    }
                                }
                            }
                            var propData = new ContentPropertyData(propDef.Value, preValues, additionalData);
                            var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
                            // make sure to send the current property value (if any) to the PE ValueEditor
                            propDef.Value = propEditor.ValueEditor.ConvertEditorToDb(propData, currentProperty != null ? currentProperty.Value : null);
                        }
                        catch (Exception ex)
                        {
                            LogHelper.Error<ArchetypePropertyValueEditor>(ex.Message, ex);
                        }
                    }
                }

                return archetype.SerializeForPersistence();
            }
            public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
			{
				if (editorValue.Value == null || editorValue.Value.ToString() == "")
					return string.Empty;

                //LogHelper.Info<ArchetypeHelper>(editorValue.Value.ToString());

				var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(editorValue.Value.ToString(), editorValue.PreValues);

				foreach (var fieldset in archetype.Fieldsets)
				{
					foreach (var propDef in fieldset.Properties)
					{
                        try
                        {
						    var dtd = ApplicationContext.Current.Services.DataTypeService.GetDataTypeDefinitionById(Guid.Parse(propDef.DataTypeGuid));
						    var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtd.Id);
						    var propData = new ContentPropertyData(propDef.Value, preValues, new Dictionary<string, object>());
                            var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
						    propDef.Value = propEditor.ValueEditor.ConvertEditorToDb(propData, propDef.Value);
                        }
                        catch (Exception ex)
                        {
                            LogHelper.Error<ArchetypePropertyValueEditor>(ex.Message, ex);
                        }
					}
				}

                return archetype.SerializeForPersistence();
			}
			protected object ConvertEditorToDb_Fake(string propEditorAlias, object value)
			{
				// Lookup the property editor
				var fakePropEditor = PropertyEditorResolver.Current.GetByAlias(propEditorAlias);

				// Create a fake content property data object (note, we don't have a prevalue, so passing in null)
				var fakeContentPropData = new ContentPropertyData(value, null, new Dictionary<string, object>());

				// Get the property editor to do it's conversion
				var fakeNewValue = fakePropEditor.ValueEditor.ConvertEditorToDb(fakeContentPropData, value);

				// Store the value back
				return fakeNewValue == null ? null : fakeNewValue.ToString();
			}
			protected object ConvertEditorToDb_DocType(string docTypeAlias, object value)
			{
				var contentType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(docTypeAlias);

				// Loop through doc type properties
				var propValues = ((JObject)value);
				var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray();
				if (contentType != null && contentType.PropertyTypes != null)
				{
					foreach (var propKey in propValueKeys)
					{
						// Fetch the current property type
						var propType = contentType.PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propKey));

						if (propType == null)
						{
							if (propKey != "name")
							{
								// Property missing so just remove the value
								propValues[propKey] = null;
							}
						}
						else
						{
							// Fetch the property types prevalue
							var propPreValues =
								ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(
									propType.DataTypeDefinitionId);

							// Lookup the property editor
							var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias);

							// Create a fake content property data object
							var contentPropData = new ContentPropertyData(
								propValues[propKey] == null ? null : propValues[propKey].ToString(), propPreValues,
								new Dictionary<string, object>());

							// Get the property editor to do it's conversion
							var newValue = propEditor.ValueEditor.ConvertEditorToDb(contentPropData, propValues[propKey]);

							// Store the value back
							propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue);
						}
					}
				}

				return propValues;
			}
        /// <summary>
        /// A method to deserialize the value that has been saved by an editor
        ///             to an object to be stored in the database.
        /// </summary>
        /// <param name="contentType">
        /// The content type.
        /// </param>
        /// <param name="dcv">
        /// The detached content value.
        /// </param>
        /// <param name="additionalData">
        /// The additional Data.
        /// </param>
        /// <returns>
        /// The converted value.
        /// </returns>
        private KeyValuePair<string, string> ConvertEditorToDb(IContentType contentType, KeyValuePair<string, string> dcv, IDictionary<string, object> additionalData)
        {
            var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == dcv.Key);
            if (propType == null)
            {
                return dcv;
            }

            // Lookup the property editor
            var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias);

            if (propEditor.ValueEditor.IsReadOnly) return dcv;

            // Fetch the property types prevalue
            var propPreValues = _dataTypeService.GetPreValuesCollectionByDataTypeId(propType.DataTypeDefinitionId);

            var rawValue = JsonConvert.DeserializeObject(dcv.Value.Trim());

            //// Create a fake content property data object
            if (additionalData == null) additionalData = new Dictionary<string, object>();
            var contentPropData = new ContentPropertyData(rawValue, propPreValues, additionalData);

            try
            {
                // Get the property editor to do it's conversion
                var newValue = propEditor.ValueEditor.ConvertEditorToDb(contentPropData, null);

                // Store the value back
                var value = newValue == null ? string.Empty :
                    JsonHelper.IsJsonObject(newValue) ?
                                    newValue.ToString() :
                                    JsonConvert.SerializeObject(newValue);

                return new KeyValuePair<string, string>(dcv.Key, value);
            }
            catch (Exception ex)
            {
                var logData = MultiLogger.GetBaseLoggingData();
                logData.AddCategory("ValueEditor");
                MultiLogHelper.WarnWithException<DetachedValuesConverter>(
                    "Failed to convert property value for Database",
                    ex,
                    logData);

                return dcv;
            }
        }
			public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
			{
				if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString()))
					return null;

				var value = JsonConvert.DeserializeObject<MortarValue>(editorValue.Value.ToString());

				if (value == null)
					return null;

				foreach (var key in value.Keys)
				{
					var rowOptionsDocTypeAlias = MortarHelper.GetRowOptionsDocType(editorValue.PreValues, key);

					foreach (var row in value[key])
					{
						row.RawOptions = !string.IsNullOrWhiteSpace(rowOptionsDocTypeAlias)
							? ConvertEditorToDb_DocType(rowOptionsDocTypeAlias, row.RawOptions)
							: null;

						foreach (var item in row.Items)
						{
							if (item != null && item.RawValue != null)
							{
								switch (item.Type.ToLowerInvariant())
								{
									case "richtext":
										item.RawValue = ConvertEditorToDb_Fake(Constants.PropertyEditors.TinyMCEAlias, item.RawValue);
										break;

									case "embed":
										item.RawValue = ConvertEditorToDb_Fake(Constants.PropertyEditors.TextboxMultipleAlias, item.RawValue);
										break;

									case "doctype":
										if (item.AdditionalInfo.ContainsKey("docType")
											&& item.AdditionalInfo["docType"] != null
											&& !item.AdditionalInfo["docType"].IsNullOrWhiteSpace())
										{
											// Lookup the doctype
											var docTypeAlias = item.AdditionalInfo["docType"];

											// We make an assumption that the docTypeAlias is a Guid and attempt to parse it,
											// failing that we assume that the docTypeAlias is the actual alias.
											Guid docTypeGuid;
											if (Guid.TryParse(docTypeAlias, out docTypeGuid))
											{
												docTypeAlias = ApplicationContext.Current.Services.ContentTypeService.GetAliasByGuid(docTypeGuid);

												// NOTE: [LK] As of v0.4.0 we want to persist the DocType's alias
												item.AdditionalInfo["docType"] = docTypeAlias;
											}

											// Serialize the dictionary back
											item.RawValue = ConvertEditorToDb_DocType(docTypeAlias, item.RawValue);
										}

										break;
								}
							}
						}
					}
				}

				return JsonConvert.SerializeObject(value);
			}
 /// <summary>
 /// A method to deserialize the string value that has been saved in the content editor
 /// to an object to be stored in the database.
 /// </summary>
 /// <param name="editorValue"></param>
 /// <param name="currentValue">
 /// The current value that has been persisted to the database for this editor. This value may be usesful for 
 /// how the value then get's deserialized again to be re-persisted. In most cases it will probably not be used.
 /// </param>
 /// <returns></returns>
 /// <remarks>
 /// By default this will attempt to automatically convert the string value to the value type supplied by ValueType.
 /// 
 /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the 
 /// value to the DB will fail when it tries to validate the value type.
 /// </remarks>
 public virtual object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
 {
     var result = TryConvertValueToCrlType(editorValue.Value);
     if (result.Success == false)
     {
         LogHelper.Warn<PropertyValueEditor>("The value " + editorValue.Value + " cannot be converted to the type " + GetDatabaseType());
         return null;
     }
     return result.Result;
 }
        public static IPublishedContent ConvertValueToContent(string id, string docTypeAlias, string dataJson)
        {
            return (IPublishedContent)ApplicationContext.Current.ApplicationCache.RequestCache.GetCacheItem(
                "DocTypeGridEditorHelper.ConvertValueToContent_" + id + "_" + docTypeAlias, () =>
                {
                    using (var timer =  DisposableTimer.DebugDuration<DocTypeGridEditorHelper>(string.Format("ConvertValueToContent ({0}, {1})", id, docTypeAlias)))
                    {
                        Guid docTypeGuid;
                        if (Guid.TryParse(docTypeAlias, out docTypeGuid))
                            docTypeAlias = Services.ContentTypeService.GetAliasByGuid(docTypeGuid);

                        var publishedContentType = PublishedContentType.Get(PublishedItemType.Content, docTypeAlias);
                        var contentType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(docTypeAlias);
                        var properties = new List<IPublishedProperty>();

                        // Convert all the properties
                        var data = JsonConvert.DeserializeObject(dataJson);
                        var propValues = ((JObject) data).ToObject<Dictionary<string, object>>();
                        foreach (var jProp in propValues)
                        {
                            var propType = publishedContentType.GetPropertyType(jProp.Key);
                            if (propType != null)
                            {
                                /* Because we never store the value in the database, we never run the property editors
                                 * "ConvertEditorToDb" method however the property editors will expect their value to
                                 * be in a "DB" state so to get round this, we run the "ConvertEditorToDb" here before
                                 * we go on to convert the value for the view.
                                 */
                                var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias);
                                var propPreValues = Services.DataTypeService.GetPreValuesCollectionByDataTypeId(
                                    propType.DataTypeId);

                                var contentPropData = new ContentPropertyData(
                                    jProp.Value == null ? null : jProp.Value.ToString(),
                                    propPreValues,
                                    new Dictionary<string, object>());

                                var newValue = propEditor.ValueEditor.ConvertEditorToDb(contentPropData, jProp.Value);

                                /* Now that we have the DB stored value, we actually need to then convert it into it's
                                 * XML serialized state as expected by the published property by calling ConvertDbToString
                                 */
                                var propType2 = contentType.PropertyTypes.Single(x => x.Alias == propType.PropertyTypeAlias);
                                var newValue2 = propEditor.ValueEditor.ConvertDbToString(new Property(propType2, newValue), propType2,
                                    ApplicationContext.Current.Services.DataTypeService);

                                properties.Add(new DetachedPublishedProperty(propType, newValue2));
                            }
                        }

                        // Parse out the name manually
                        object nameObj = null;
                        if (propValues.TryGetValue("name", out nameObj))
                        {
                            // Do nothing, we just want to parse out the name if we can
                        }

                        return new DetachedPublishedContent(nameObj == null ? null : nameObj.ToString(), publishedContentType,
                            properties.ToArray());
                    }
                });
        }
        /// <summary>
        /// Overrides the deserialize value so that we can save the file accordingly
        /// </summary>
        /// <param name="editorValue">
        /// This 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") )
        /// </param>
        /// <param name="currentValue">
        /// The current value persisted for this property. This will allow us to determine if we want to create a new
        /// file path or use the existing file path.
        /// </param>
        /// <returns></returns>
        public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
        {
            if (currentValue == null)
            {
                currentValue = string.Empty;
            }

            //if the value is the same then just return the current value so we don't re-process everything
            if (string.IsNullOrEmpty(currentValue.ToString()) == false && editorValue.Value == currentValue.ToString())
            {
                return currentValue;
            }

            //check the editorValue value to see if we need to clear the files or not.
            var clear = false;
            var json = editorValue.Value as JObject;
            if (json != null && json["clearFiles"] != null && json["clearFiles"].Value<bool>())
            {
                clear = json["clearFiles"].Value<bool>();
            }

            var currentPersistedValues = new string[] {};
            if (string.IsNullOrEmpty(currentValue.ToString()) == false)
            {
                currentPersistedValues = currentValue.ToString().Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
            }

            var newValue = new List<string>();

            var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();

            if (clear)
            {
                //Remove any files that are saved for this item
                foreach (var toRemove in currentPersistedValues)
                {
                    fs.DeleteFile(fs.GetRelativePath(toRemove), true);
                }
                return "";
            }
            
            //check for any files
            if (editorValue.AdditionalData.ContainsKey("files"))
            {
                var files = editorValue.AdditionalData["files"] as IEnumerable<ContentItemFile>;
                if (files != null)
                {
                    //now we just need to move the files to where they should be
                    var filesAsArray = files.ToArray();
                    //a list of all of the newly saved files so we can compare with the current saved files and remove the old ones
                    var savedFilePaths = new List<string>();
                    for (var i = 0; i < filesAsArray.Length; i++)
                    {
                        var file = filesAsArray[i];

                        //don't continue if this is not allowed!
                        if (UploadFileTypeValidator.ValidateFileExtension(file.FileName) == false)
                        {
                            continue;
                        }

                        //TODO: ALl of this naming logic needs to be put into the ImageHelper and then we need to change ContentExtensions to do the same!

                        var currentPersistedFile = currentPersistedValues.Length >= (i + 1)
                                                       ? currentPersistedValues[i]
                                                       : "";

                        var name = IOHelper.SafeFileName(file.FileName.Substring(file.FileName.LastIndexOf(IOHelper.DirSepChar) + 1, file.FileName.Length - file.FileName.LastIndexOf(IOHelper.DirSepChar) - 1).ToLower());

                        var subfolder = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
                                            ? currentPersistedFile.Replace(fs.GetUrl("/"), "").Split('/')[0]
                                            : currentPersistedFile.Substring(currentPersistedFile.LastIndexOf("/", StringComparison.Ordinal) + 1).Split('-')[0];

                        int subfolderId;
                        var numberedFolder = int.TryParse(subfolder, out subfolderId)
                                                 ? subfolderId.ToString(CultureInfo.InvariantCulture)
                                                 : MediaSubfolderCounter.Current.Increment().ToString(CultureInfo.InvariantCulture);

                        var fileName = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
                                           ? Path.Combine(numberedFolder, name)
                                           : numberedFolder + "-" + name;

                        using (var fileStream = File.OpenRead(file.TempFilePath))
                        {
                            var umbracoFile = UmbracoMediaFile.Save(fileStream, fileName);

                            if (umbracoFile.SupportsResizing)
                            {
                                var additionalSizes = new List<int>();
                                //get the pre-vals value
                                var thumbs = editorValue.PreValues.FormatAsDictionary();
                                if (thumbs.Any())
                                {
                                    var thumbnailSizes = thumbs.First().Value.Value;
                                    // additional thumbnails configured as prevalues on the DataType
                                    var sep = (thumbnailSizes.Contains("") == false && thumbnailSizes.Contains(",")) ? ',' : ';';
                                    foreach (var thumb in thumbnailSizes.Split(sep))
                                    {
                                        int thumbSize;
                                        if (thumb == "" || int.TryParse(thumb, out thumbSize) == false) continue;
                                        additionalSizes.Add(thumbSize);
                                    }
                                }

                                using (var image = Image.FromStream(fileStream))
                                {
                                    ImageHelper.GenerateMediaThumbnails(fs, fileName, umbracoFile.Extension, image, additionalSizes);
                                }

                            }
                            newValue.Add(umbracoFile.Url);
                            //add to the saved paths
                            savedFilePaths.Add(umbracoFile.Url);
                        }
                        //now remove the temp file
                        File.Delete(file.TempFilePath);   
                    }
                    
                    //Remove any files that are no longer saved for this item
                    foreach (var toRemove in currentPersistedValues.Except(savedFilePaths))
                    {
                        fs.DeleteFile(fs.GetRelativePath(toRemove), true);
                    }
                    

                    return string.Join(",", newValue);
                }
            }

            //if we've made it here, we had no files to save and we were not clearing anything so just persist the same value we had before
            return currentValue;
        }
            /// <summary>
            /// The value passed in from the editor will be an array of simple objects so we'll need to parse them to get the string
            /// </summary>
            /// <param name="editorValue"></param>
            /// <param name="currentValue"></param>
            /// <returns></returns>
            /// <remarks>
            /// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end
            /// </remarks>
            public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
            {
                var asArray = editorValue.Value as JArray;
                if (asArray == null)
                {
                    return null;
                }

                var preVals = editorValue.PreValues.FormatAsDictionary();
                var max = -1;
                if (preVals.Any())
                {
                    try
                    {
                        var json = JsonConvert.DeserializeObject<JObject>(preVals.First().Value.Value);
                        max = int.Parse(json["Maximum"].ToString());
                    }
                    catch (Exception)
                    {
                        //swallow
                        max = -1;
                    }                    
                }

                //The legacy property editor saved this data as new line delimited! strange but we have to maintain that.
                var array = asArray.OfType<JObject>()
                                   .Where(x => x["value"] != null)
                                   .Select(x => x["value"].Value<string>());
                
                //only allow the max if over 0
                if (max > 0)
                {
                    return string.Join(Environment.NewLine, array.Take(max));    
                }
                
                return string.Join(Environment.NewLine, array);
            }
 public TagPropertyEditorTagDefinition(ContentPropertyData propertySaving, SupportTagsAttribute tagsAttribute)
     : base(propertySaving, tagsAttribute)
 {
 }