-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathContentExtensions.cs
More file actions
200 lines (180 loc) · 8.61 KB
/
ContentExtensions.cs
File metadata and controls
200 lines (180 loc) · 8.61 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
using Lombiq.HelpfulLibraries.OrchardCore.Contents;
using OrchardCore.Alias.Models;
using OrchardCore.ContentManagement.Records;
using System;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Settings;
using System.Threading.Tasks;
using YesSql;
namespace OrchardCore.ContentManagement;
public static class ContentExtensions
{
/// <summary>
/// Gets a content part by its type.
/// </summary>
/// <returns>The content part or <see langword="null"/> if it doesn't exist.</returns>
[Obsolete($"Use {nameof(GetOrCreate)} instead.")]
public static TPart? As<TPart>(this IContent content)
where TPart : ContentPart =>
content.ContentItem.As<TPart>();
/// <summary>
/// Gets a content part by its type or create a new one.
/// </summary>
/// <typeparam name="TPart">The type of the content part.</typeparam>
/// <returns>The content part instance or a new one if it doesn't exist.</returns>
public static TPart? GetMaybe<TPart>(this IContent? content)
where TPart : ContentPart, new() =>
content?.ContentItem?.TryGet<TPart>(out var part) == true ? part : null;
/// <summary>
/// Gets a content part by its type or create a new one.
/// </summary>
/// <typeparam name="TPart">The type of the content part.</typeparam>
/// <returns>The content part instance or a new one if it doesn't exist.</returns>
public static TPart GetOrCreate<TPart>(this IContent content)
where TPart : ContentPart, new() =>
content.ContentItem.GetOrCreate<TPart>();
/// <summary>
/// Adds a content part by its type.
/// </summary>
/// <typeparam name="TPart">The part to add to the <see cref="ContentItem"/>.</typeparam>
/// <returns>The current <see cref="IContent"/> instance.</returns>
public static IContent Weld<TPart>(this IContent content, TPart part)
where TPart : ContentPart =>
content.ContentItem.Weld(part);
/// <summary>
/// Updates the content part with the specified type.
/// </summary>
/// <typeparam name="TPart">The type of the part to update.</typeparam>
/// <returns>The current <see cref="IContent"/> instance.</returns>
public static IContent Apply<TPart>(this IContent content, TPart part)
where TPart : ContentPart =>
content.ContentItem.Apply(part);
/// <summary>
/// Modifies a new or existing content part by name.
/// </summary>
/// <param name="action">An action to apply on the content part.</param>
/// <returns>The current <see cref="IContent"/> instance.</returns>
public static IContent Alter<TPart>(this IContent content, Action<TPart> action)
where TPart : ContentPart, new() =>
content.ContentItem.Alter(action);
/// <summary>
/// Modifies a new or existing content part by name.
/// </summary>
/// <param name="action">An action to apply on the content part.</param>
/// <typeparam name="TPart">The type of the part to update.</typeparam>
/// <returns>The current <see cref="IContent"/> instance.</returns>
public static async Task<IContent> AlterAsync<TPart>(this IContent content, Func<TPart, Task> action)
where TPart : ContentPart, new() =>
await content.ContentItem.AlterAsync(action);
/// <summary>
/// Merges properties to the contents of a content item.
/// </summary>
/// <param name="properties">The object to merge.</param>
/// <param name="jsonMergeSettings">Settings for the merge.</param>
/// <returns>The modified <see cref="ContentItem"/> instance.</returns>
public static IContent Merge(this IContent content, object properties, JsonMergeSettings? jsonMergeSettings = null) =>
content.ContentItem.Merge(properties, jsonMergeSettings);
/// <summary>
/// Returns the <see cref="PublicationStatus"/> of the content item.
/// </summary>
/// <param name="content">The <see cref="IContent"/> whose <see cref="ContentItem"/> to check.</param>
/// <returns>The status of the <see cref="ContentItem"/>'s publication if any.</returns>
public static PublicationStatus GetPublicationStatus(this IContent content)
{
ArgumentNullException.ThrowIfNull(content);
if (content.ContentItem == null)
{
throw new ArgumentNullException($"{nameof(content)}.{nameof(content.ContentItem)} shouldn't be null.");
}
if (content.ContentItem.Published) return PublicationStatus.Published;
return content.ContentItem.Latest ? PublicationStatus.Draft : PublicationStatus.Deleted;
}
/// <summary>
/// Prevents multiple "latest" versions in case somehow two threads edited the same <see cref="ContentItem"/> at the
/// same time. For example this is possible if the update was done through XHR.
/// </summary>
/// <param name="content">The desired latest version of the content.</param>
/// <remarks>
/// <para>
/// If the <paramref name="content"/> is not <see cref="ContentItem.Latest"/> nothing will happen. This is to
/// prevent accidental deletion.
/// </para>
/// </remarks>
public static async Task SanitizeContentItemVersionsAsync(this IContent content, ISession session)
{
if (!content.ContentItem.Latest) return;
var contentItemId = content.ContentItem.ContentItemId;
var contentItemVersionId = content.ContentItem.ContentItemVersionId;
var stuckOtherDocuments = await session
.Query<ContentItem, ContentItemIndex>(index =>
index.Latest &&
index.ContentItemId == contentItemId &&
index.ContentItemVersionId != contentItemVersionId)
.ListAsync();
foreach (var toRemove in stuckOtherDocuments)
{
toRemove.Published = false;
toRemove.Latest = false;
await session.SaveAsync(toRemove);
}
}
/// <summary>
/// Returns the alias of the content item if the <see cref="AliasPart"/> is attached to it.
/// </summary>
/// <param name="content">Content item containing <see cref="AliasPart"/>.</param>
/// <returns>Alias of the content item.</returns>
public static string? GetAlias(this IContent content) => content.GetMaybe<AliasPart>()?.Alias;
/// <summary>
/// Provides the most essential data for a <see cref="ContentItem"/> enough to identify it in a text format. Can be
/// used as a human-readable text representing the <see cref="ContentItem"/> in a log.
/// </summary>
/// <returns>Technical text representing a Content Item.</returns>
public static string ToTechnicalString(this IContent content) =>
$"DisplayText: {content.ContentItem.DisplayText}, " +
$"ID: {content.ContentItem.ContentItemId}, " +
$"Version ID: {content.ContentItem.ContentItemVersionId}";
/// <summary>
/// Returns the most relevant date of the <paramref name="content"/>'s <see cref="ContentItem"/>.
/// </summary>
/// <returns>
/// <para>
/// The values are resolved in the following order if available. If all of them are <see langword="null"/> then <see
/// cref="DateTime.MinValue"/> is returned.
/// </para>
/// <list type="bullet">
/// <item>
/// <description><see cref="ContentItem.ModifiedUtc"/></description>
/// </item>
/// <item>
/// <description><see cref="ContentItem.PublishedUtc"/></description>
/// </item>
/// <item>
/// <description><see cref="ContentItem.CreatedUtc"/></description>
/// </item>
/// </list>
/// </returns>
public static DateTime GetDateTimeUtc(this IContent? content) =>
content?.ContentItem?.ModifiedUtc ??
content?.ContentItem?.PublishedUtc ??
content?.ContentItem?.CreatedUtc ??
DateTime.MinValue;
/// <summary>
/// Indicates whether the <see cref="ContentItem"/> is a newly instantiated one or an already existing one.
/// </summary>
/// <returns>Returns <see langword="true"/> if the item is new.</returns>
public static bool IsNew(this IContent content) =>
!content.ContentItem.Latest &&
!content.ContentItem.Published &&
content.ContentItem.Id == 0;
/// <summary>
/// Deserializes the <paramref name="contentElement"/>'s first JSON node that matches <paramref name="path"/>.
/// </summary>
public static T? GetProperty<T>(this ContentElement contentElement, string path)
where T : class
{
// Re-serializing ensures that the SelectNode will query from the current root.
var data = JObject.FromObject((JsonObject)contentElement.Content);
return data?.SelectNode(path)?.Deserialize<T>();
}
}