forked from Sitecore/ASP.NET-Core-SDK
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSitecoreFieldExtensions.cs
More file actions
266 lines (231 loc) · 9.42 KB
/
Copy pathSitecoreFieldExtensions.cs
File metadata and controls
266 lines (231 loc) · 9.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.Web;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.WebUtilities;
using Sitecore.AspNetCore.SDK.LayoutService.Client.Response.Model.Fields;
namespace Sitecore.AspNetCore.SDK.RenderingEngine.Extensions;
/// <summary>
/// Set of extension methods for Sitecore fields.
/// </summary>
public static partial class SitecoreFieldExtensions
{
/// <summary>
/// Gets modified URL string to Sitecore media item.
/// </summary>
/// <param name="imageField">The image field.</param>
/// <param name="imageParams">Image parameters, example: new { mw = 100, mh = 50 }. **IMPORTANT**: All the parameters you pass must be whitelisted for resizing to occur. See /sitecore/config/*.config (search for 'allowedMediaParams').</param>
/// <returns>Media item URL.</returns>
public static string? GetMediaLink(this ImageField imageField, object? imageParams)
{
ArgumentNullException.ThrowIfNull(imageField);
string? urlStr = imageField.Value.Src;
string? result = null;
if (urlStr != null)
{
result = GetSitecoreMediaUri(urlStr, imageParams);
}
return result;
}
/// <summary>
/// Gets modified URL string to Sitecore media item for srcSet.
/// This method preserves existing URL parameters and merges them with new ones.
/// </summary>
/// <param name="imageField">The image field.</param>
/// <param name="imageParams">Base image parameters.</param>
/// <param name="srcSetParams">SrcSet specific parameters that override imageParams.</param>
/// <returns>Media item URL.</returns>
public static string? GetMediaLinkForSrcSet(this ImageField imageField, object? imageParams, object? srcSetParams)
{
ArgumentNullException.ThrowIfNull(imageField);
string? urlStr = imageField.Value.Src;
string? result = null;
if (urlStr != null)
{
Dictionary<string, object?> mergedParams = MergeParameters(imageParams, srcSetParams);
result = GetSitecoreMediaUriWithPreservation(urlStr, mergedParams);
}
return result;
}
/// <summary>
/// Merges base parameters with override parameters.
/// </summary>
/// <param name="imageParams">Base image parameters.</param>
/// <param name="srcSetParams">SrcSet specific parameters that take precedence.</param>
/// <returns>Merged parameters as dictionary.</returns>
private static Dictionary<string, object?> MergeParameters(object? imageParams, object? srcSetParams)
{
Dictionary<string, object?> result = new(StringComparer.OrdinalIgnoreCase);
// Add base parameters first
AddParametersToResult(result, imageParams);
// Override with srcSet parameters
AddParametersToResult(result, srcSetParams);
return result;
}
/// <summary>
/// Adds parameters from an object to the result dictionary.
/// </summary>
/// <param name="result">The result dictionary to add parameters to.</param>
/// <param name="parameters">The parameters object (can be Dictionary or any object with properties).</param>
/// <param name="skipNullValues">Whether to skip null values when adding parameters.</param>
private static void AddParametersToResult(Dictionary<string, object?> result, object? parameters, bool skipNullValues = false)
{
switch (parameters)
{
case null:
break;
case Dictionary<string, object?> paramDict:
foreach (KeyValuePair<string, object?> kvp in paramDict.Where(kvp => !skipNullValues || kvp.Value != null))
{
result[kvp.Key] = kvp.Value;
}
break;
default:
RouteValueDictionary routeValues = new(parameters);
foreach (KeyValuePair<string, object?> kvp in routeValues.Where(kvp => !skipNullValues || kvp.Value != null))
{
result[kvp.Key] = kvp.Value;
}
break;
}
}
/// <summary>
/// Gets URL to Sitecore media item.
/// </summary>
/// <param name="url">The image URL.</param>
/// <param name="imageParams">Image parameters.</param>
/// <returns>Media item URL.</returns>
private static string GetSitecoreMediaUri(string url, object? imageParams)
{
// TODO What's the reason we strip away existing querystring?
if (imageParams != null)
{
string[] urlParts = url.Split('?');
if (urlParts.Length > 1)
{
url = urlParts[0];
}
RouteValueDictionary parameters = new(imageParams);
foreach (string key in parameters.Keys)
{
url = QueryHelpers.AddQueryString(url, key, parameters[key]?.ToString() ?? string.Empty);
}
}
return ApplyJssMediaUrlPrefix(url);
}
/// <summary>
/// Gets modified URL string to Sitecore media item with parameter preservation.
/// This method preserves existing URL parameters and merges them with new ones.
/// </summary>
/// <param name="urlStr">The URL string.</param>
/// <param name="parameters">Parameters to merge.</param>
/// <returns>Modified URL string.</returns>
private static string? GetSitecoreMediaUriWithPreservation(string? urlStr, object? parameters)
{
string? url;
if (string.IsNullOrEmpty(urlStr))
{
url = urlStr;
}
else
{
// Parse existing query parameters and build merged parameters dictionary
Dictionary<string, object?> mergedParams = new(StringComparer.OrdinalIgnoreCase);
if (!Uri.TryCreate(urlStr, UriKind.RelativeOrAbsolute, out Uri? uri))
{
url = urlStr;
}
else
{
url = ParseUrlParams(uri, mergedParams);
// Add new parameters (these will override existing ones)
AddParametersToResult(mergedParams, parameters, skipNullValues: true);
// Add query parameters
foreach (KeyValuePair<string, object?> kvp in mergedParams)
{
if (kvp.Value != null)
{
url = QueryHelpers.AddQueryString(url, kvp.Key, kvp.Value.ToString() ?? string.Empty);
}
}
}
}
string? result = url == null ? null : ApplyJssMediaUrlPrefix(url);
return result;
}
/// <summary>
/// Parses URL query string parameters and adds them to the provided dictionary.
/// </summary>
/// <param name="uri">The Uri with potential query parameters.</param>
/// <param name="parameters">The dictionary to add parsed parameters to.</param>
/// <returns>The URL without query parameters.</returns>
private static string ParseUrlParams(Uri? uri, Dictionary<string, object?> parameters)
{
string result;
if (uri == null)
{
result = string.Empty;
}
else if (uri.IsAbsoluteUri)
{
result = ParseAbsoluteUriParams(uri, parameters);
}
else
{
result = ParseRelativeUriParams(uri, parameters);
}
return result;
}
private static string ParseAbsoluteUriParams(Uri uri, Dictionary<string, object?> parameters)
{
string url = $"{uri.Scheme}://{uri.Host}{uri.AbsolutePath}";
NameValueCollection queryParams = HttpUtility.ParseQueryString(uri.Query);
foreach (string? param in queryParams.AllKeys)
{
if (!string.IsNullOrEmpty(param))
{
parameters[param] = queryParams[param];
}
}
return url;
}
private static string ParseRelativeUriParams(Uri uri, Dictionary<string, object?> parameters)
{
// For relative URIs, accessing Uri.Query throws InvalidOperationException, so we use string manipulation
string original = uri.OriginalString;
int queryIndex = original.IndexOf('?');
string result;
if (queryIndex >= 0)
{
string query = original[queryIndex..];
Dictionary<string, Microsoft.Extensions.Primitives.StringValues> parsedQuery = QueryHelpers.ParseQuery(query);
foreach (KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues> kvp in parsedQuery)
{
parameters[kvp.Key] = kvp.Value.Count > 0 ? kvp.Value[0] : null;
}
result = original[..queryIndex];
}
else
{
result = original;
}
return result;
}
/// <summary>
/// Applies JSS media URL prefix replacement to the given URL.
/// </summary>
/// <param name="url">The URL to transform.</param>
/// <returns>The URL with JSS media prefix applied if applicable.</returns>
private static string ApplyJssMediaUrlPrefix(string url)
{
// TODO Review hardcoded matching and replacement
Match match = MediaUrlPrefixRegex().Match(url);
if (match.Success)
{
url = url.Replace(match.Value, $"/{match.Groups[1]}/jssmedia/", StringComparison.InvariantCulture);
}
return url;
}
[GeneratedRegex("/([-~]{1})/media/")]
private static partial Regex MediaUrlPrefixRegex();
}