Skip to content

Commit 4f5539e

Browse files
author
Claus
committed
adding MultiUrlPickerValueConnector
1 parent 2ffd9f1 commit 4f5539e

File tree

2 files changed

+336
-3
lines changed

2 files changed

+336
-3
lines changed

src/Umbraco.Deploy.Contrib.Connectors/Umbraco.Deploy.Contrib.Connectors.csproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,16 +158,15 @@
158158
<ItemGroup>
159159
<Compile Include="Properties\AssemblyInfo.cs" />
160160
<Compile Include="Properties\VersionInfo.cs" />
161+
<Compile Include="ValueConnectors\MultiUrlPickerValueConnector.cs" />
161162
</ItemGroup>
162163
<ItemGroup>
163164
<Content Include="GridCellValueConnectors\dummy.txt" />
164165
</ItemGroup>
165166
<ItemGroup>
166167
<None Include="packages.config" />
167168
</ItemGroup>
168-
<ItemGroup>
169-
<Folder Include="ValueConnectors\" />
170-
</ItemGroup>
169+
<ItemGroup />
171170
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
172171
<Import Project="..\packages\Umbraco.SqlServerCE.4.0.0.1\build\Umbraco.SqlServerCE.targets" Condition="Exists('..\packages\Umbraco.SqlServerCE.4.0.0.1\build\Umbraco.SqlServerCE.targets')" />
173172
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text.RegularExpressions;
4+
using Newtonsoft.Json;
5+
using Newtonsoft.Json.Linq;
6+
using Umbraco.Core;
7+
using Umbraco.Core.Deploy;
8+
using Umbraco.Core.Logging;
9+
using Umbraco.Core.Models;
10+
using Umbraco.Core.Services;
11+
12+
namespace Umbraco.Deploy.Contrib.Connectors.ValueConnectors
13+
{
14+
public class MultiUrlPickerValueConnector : IValueConnector
15+
{
16+
private readonly IEntityService _entityService;
17+
private readonly IMediaService _mediaService;
18+
private readonly ILogger _logger;
19+
20+
// Used to fetch the udi from a umb://-based url
21+
private static readonly Regex MediaUdiSrcRegex = new Regex(@"(?<udi>umb://media/[A-z0-9]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="MultiUrlPickerValueConnector"/> class.
25+
/// Source found here: https://github.com/rasmusjp/umbraco-multi-url-picker
26+
/// MultiUrlPicker can have a list of links, these can be document links, links to files in the
27+
/// media library, or just a text string with a link to whatever. We need to resolve if its a link
28+
/// to a document or a file in the media library, and transfer it as an artifact to the target environment.
29+
/// The object value is stored as json, and is an array of Links, defined with the following properties
30+
/// Link = {
31+
/// id,
32+
/// name,
33+
/// url,
34+
/// target,
35+
/// isMedia,
36+
/// icon
37+
/// }
38+
/// https://github.com/rasmusjp/umbraco-multi-url-picker/blob/master/src/RJP.MultiUrlPicker/App_Plugins/RJP.MultiUrlPicker/MultiUrlPicker.js#L120
39+
/// </summary>
40+
/// <param name="entityService">An <see cref="IEntityService"/> implementation.</param>
41+
/// <param name="mediaService"></param>
42+
/// <param name="logger"></param>
43+
public MultiUrlPickerValueConnector(IEntityService entityService, IMediaService mediaService, ILogger logger)
44+
{
45+
if (entityService == null) throw new ArgumentNullException(nameof(entityService));
46+
if (mediaService == null) throw new ArgumentNullException(nameof(mediaService));
47+
_entityService = entityService;
48+
_mediaService = mediaService;
49+
_logger = logger;
50+
}
51+
52+
public string ToArtifact(object value, PropertyType propertyType, ICollection<ArtifactDependency> dependencies)
53+
{
54+
var svalue = value as string;
55+
if (string.IsNullOrWhiteSpace(svalue))
56+
return null;
57+
58+
var valueAsJToken = JToken.Parse(svalue);
59+
60+
if (valueAsJToken is JArray)
61+
{
62+
// Multiple links, parse as JArray
63+
var links = JsonConvert.DeserializeObject<JArray>(svalue);
64+
if (links == null)
65+
return null;
66+
67+
foreach (var link in links)
68+
{
69+
var isMedia = link["isMedia"] != null;
70+
int intId;
71+
string url;
72+
GuidUdi guidUdi;
73+
// Only do processing if the Id is set on the element. OR if the url is set and its a media item
74+
if (TryParseJTokenAttr(link, "id", out intId))
75+
{
76+
// Checks weather we are resolving a media item or a document
77+
var objectTypeId = isMedia
78+
? UmbracoObjectTypes.Media
79+
: UmbracoObjectTypes.Document;
80+
var entityType = isMedia ? Constants.UdiEntityType.Media : Constants.UdiEntityType.Document;
81+
82+
var guidAttempt = _entityService.GetKey(intId, objectTypeId);
83+
if (guidAttempt.Success == false)
84+
continue;
85+
86+
var udi = new GuidUdi(entityType, guidAttempt.Result);
87+
// Add the artifact dependency
88+
dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist));
89+
90+
// Set the Id attribute to the udi
91+
link["id"] = udi.ToString();
92+
}
93+
else if (TryParseJTokenAttr(link, "udi", out guidUdi))
94+
{
95+
var entity = _entityService.Get(guidUdi.Guid, Constants.UdiEntityType.ToUmbracoObjectType(guidUdi.EntityType));
96+
if (entity == null)
97+
continue;
98+
99+
// Add the artifact dependency
100+
dependencies.Add(new ArtifactDependency(guidUdi, false, ArtifactDependencyMode.Exist));
101+
}
102+
else if (isMedia && TryParseJTokenAttr(link, "url", out url))
103+
{
104+
// This state can happen due to an issue in RJP.MultiUrlPicker(or our linkPicker in RTE which it relies on),
105+
// where you edit a media link, and just hit "Select".
106+
// That will set the id to null, but the url will still be filled. We try to get the media item, and if so add it as
107+
// a dependency to the package. If we can't find it, we abort(aka continue)
108+
var entry = _mediaService.GetMediaByPath(url);
109+
if (entry == null)
110+
continue;
111+
112+
// Add the artifact dependency
113+
var udi = entry.GetUdi();
114+
dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist));
115+
116+
// Update the url on the item to the udi aka umb://media/fileguid
117+
link["url"] = udi.ToString();
118+
}
119+
}
120+
return JsonConvert.SerializeObject(links);
121+
}
122+
123+
if (valueAsJToken is JObject)
124+
{
125+
// Single link, parse as JToken
126+
var link = JsonConvert.DeserializeObject<JToken>(svalue);
127+
if (link == null)
128+
return string.Empty;
129+
130+
var isMedia = link["isMedia"] != null;
131+
int intId;
132+
string url;
133+
GuidUdi guidUdi;
134+
// Only do processing if the Id is set on the element. OR if the url is set and its a media item
135+
if (TryParseJTokenAttr(link, "id", out intId))
136+
{
137+
// Checks weather we are resolving a media item or a document
138+
var objectTypeId = isMedia
139+
? UmbracoObjectTypes.Media
140+
: UmbracoObjectTypes.Document;
141+
var entityType = isMedia ? Constants.UdiEntityType.Media : Constants.UdiEntityType.Document;
142+
143+
var guidAttempt = _entityService.GetKey(intId, objectTypeId);
144+
if (guidAttempt.Success)
145+
{
146+
var udi = new GuidUdi(entityType, guidAttempt.Result);
147+
// Add the artifact dependency
148+
dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist));
149+
150+
// Set the Id attribute to the udi
151+
link["id"] = udi.ToString();
152+
}
153+
}
154+
else if (TryParseJTokenAttr(link, "udi", out guidUdi))
155+
{
156+
var entity = _entityService.Get(guidUdi.Guid, Constants.UdiEntityType.ToUmbracoObjectType(guidUdi.EntityType));
157+
if (entity != null)
158+
{
159+
// Add the artifact dependency
160+
dependencies.Add(new ArtifactDependency(guidUdi, false, ArtifactDependencyMode.Exist));
161+
}
162+
}
163+
else if (isMedia && TryParseJTokenAttr(link, "url", out url))
164+
{
165+
// This state can happen due to an issue in RJP.MultiUrlPicker(or our linkPicker in RTE which it relies on),
166+
// where you edit a media link, and just hits "Select".
167+
// That will set the id to null, but the url will still be filled. We try to get the media item, and if so add it as
168+
// a dependency to the package. If we can't find it, we abort(aka continue)
169+
var entry = _mediaService.GetMediaByPath(url);
170+
if (entry != null)
171+
{
172+
// Add the artifact dependency
173+
var udi = entry.GetUdi();
174+
dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist));
175+
176+
// Update the url on the item to the udi aka umb://media/fileguid
177+
link["url"] = udi.ToString();
178+
}
179+
}
180+
return JsonConvert.SerializeObject(link);
181+
}
182+
183+
//if none of the above...
184+
return string.Empty;
185+
}
186+
187+
public object FromArtifact(string value, PropertyType propertyType, object currentValue)
188+
{
189+
if (string.IsNullOrWhiteSpace(value))
190+
{
191+
return value;
192+
}
193+
194+
var valueAsJToken = JToken.Parse(value);
195+
196+
if (valueAsJToken is JArray)
197+
{
198+
//Multiple links, parse as JArray
199+
var links = JsonConvert.DeserializeObject<JArray>(value);
200+
if (links != null)
201+
{
202+
foreach (var link in links)
203+
{
204+
GuidUdi udi;
205+
string url;
206+
// Only do processing on an item if the Id or the url is set
207+
if (TryParseJTokenAttr(link, "id", out udi))
208+
{
209+
// Check the type of the link
210+
var nodeObjectType = link["isMedia"] != null
211+
? UmbracoObjectTypes.Media
212+
: UmbracoObjectTypes.Document;
213+
214+
// Get the Id corresponding to the Guid
215+
// it *should* succeed when deploying, due to dependencies management
216+
// nevertheless, assume it can fail, and then create an invalid localLink
217+
var idAttempt = _entityService.GetId(udi.Guid, nodeObjectType);
218+
if (idAttempt)
219+
link["id"] = idAttempt.Success ? idAttempt.Result : 0;
220+
}
221+
else if (TryParseJTokenAttr(link, "url", out url))
222+
{
223+
// Check whether the url attribute of the link contains a udi, if so, replace it with the
224+
// path to the file, i.e. the regex replaces <udi> with /path/to/file
225+
var newUrl = MediaUdiSrcRegex.Replace(url, match =>
226+
{
227+
var udiString = match.Groups["udi"].ToString();
228+
GuidUdi foundUdi;
229+
if (GuidUdi.TryParse(udiString, out foundUdi) && foundUdi.EntityType == Constants.UdiEntityType.Media)
230+
{
231+
// (take care of nulls)
232+
var media = _mediaService.GetById(foundUdi.Guid);
233+
if (media != null)
234+
return media.GetUrl("umbracoFile", _logger);
235+
}
236+
return string.Empty;
237+
});
238+
link["url"] = newUrl;
239+
}
240+
}
241+
value = JsonConvert.SerializeObject(links);
242+
}
243+
}
244+
else if (valueAsJToken is JObject)
245+
{
246+
//Single link, parse as JToken
247+
var link = JsonConvert.DeserializeObject<JToken>(value);
248+
249+
GuidUdi udi;
250+
string url;
251+
// Only do processing on an item if the Id or the url is set
252+
if (TryParseJTokenAttr(link, "id", out udi))
253+
{
254+
// Check the type of the link
255+
var nodeObjectType = link["isMedia"] != null
256+
? UmbracoObjectTypes.Media
257+
: UmbracoObjectTypes.Document;
258+
259+
// Get the Id corresponding to the Guid
260+
// it *should* succeed when deploying, due to dependencies management
261+
// nevertheless, assume it can fail, and then create an invalid localLink
262+
var idAttempt = _entityService.GetId(udi.Guid, nodeObjectType);
263+
if (idAttempt)
264+
link["id"] = idAttempt.Success ? idAttempt.Result : 0;
265+
}
266+
else if (TryParseJTokenAttr(link, "url", out url))
267+
{
268+
// Check whether the url attribute of the link contains a udi, if so, replace it with the
269+
// path to the file, i.e. the regex replaces <udi> with /path/to/file
270+
var newUrl = MediaUdiSrcRegex.Replace(url, match =>
271+
{
272+
var udiString = match.Groups["udi"].ToString();
273+
GuidUdi foundUdi;
274+
if (GuidUdi.TryParse(udiString, out foundUdi) &&
275+
foundUdi.EntityType == Constants.UdiEntityType.Media)
276+
{
277+
// (take care of nulls)
278+
var media = _mediaService.GetById(foundUdi.Guid);
279+
if (media != null)
280+
return media.GetUrl("umbracoFile", _logger);
281+
}
282+
283+
return string.Empty;
284+
});
285+
link["url"] = newUrl;
286+
}
287+
288+
value = JsonConvert.SerializeObject(link);
289+
}
290+
291+
return value;
292+
}
293+
294+
/// <inheritdoc/>
295+
public virtual IEnumerable<string> PropertyEditorAliases => new[] { "RJP.MultiUrlPicker", "Umbraco.MultiUrlPicker" };
296+
297+
private bool TryParseJTokenAttr(JToken link, string attrName, out int attrValue)
298+
{
299+
if (link[attrName] != null)
300+
{
301+
var val = link[attrName].ToString();
302+
return int.TryParse(val, out attrValue);
303+
}
304+
attrValue = 0;
305+
return false;
306+
}
307+
308+
private bool TryParseJTokenAttr(JToken link, string attrName, out GuidUdi attrValue)
309+
{
310+
if (link[attrName] != null)
311+
{
312+
var val = link[attrName].ToString();
313+
return GuidUdi.TryParse(val, out attrValue);
314+
}
315+
attrValue = null;
316+
return false;
317+
}
318+
319+
private bool TryParseJTokenAttr(JToken link, string attrName, out string strAttr)
320+
{
321+
if (link[attrName] != null)
322+
{
323+
var val = link[attrName].ToString();
324+
if (string.IsNullOrEmpty(val) == false)
325+
{
326+
strAttr = val;
327+
return true;
328+
}
329+
}
330+
strAttr = "";
331+
return false;
332+
}
333+
}
334+
}

0 commit comments

Comments
 (0)