mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Merge pull request #5568 from Codinlab/Tokens
Token chaining improvements
This commit is contained in:
@@ -7,7 +7,7 @@ namespace Orchard.DynamicForms.Tokens {
|
||||
|
||||
public void Describe(DescribeContext context) {
|
||||
context.For("FormSubmission", T("Dynamic Form submission"), T("Dynamic Form Submission tokens for use in workflows handling the Dynamic Form Submitted event."))
|
||||
.Token("Field:*", T("Field:<field name>"), T("The posted field value to access."))
|
||||
.Token("Field:*", T("Field:<field name>"), T("The posted field value to access."), "Text")
|
||||
.Token("IsValid:*", T("IsValid:<field name>"), T("The posted field validation status."))
|
||||
;
|
||||
}
|
||||
@@ -15,10 +15,20 @@ namespace Orchard.DynamicForms.Tokens {
|
||||
public void Evaluate(EvaluateContext context) {
|
||||
context.For<FormSubmissionTokenContext>("FormSubmission")
|
||||
.Token(token => token.StartsWith("Field:", StringComparison.OrdinalIgnoreCase) ? token.Substring("Field:".Length) : null, GetFieldValue)
|
||||
.Chain(FilterChainParam, "Text", GetFieldValue)
|
||||
.Token(token => token.StartsWith("IsValid:", StringComparison.OrdinalIgnoreCase) ? token.Substring("IsValid:".Length) : null, GetFieldValidationStatus);
|
||||
}
|
||||
|
||||
private object GetFieldValue(string fieldName, FormSubmissionTokenContext context) {
|
||||
private static Tuple<string, string> FilterChainParam(string token) {
|
||||
int tokenLength = "Field:".Length;
|
||||
int chainIndex = token.IndexOf('.');
|
||||
if (token.StartsWith("Field:", StringComparison.OrdinalIgnoreCase) && chainIndex > tokenLength)
|
||||
return new Tuple<string, string>(token.Substring(tokenLength, chainIndex - tokenLength), token.Substring(chainIndex + 1));
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetFieldValue(string fieldName, FormSubmissionTokenContext context) {
|
||||
return context.PostedValues[fieldName];
|
||||
}
|
||||
|
||||
|
@@ -31,21 +31,33 @@ namespace Orchard.Taxonomies.Tokens {
|
||||
|
||||
context.For<TaxonomyField>("TaxonomyField")
|
||||
.Token("Terms", field => String.Join(", ", field.Terms.Select(t => t.Name).ToArray()))
|
||||
.Token(
|
||||
token => {
|
||||
var index = 0;
|
||||
return (token.StartsWith("Terms:", StringComparison.OrdinalIgnoreCase) && int.TryParse(token.Substring("Terms:".Length), out index)) ? index.ToString() : null;
|
||||
},
|
||||
(token, t) => {
|
||||
var index = Convert.ToInt32(token);
|
||||
return index + 1 > t.Terms.Count() ? null : t.Terms.ElementAt(index).Name;
|
||||
.Token(FilterTokenParam,
|
||||
(index, field) => {
|
||||
var term = field.Terms.ElementAtOrDefault(Convert.ToInt32(index));
|
||||
return term != null ? term.Name : null;
|
||||
})
|
||||
// todo: extend Chain() in order to accept a filter like in Token() so that we can chain on an expression
|
||||
.Chain("Terms:0", "Content", t => t.Terms.ElementAt(0))
|
||||
.Chain("Terms:1", "Content", t => t.Terms.ElementAt(1))
|
||||
.Chain("Terms:2", "Content", t => t.Terms.ElementAt(2))
|
||||
.Chain("Terms:3", "Content", t => t.Terms.ElementAt(3))
|
||||
.Chain(FilterChainParam, "Content", (index, field) => field.Terms.ElementAtOrDefault(Convert.ToInt32(index)))
|
||||
;
|
||||
}
|
||||
|
||||
private static string FilterTokenParam(string token) {
|
||||
int index = 0;
|
||||
return (token.StartsWith("Terms:", StringComparison.OrdinalIgnoreCase) && int.TryParse(token.Substring("Terms:".Length), out index)) ? index.ToString() : null;
|
||||
}
|
||||
|
||||
private static Tuple<string, string> FilterChainParam(string token) {
|
||||
int tokenLength = "Terms:".Length;
|
||||
int index = 0;
|
||||
int chainIndex = token.IndexOf('.');
|
||||
if (!token.StartsWith("Terms:", StringComparison.OrdinalIgnoreCase) || chainIndex <= tokenLength)
|
||||
return null;
|
||||
|
||||
if (int.TryParse(token.Substring(tokenLength, chainIndex - tokenLength), out index)) {
|
||||
return new Tuple<string, string>(index.ToString(), token.Substring(chainIndex + 1));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,5 +7,6 @@ namespace Orchard.Tokens {
|
||||
public abstract EvaluateFor<TData> Chain(string token, string chainTarget, Func<TData, object> chainValue);
|
||||
public abstract EvaluateFor<TData> Token(Func<string, TData, object> tokenValue);
|
||||
public abstract EvaluateFor<TData> Token(Func<string, string> filter, Func<string, TData, object> tokenValue);
|
||||
public abstract EvaluateFor<TData> Chain(Func<string, Tuple<string, string>> filter, string chainTarget, Func<string, TData, object> chainValue);
|
||||
}
|
||||
}
|
@@ -153,6 +153,27 @@ namespace Orchard.Tokens.Implementation {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public override EvaluateFor<TData> Chain(Func<string, Tuple<string, string>> filter, string chainTarget, Func<string, TData, object> chainValue) {
|
||||
var subTokens = _context.Tokens
|
||||
.Where(kv => kv.Key.Contains('.'))
|
||||
.Select(kv => {
|
||||
var filterResult = filter(kv.Key);
|
||||
return filterResult != null ? new Tuple<string, string, string>(filterResult.Item1, filterResult.Item2, kv.Value) : null;
|
||||
})
|
||||
.Where(st => st != null)
|
||||
.ToList();
|
||||
if (!subTokens.Any()) {
|
||||
return this;
|
||||
}
|
||||
foreach(var chainGroup in subTokens.GroupBy(st => st.Item1)) {
|
||||
var subValues = _context._manager.Evaluate(chainTarget, chainGroup.ToDictionary(cg => cg.Item2, cg => cg.Item3), new Dictionary<string, object> { { chainTarget, chainValue(chainGroup.Key, _data) } });
|
||||
foreach (var subValue in subValues) {
|
||||
_context.Values[subValue.Key] = subValue.Value;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private class EvaluateForSilent<TData> : EvaluateFor<TData> {
|
||||
@@ -175,6 +196,10 @@ namespace Orchard.Tokens.Implementation {
|
||||
public override EvaluateFor<TData> Chain(string token, string chainTarget, Func<TData, object> chainValue) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public override EvaluateFor<TData> Chain(Func<string, Tuple<string, string>> filter, string chainTarget, Func<string, TData, object> chainValue) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -36,7 +36,7 @@ namespace Orchard.Tokens.Providers {
|
||||
.Token("DisplayUrl", T("Display Url"), T("Url to display the content."), "Url")
|
||||
.Token("EditUrl", T("Edit Url"), T("Url to edit the content."), "Url")
|
||||
.Token("Container", T("Container"), T("The container Content Item."), "Content")
|
||||
.Token("Body", T("Body"), T("The body text of the content item."), "Content")
|
||||
.Token("Body", T("Body"), T("The body text of the content item."), "Text")
|
||||
;
|
||||
|
||||
// Token descriptors for fields
|
||||
|
@@ -13,38 +13,42 @@ namespace Orchard.Tokens.Providers {
|
||||
public void Describe(DescribeContext context) {
|
||||
context.For("Text", T("Text"), T("Tokens for text strings"))
|
||||
.Token("Limit:*", T("Limit:<text length>[,<ellipsis>]"), T("Limit text to specified length and append an optional ellipsis text."))
|
||||
.Token("Format:*", T("Format:<text format>"), T("Optional format specifier (e.g. foo{0}bar). See format strings at <a target=\"_blank\" href=\"http://msdn.microsoft.com/en-us/library/az4se3k1.aspx\">Standard Formats</a> and <a target=\"_blank\" href=\"http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx\">Custom Formats</a>"), "DateTime")
|
||||
.Token("TrimEnd:*", T("TrimEnd:<chars|number>"), T("Trims the specified characters or number of them from the end of the string."), "Text")
|
||||
.Token("Format:*", T("Format:<text format>"), T("Optional format specifier (e.g. foo{0}bar)."))
|
||||
.Token("TrimEnd:*", T("TrimEnd:<chars|number>"), T("Trims the specified characters or number of them from the end of the string."))
|
||||
.Token("UrlEncode", T("Url Encode"), T("Encodes a URL string."), "Text")
|
||||
.Token("HtmlEncode", T("Html Encode"), T("Encodes an HTML string."), "Text")
|
||||
.Token("LineEncode", T("Line Encode"), T("Replaces new lines with <br /> tags."))
|
||||
.Token("LineEncode", T("Line Encode"), T("Replaces new lines with <br /> tags."), "Text")
|
||||
;
|
||||
}
|
||||
|
||||
public void Evaluate(EvaluateContext context) {
|
||||
context.For<String>("Text", () => "")
|
||||
.Token( // {Text}
|
||||
// {Text}
|
||||
.Token(
|
||||
token => token == String.Empty ? String.Empty : null,
|
||||
(token, d) => d.ToString())
|
||||
.Token( // {Text.Limit:<length>[,<ellipsis>]}
|
||||
token => {
|
||||
if (token.StartsWith("Limit:", StringComparison.OrdinalIgnoreCase)) {
|
||||
var param = token.Substring("Limit:".Length);
|
||||
return param;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// {Text.Limit:<length>[,<ellipsis>]}
|
||||
.Token(
|
||||
token => FilterTokenParam("Limit:", token),
|
||||
(token, t) => Limit(t, token))
|
||||
// {Text.Format:<formatstring>}
|
||||
.Token(
|
||||
token => token.StartsWith("Format:", StringComparison.OrdinalIgnoreCase) ? token.Substring("Format:".Length) : null,
|
||||
(token, d) => String.Format(d,token))
|
||||
.Token(token => token.StartsWith("TrimEnd:", StringComparison.OrdinalIgnoreCase) ? token.Substring("TrimEnd:".Length) : null, TrimEnd)
|
||||
token => FilterTokenParam("Format:", token),
|
||||
(token, d) => String.Format(d, token))
|
||||
// {Text.TrimEnd:<chars|number>}
|
||||
.Token(token => FilterTokenParam("TrimEnd:", token), TrimEnd)
|
||||
.Token("UrlEncode", HttpUtility.UrlEncode)
|
||||
.Chain("UrlEncode", "Text", HttpUtility.UrlEncode)
|
||||
.Token("HtmlEncode", HttpUtility.HtmlEncode)
|
||||
.Chain("HtmlEncode", "Text", HttpUtility.HtmlEncode)
|
||||
.Token("LineEncode", text => text.Replace(System.Environment.NewLine, "<br />"))
|
||||
.Chain("LineEncode", "Text", text => text.Replace(System.Environment.NewLine, "<br />"))
|
||||
;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static string FilterTokenParam(string tokenName, string token) {
|
||||
return token.StartsWith(tokenName, StringComparison.OrdinalIgnoreCase) ? token.Substring(tokenName.Length) : null;
|
||||
}
|
||||
|
||||
private static string TrimEnd(string token, string param) {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Security;
|
||||
@@ -24,6 +25,9 @@ namespace Orchard.Tokens.Tests {
|
||||
|
||||
context.For("Date")
|
||||
.Token("Now", T("Now"), T("Current system date in short date format. You can chain a .NET DateTime format string to customize."));
|
||||
|
||||
context.For("Users")
|
||||
.Token("Users[*:]", T("Users"), T("A user by its username"), "User");
|
||||
}
|
||||
|
||||
public void Evaluate(EvaluateContext context) {
|
||||
@@ -36,6 +40,7 @@ namespace Orchard.Tokens.Tests {
|
||||
|
||||
context.For<IUser>("User", () => new TestUser { UserName = "CurrentUser" })
|
||||
.Token("Name", u => u.UserName)
|
||||
.Token("Email", u => u.Email)
|
||||
.Token("Birthdate", u => "Nov 15")
|
||||
.Chain("Birthdate", "DateTime", u => new DateTime(1978, 11, 15));
|
||||
|
||||
@@ -45,6 +50,29 @@ namespace Orchard.Tokens.Tests {
|
||||
|
||||
context.For<DateTime>("DateTime")
|
||||
.Token((token, value) => value.ToString(token));
|
||||
|
||||
context.For<TestUser[]>("Users", () => new TestUser[] {
|
||||
new TestUser { UserName = "User1", Email = "user1@test.com" },
|
||||
new TestUser { UserName = "User2", Email = "user2@test.com" },
|
||||
new TestUser { UserName = "User3", Email = "user3@test.com" }
|
||||
})
|
||||
.Token(
|
||||
(token) => token.StartsWith("User:", StringComparison.OrdinalIgnoreCase) ? token.Substring("User:".Length) : null,
|
||||
(userName, users) => users.Where(u => u.UserName == userName).Select(u => u.UserName).FirstOrDefault()
|
||||
)
|
||||
.Chain(
|
||||
(token) => {
|
||||
int tokenLength = "User:".Length;
|
||||
int chainIndex = token.IndexOf('.');
|
||||
if (token.StartsWith("User:", StringComparison.OrdinalIgnoreCase) && chainIndex > tokenLength)
|
||||
return new Tuple<string, string>(token.Substring(tokenLength, chainIndex - tokenLength), token.Substring(chainIndex + 1));
|
||||
else
|
||||
return null;
|
||||
},
|
||||
"User",
|
||||
(userName, users) => users.Where(u => u.UserName == userName).FirstOrDefault()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -40,6 +40,18 @@ namespace Orchard.Tokens.Tests {
|
||||
Assert.That(_tokenizer.Replace("{Site.CurrentUser.Birthdate.yyyy}", null), Is.EqualTo("1978"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParameterizedTokens() {
|
||||
Assert.That(_tokenizer.Replace("{Users.User:User2}", null), Is.EqualTo("User2"));
|
||||
Assert.That(_tokenizer.Replace("{Users.User:FakeUser}", null), Is.EqualTo(""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParameterizedChainedTokens() {
|
||||
Assert.That(_tokenizer.Replace("{Users.User:User2.Email}", null), Is.EqualTo("user2@test.com"));
|
||||
Assert.That(_tokenizer.Replace("{Users.User:FakeUser.Email}", null), Is.EqualTo(""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissingTokens() {
|
||||
Assert.That(_tokenizer.Replace("[{Site.NotAToken}]", null), Is.EqualTo("[]"));
|
||||
|
Reference in New Issue
Block a user