|
| 1 | + |
| 2 | +namespace Umbraco.Deploy.Contrib.Connectors.GridCellValueConnectors |
| 3 | +{ |
| 4 | + using Deploy.Connectors.ValueConnectors.Services; |
| 5 | + using Newtonsoft.Json; |
| 6 | + using Newtonsoft.Json.Linq; |
| 7 | + using System; |
| 8 | + using System.Collections.Generic; |
| 9 | + using Umbraco.Core; |
| 10 | + using Umbraco.Core.Deploy; |
| 11 | + using Umbraco.Core.Logging; |
| 12 | + using Umbraco.Core.Models; |
| 13 | + using Umbraco.Core.Services; |
| 14 | + |
| 15 | + public class DocTypeGridEditorCellValueConnector : IGridCellValueConnector |
| 16 | + { |
| 17 | + private readonly ILogger _logger; |
| 18 | + private readonly IContentTypeService _contentTypeService; |
| 19 | + private readonly Lazy<ValueConnectorCollection> _valueConnectorsLazy; |
| 20 | + private ValueConnectorCollection ValueConnectors => _valueConnectorsLazy.Value; |
| 21 | + |
| 22 | + public DocTypeGridEditorCellValueConnector(ILogger logger, IContentTypeService contentTypeService, Lazy<ValueConnectorCollection> valueConnectors) |
| 23 | + { |
| 24 | + this._logger = logger; |
| 25 | + this._contentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService)); |
| 26 | + this._valueConnectorsLazy = valueConnectors ?? throw new ArgumentNullException(nameof(valueConnectors)); |
| 27 | + } |
| 28 | + |
| 29 | + public bool IsConnector(string view) |
| 30 | + { |
| 31 | + return !string.IsNullOrWhiteSpace(view) && view.Contains("doctypegrideditor"); |
| 32 | + } |
| 33 | + |
| 34 | + public string GetValue(GridValue.GridControl gridControl, ICollection<ArtifactDependency> dependencies) |
| 35 | + { |
| 36 | + // cancel if there's no values |
| 37 | + if (gridControl.Value == null || gridControl.Value.HasValues == false) return null; |
| 38 | + |
| 39 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - Grid Values: {gridControl.Value}"); |
| 40 | + |
| 41 | + var docTypeGridEditorContent = JsonConvert.DeserializeObject<DocTypeGridEditorValue>(gridControl.Value.ToString()); |
| 42 | + |
| 43 | + |
| 44 | + // if an 'empty' dtge item has been added - it has no ContentTypeAlias set .. just return and don't throw. |
| 45 | + if (docTypeGridEditorContent == null || string.IsNullOrWhiteSpace(docTypeGridEditorContent.ContentTypeAlias)) |
| 46 | + { |
| 47 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>("GetValue - DTGE Empty without ContentTypeAlias"); |
| 48 | + return null; |
| 49 | + } |
| 50 | + |
| 51 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - ContentTypeAlias - {docTypeGridEditorContent.ContentTypeAlias}"); |
| 52 | + |
| 53 | + // check if the doc type exist - else abort packaging |
| 54 | + var contentType = this._contentTypeService.Get(docTypeGridEditorContent.ContentTypeAlias); |
| 55 | + |
| 56 | + if (contentType == null) |
| 57 | + { |
| 58 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>("GetValue - Missing ContentType"); |
| 59 | + throw new InvalidOperationException($"Could not resolve the Content Type for the Doc Type Grid Editor property: {docTypeGridEditorContent.ContentTypeAlias}"); |
| 60 | + } |
| 61 | + |
| 62 | + // add content type as a dependency |
| 63 | + dependencies.Add(new ArtifactDependency(contentType.GetUdi(), false, ArtifactDependencyMode.Match)); |
| 64 | + |
| 65 | + // find all properties |
| 66 | + var propertyTypes = contentType.CompositionPropertyTypes; |
| 67 | + |
| 68 | + foreach (var propertyType in propertyTypes) |
| 69 | + { |
| 70 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - PropertyTypeAlias - {propertyType.Alias}"); |
| 71 | + |
| 72 | + // test if there's a value for the given property |
| 73 | + if (this.IsValueNull(docTypeGridEditorContent, propertyType, out var value)) continue; |
| 74 | + |
| 75 | + // if the value is an Udi then add it as a dependency |
| 76 | + if (this.AddUdiDependency(dependencies, value)) continue; |
| 77 | + |
| 78 | + JToken jtokenValue = null; |
| 79 | + var parsedValue = string.Empty; |
| 80 | + |
| 81 | + // throws if not found - no need for a null check |
| 82 | + var propValueConnector = this.ValueConnectors.Get(propertyType); |
| 83 | + |
| 84 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - PropertyValueConnectorAlias - {propValueConnector.PropertyEditorAliases}"); |
| 85 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - propertyTypeValue - {value}"); |
| 86 | + |
| 87 | + //properties like MUP / Nested Content seems to be in json and it breaks, so pass it back as jtokenValue right away |
| 88 | + if (this.IsJson(value)) |
| 89 | + { |
| 90 | + jtokenValue = this.GetJTokenValue(value); |
| 91 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - Inner JtokenValue - Eg MUP/NestedContent {jtokenValue}"); |
| 92 | + } |
| 93 | + else |
| 94 | + { |
| 95 | + parsedValue = propValueConnector.ToArtifact(value, propertyType, dependencies); |
| 96 | + |
| 97 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - ParsedValue - {parsedValue}"); |
| 98 | + |
| 99 | + if (this.IsJson(parsedValue)) |
| 100 | + { |
| 101 | + // if that's the case we'll need to add it as a json object instead of string to avoid it being escaped |
| 102 | + jtokenValue = this.GetJTokenValue(parsedValue); |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + if (jtokenValue != null) |
| 107 | + { |
| 108 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - JtokenValue - {jtokenValue}"); |
| 109 | + docTypeGridEditorContent.Value[propertyType.Alias] = jtokenValue; |
| 110 | + } |
| 111 | + else |
| 112 | + { |
| 113 | + docTypeGridEditorContent.Value[propertyType.Alias] = parsedValue; |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + var resolvedValue = JsonConvert.SerializeObject(docTypeGridEditorContent); |
| 118 | + |
| 119 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - ResolvedValue - {resolvedValue}"); |
| 120 | + |
| 121 | + return resolvedValue; |
| 122 | + } |
| 123 | + |
| 124 | + public void SetValue(GridValue.GridControl gridControl) |
| 125 | + { |
| 126 | + // cancel if there's no values |
| 127 | + if (string.IsNullOrWhiteSpace(gridControl.Value.ToString())) return; |
| 128 | + |
| 129 | + // For some reason the control value isn't properly parsed so we need this extra step to parse it into a JToken |
| 130 | + gridControl.Value = JToken.Parse(gridControl.Value.ToString()); |
| 131 | + |
| 132 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - GridControlValue - {gridControl.Value}"); |
| 133 | + |
| 134 | + var docTypeGridEditorContent = JsonConvert.DeserializeObject<DocTypeGridEditorValue>(gridControl.Value.ToString()); |
| 135 | + |
| 136 | + if (docTypeGridEditorContent == null) return; |
| 137 | + |
| 138 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - ContentTypeAlias - {docTypeGridEditorContent.ContentTypeAlias}"); |
| 139 | + |
| 140 | + // check if the doc type exist - else abort packaging |
| 141 | + var contentType = this._contentTypeService.Get(docTypeGridEditorContent.ContentTypeAlias); |
| 142 | + |
| 143 | + if (contentType == null) |
| 144 | + throw new InvalidOperationException($"Could not resolve the Content Type for the Doc Type Grid Editor property: {docTypeGridEditorContent.ContentTypeAlias}"); |
| 145 | + |
| 146 | + |
| 147 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - ContentType - {contentType}"); |
| 148 | + |
| 149 | + |
| 150 | + // find all properties |
| 151 | + var propertyTypes = contentType.CompositionPropertyTypes; |
| 152 | + |
| 153 | + foreach (var propertyType in propertyTypes) |
| 154 | + { |
| 155 | + // test if there's a value for the given property |
| 156 | + object value; |
| 157 | + object convertedValue; |
| 158 | + JToken jtokenValue = null; |
| 159 | + |
| 160 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - PropertyEditorAlias -- {propertyType.PropertyEditorAlias}"); |
| 161 | + |
| 162 | + if (!docTypeGridEditorContent.Value.TryGetValue(propertyType.Alias, out value) || value == null) |
| 163 | + { |
| 164 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>("SetValue - Value Is Null"); |
| 165 | + continue; |
| 166 | + } |
| 167 | + |
| 168 | + // throws if not found - no need for a null check |
| 169 | + var propValueConnector = this.ValueConnectors.Get(propertyType); |
| 170 | + propValueConnector.FromArtifact(value.ToString(), propertyType, ""); |
| 171 | + |
| 172 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - PropertyValueConnecterType - {propValueConnector.GetType()}"); |
| 173 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - Value - {value}"); |
| 174 | + |
| 175 | + //don't convert if it's json (MUP/Nested) / udi (Content/Media/Multi Pickers) / guid (form picker) / rte / textstring values |
| 176 | + if (this.IsJson(value) || this.IsUdi(value) || this.IsGuid(value) || this.IsText(propertyType.PropertyEditorAlias)) |
| 177 | + { |
| 178 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - IsJsonValue / IsUdiValue / IsGuidValue / IsTextValue - {value}"); |
| 179 | + convertedValue = this.CleanValue(propertyType.PropertyEditorAlias, value); |
| 180 | + } |
| 181 | + else |
| 182 | + { |
| 183 | + //using mockContent to get the converted values |
| 184 | + var mockProperty = new Property(propertyType); |
| 185 | + var mockContent = new Content("mockContentGrid", -1, new ContentType(-1), new PropertyCollection(new List<Property> { mockProperty })); |
| 186 | + convertedValue = mockContent.GetValue(mockProperty.Alias); |
| 187 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - ConvertedValue - Before - {convertedValue}"); |
| 188 | + } |
| 189 | + |
| 190 | + // integers needs to be converted into strings for DTGE to work |
| 191 | + if (convertedValue is int) |
| 192 | + { |
| 193 | + docTypeGridEditorContent.Value[propertyType.Alias] = convertedValue.ToString(); |
| 194 | + } |
| 195 | + else if (convertedValue == null) |
| 196 | + { |
| 197 | + //Assign the null back - otherwise the check for JSON will fail as we cant convert a null to a string |
| 198 | + //NOTE: LinkPicker2 for example if no link set is returning a null as opposed to empty string |
| 199 | + docTypeGridEditorContent.Value[propertyType.Alias] = null; |
| 200 | + } |
| 201 | + else |
| 202 | + { |
| 203 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - ConvertedValue - After - {convertedValue}"); |
| 204 | + |
| 205 | + if (this.IsJson(convertedValue)) |
| 206 | + { |
| 207 | + // test if the value is a json object (thus could be a nested complex editor) |
| 208 | + // if that's the case we'll need to add it as a json object instead of string to avoid it being escaped |
| 209 | + jtokenValue = this.GetJTokenValue(convertedValue); |
| 210 | + } |
| 211 | + |
| 212 | + if (jtokenValue != null) |
| 213 | + { |
| 214 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - jtokenValue - {jtokenValue}"); |
| 215 | + docTypeGridEditorContent.Value[propertyType.Alias] = jtokenValue; |
| 216 | + } |
| 217 | + else |
| 218 | + { |
| 219 | + docTypeGridEditorContent.Value[propertyType.Alias] = convertedValue; |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + var jtokenObj = JToken.FromObject(docTypeGridEditorContent); |
| 225 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"SetValue - jtokenObject - {jtokenObj}"); |
| 226 | + gridControl.Value = jtokenObj; |
| 227 | + } |
| 228 | + |
| 229 | + #region Private Methods |
| 230 | + |
| 231 | + private JToken GetJTokenValue(object value) |
| 232 | + { |
| 233 | + return value != null && this.IsJson(value) ? JToken.Parse(value.ToString()) : null; |
| 234 | + } |
| 235 | + |
| 236 | + private bool IsJson(object value) |
| 237 | + { |
| 238 | + return value != null && value.ToString().DetectIsJson(); |
| 239 | + } |
| 240 | + |
| 241 | + private bool IsGuid(object value) |
| 242 | + { |
| 243 | + return Guid.TryParse(value.ToString(), out var guidID); |
| 244 | + } |
| 245 | + |
| 246 | + private bool IsUdi(object value) |
| 247 | + { |
| 248 | + //for picker like content/media either single or multi, it comes with udi values |
| 249 | + return value.ToString().Contains("umb://"); |
| 250 | + } |
| 251 | + |
| 252 | + private bool IsText(string editorAlias) |
| 253 | + { |
| 254 | + //if it's either RTE / Textstring data type |
| 255 | + return this.IsRichtext(editorAlias) || this.IsTextstring(editorAlias); |
| 256 | + } |
| 257 | + |
| 258 | + private bool IsRichtext(string editorAlias) |
| 259 | + { |
| 260 | + return editorAlias.InvariantEquals("Umbraco.TinyMCE"); |
| 261 | + } |
| 262 | + |
| 263 | + private bool IsTextstring(string editorAlias) |
| 264 | + { |
| 265 | + return editorAlias.InvariantEquals("Umbraco.TextBox"); |
| 266 | + } |
| 267 | + |
| 268 | + private string CleanValue(string editorAlias, object value) |
| 269 | + { |
| 270 | + //can't tell why textstring have got a weird character 's' in front coming from deploy? so removing first char if that's the case |
| 271 | + return this.IsTextstring(editorAlias) ? value.ToString().Substring(1) : value.ToString(); |
| 272 | + } |
| 273 | + |
| 274 | + private bool AddUdiDependency(ICollection<ArtifactDependency> dependencies, object value) |
| 275 | + { |
| 276 | + if (Udi.TryParse(value.ToString(), out var udi)) |
| 277 | + { |
| 278 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>($"GetValue - Udi Dependency - {udi}"); |
| 279 | + dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Match)); |
| 280 | + return true; |
| 281 | + } |
| 282 | + |
| 283 | + return false; |
| 284 | + } |
| 285 | + |
| 286 | + private bool IsValueNull(DocTypeGridEditorValue docTypeGridEditorContent, PropertyType propertyType, out object objVal) |
| 287 | + { |
| 288 | + if (!docTypeGridEditorContent.Value.TryGetValue(propertyType.Alias, out objVal) || objVal == null) |
| 289 | + { |
| 290 | + this._logger.Debug<DocTypeGridEditorCellValueConnector>("GetValue - Value is null"); |
| 291 | + return true; |
| 292 | + } |
| 293 | + |
| 294 | + return false; |
| 295 | + } |
| 296 | + |
| 297 | + #endregion |
| 298 | + |
| 299 | + #region DocTypeGridEditor model |
| 300 | + private class DocTypeGridEditorValue |
| 301 | + { |
| 302 | + [JsonProperty("id")] |
| 303 | + public Guid Id { get; set; } |
| 304 | + |
| 305 | + [JsonProperty("dtgeContentTypeAlias")] |
| 306 | + public string ContentTypeAlias { get; set; } |
| 307 | + |
| 308 | + [JsonProperty("value")] |
| 309 | + public Dictionary<string, object> Value { get; set; } |
| 310 | + } |
| 311 | + |
| 312 | + #endregion |
| 313 | + } |
| 314 | +} |
0 commit comments