mirror of https://github.com/Squidex/squidex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
361 lines
11 KiB
361 lines
11 KiB
// ==========================================================================
|
|
// Squidex Headless CMS
|
|
// ==========================================================================
|
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
// All rights reserved. Licensed under the MIT license.
|
|
// =========================================-=================================
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Squidex.Domain.Apps.Core.Contents;
|
|
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
|
|
using Squidex.Domain.Apps.Core.Scripting;
|
|
using Squidex.Infrastructure;
|
|
using Squidex.Infrastructure.Json;
|
|
using Squidex.Infrastructure.Json.Objects;
|
|
using Squidex.Shared.Users;
|
|
|
|
namespace Squidex.Domain.Apps.Core.HandleRules
|
|
{
|
|
public class RuleEventFormatter
|
|
{
|
|
private const string Fallback = "null";
|
|
private const string ScriptSuffix = ")";
|
|
private const string ScriptPrefix = "Script(";
|
|
private static readonly Regex RegexPatternOld = new Regex(@"^(?<Type>[^_]*)_(?<Path>.*)", RegexOptions.Compiled);
|
|
private static readonly Regex RegexPatternNew = new Regex(@"^\{(?<Type>[^_]*)_(?<Path>.*)\}", RegexOptions.Compiled);
|
|
private readonly List<(char[] Pattern, Func<EnrichedEvent, string?> Replacer)> patterns = new List<(char[] Pattern, Func<EnrichedEvent, string?> Replacer)>();
|
|
private readonly IJsonSerializer jsonSerializer;
|
|
private readonly IUrlGenerator urlGenerator;
|
|
private readonly IScriptEngine scriptEngine;
|
|
|
|
public RuleEventFormatter(IJsonSerializer jsonSerializer, IUrlGenerator urlGenerator, IScriptEngine scriptEngine)
|
|
{
|
|
Guard.NotNull(jsonSerializer);
|
|
Guard.NotNull(scriptEngine);
|
|
Guard.NotNull(urlGenerator);
|
|
|
|
this.jsonSerializer = jsonSerializer;
|
|
this.scriptEngine = scriptEngine;
|
|
this.urlGenerator = urlGenerator;
|
|
|
|
AddPattern("APP_ID", AppId);
|
|
AddPattern("APP_NAME", AppName);
|
|
AddPattern("ASSET_CONTENT_URL", AssetContentUrl);
|
|
AddPattern("CONTENT_ACTION", ContentAction);
|
|
AddPattern("CONTENT_URL", ContentUrl);
|
|
AddPattern("MENTIONED_ID", MentionedId);
|
|
AddPattern("MENTIONED_NAME", MentionedName);
|
|
AddPattern("MENTIONED_EMAIL", MentionedEmail);
|
|
AddPattern("SCHEMA_ID", SchemaId);
|
|
AddPattern("SCHEMA_NAME", SchemaName);
|
|
AddPattern("TIMESTAMP_DATETIME", TimestampTime);
|
|
AddPattern("TIMESTAMP_DATE", TimestampDate);
|
|
AddPattern("USER_ID", UserId);
|
|
AddPattern("USER_NAME", UserName);
|
|
AddPattern("USER_EMAIL", UserEmail);
|
|
}
|
|
|
|
private void AddPattern(string placeholder, Func<EnrichedEvent, string?> generator)
|
|
{
|
|
patterns.Add((placeholder.ToCharArray(), generator));
|
|
}
|
|
|
|
public virtual string ToPayload<T>(T @event)
|
|
{
|
|
return jsonSerializer.Serialize(@event);
|
|
}
|
|
|
|
public virtual string ToEnvelope(EnrichedEvent @event)
|
|
{
|
|
return jsonSerializer.Serialize(new { type = @event.Name, payload = @event, timestamp = @event.Timestamp });
|
|
}
|
|
|
|
public string? Format(string text, EnrichedEvent @event)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
{
|
|
return text;
|
|
}
|
|
|
|
var trimmed = text.Trim();
|
|
|
|
if (trimmed.StartsWith(ScriptPrefix, StringComparison.OrdinalIgnoreCase) && trimmed.EndsWith(ScriptSuffix, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var script = trimmed.Substring(ScriptPrefix.Length, trimmed.Length - ScriptPrefix.Length - ScriptSuffix.Length);
|
|
|
|
var context = new ScriptContext
|
|
{
|
|
["event"] = @event
|
|
};
|
|
|
|
return scriptEngine.Interpolate(context, script);
|
|
}
|
|
|
|
var current = text.AsSpan();
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
for (var i = 0; i < current.Length; i++)
|
|
{
|
|
var c = current[i];
|
|
|
|
if (c == '$')
|
|
{
|
|
sb.Append(current.Slice(0, i).ToString());
|
|
|
|
current = current.Slice(i);
|
|
|
|
var (replacement, length) = GetReplacement(current.Slice(1), @event);
|
|
|
|
if (length > 0)
|
|
{
|
|
sb.Append(replacement);
|
|
|
|
current = current.Slice(length + 1);
|
|
i = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
sb.Append(current.ToString());
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private (string Result, int Length) GetReplacement(ReadOnlySpan<char> test, EnrichedEvent @event)
|
|
{
|
|
for (var j = 0; j < patterns.Count; j++)
|
|
{
|
|
var (pattern, replacer) = patterns[j];
|
|
|
|
if (test.StartsWith(pattern, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return (replacer(@event) ?? Fallback, pattern.Length);
|
|
}
|
|
}
|
|
|
|
var currentString = test.ToString();
|
|
|
|
var match = RegexPatternNew.Match(currentString);
|
|
|
|
if (!match.Success)
|
|
{
|
|
match = RegexPatternOld.Match(currentString);
|
|
}
|
|
|
|
if (match.Success)
|
|
{
|
|
var path = match.Groups["Path"].Value.Split('.', StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
return (CalculateData(@event, path) ?? Fallback, match.Length);
|
|
}
|
|
|
|
return (Fallback, 0);
|
|
}
|
|
|
|
private static string TimestampDate(EnrichedEvent @event)
|
|
{
|
|
return @event.Timestamp.ToDateTimeUtc().ToString("yyy-MM-dd", CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private static string TimestampTime(EnrichedEvent @event)
|
|
{
|
|
return @event.Timestamp.ToDateTimeUtc().ToString("yyy-MM-dd-hh-mm-ss", CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private static string AppId(EnrichedEvent @event)
|
|
{
|
|
return @event.AppId.Id.ToString();
|
|
}
|
|
|
|
private static string AppName(EnrichedEvent @event)
|
|
{
|
|
return @event.AppId.Name;
|
|
}
|
|
|
|
private static string? SchemaId(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedSchemaEventBase schemaEvent)
|
|
{
|
|
return schemaEvent.SchemaId.Id.ToString();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string? SchemaName(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedSchemaEventBase schemaEvent)
|
|
{
|
|
return schemaEvent.SchemaId.Name;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string? ContentAction(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedContentEvent contentEvent)
|
|
{
|
|
return contentEvent.Type.ToString();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string? ContentStatus(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedContentEvent contentEvent)
|
|
{
|
|
return contentEvent.Status.ToString();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private string? AssetContentUrl(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedAssetEvent assetEvent)
|
|
{
|
|
return urlGenerator.AssetContent(assetEvent.Id);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private string? ContentUrl(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedContentEvent contentEvent)
|
|
{
|
|
return urlGenerator.ContentUI(contentEvent.AppId, contentEvent.SchemaId, contentEvent.Id);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string? UserName(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedUserEventBase userEvent)
|
|
{
|
|
return userEvent.User?.DisplayName() ?? Fallback;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string? UserId(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedUserEventBase userEvent)
|
|
{
|
|
return userEvent.User?.Id ?? Fallback;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string UserEmail(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedUserEventBase userEvent)
|
|
{
|
|
return userEvent.User?.Email ?? Fallback;
|
|
}
|
|
|
|
return Fallback;
|
|
}
|
|
|
|
private static string MentionedName(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedCommentEvent commentEvent)
|
|
{
|
|
return commentEvent.MentionedUser.DisplayName() ?? Fallback;
|
|
}
|
|
|
|
return Fallback;
|
|
}
|
|
|
|
private static string MentionedId(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedCommentEvent commentEvent)
|
|
{
|
|
return commentEvent.MentionedUser.Id ?? Fallback;
|
|
}
|
|
|
|
return Fallback;
|
|
}
|
|
|
|
private static string MentionedEmail(EnrichedEvent @event)
|
|
{
|
|
if (@event is EnrichedCommentEvent commentEvent)
|
|
{
|
|
return commentEvent.MentionedUser.Email ?? Fallback;
|
|
}
|
|
|
|
return Fallback;
|
|
}
|
|
|
|
private static string? CalculateData(object @event, string[] path)
|
|
{
|
|
object? current = @event;
|
|
|
|
foreach (var segment in path)
|
|
{
|
|
if (current is NamedContentData data)
|
|
{
|
|
if (!data.TryGetValue(segment, out var temp) || temp == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
current = temp;
|
|
}
|
|
else if (current is ContentFieldData field)
|
|
{
|
|
if (!field.TryGetValue(segment, out var temp) || temp == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
current = temp;
|
|
}
|
|
else if (current is IJsonValue json)
|
|
{
|
|
if (!json.TryGet(segment, out var temp) || temp == null || temp.Type == JsonValueType.Null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
current = temp;
|
|
}
|
|
else if (current != null)
|
|
{
|
|
const BindingFlags bindingFlags =
|
|
BindingFlags.FlattenHierarchy |
|
|
BindingFlags.Public |
|
|
BindingFlags.Instance;
|
|
|
|
var properties = current.GetType().GetProperties(bindingFlags);
|
|
var property = properties.FirstOrDefault(x => x.CanRead && string.Equals(x.Name, segment, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (property == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
current = property.GetValue(current);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return current?.ToString();
|
|
}
|
|
}
|
|
}
|
|
|