Skip to content

Commit eb88156

Browse files
committed
Liquid template engine (work in progress)
1 parent 227fe4e commit eb88156

25 files changed

Lines changed: 904 additions & 129 deletions

File tree

src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -619,14 +619,14 @@ protected static IDictionary<string, FastProperty> GetProperties(
619619
// part of the sequence of properties returned by this method.
620620
type = Nullable.GetUnderlyingType(type) ?? type;
621621

622-
if (!cache.TryGetValue(type, out var fastProperties))
622+
return cache.GetOrAdd(type, Get);
623+
624+
IDictionary<string, FastProperty> Get(Type t)
623625
{
624-
var candidates = GetCandidateProperties(type);
625-
fastProperties = candidates.Select(p => createPropertyHelper(p)).ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
626-
cache.TryAdd(type, fastProperties);
626+
var candidates = GetCandidateProperties(t);
627+
var fastProperties = candidates.Select(p => createPropertyHelper(p)).ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
628+
return fastProperties;
627629
}
628-
629-
return fastProperties;
630630
}
631631

632632
internal static IEnumerable<PropertyInfo> GetCandidateProperties(Type type)

src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs

Lines changed: 97 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@
4040

4141
namespace SmartStore.ComponentModel
4242
{
43-
/// <summary>
43+
public enum MemberOptMethod
44+
{
45+
Allow,
46+
Disallow
47+
}
48+
49+
/// <summary>
4450
/// Class that provides extensible properties and methods to an
4551
/// existing object when cast to dynamic. This
4652
/// dynamic object stores 'extra' properties in a dictionary or
@@ -71,20 +77,29 @@ public class HybridExpando : DynamicObject, IDictionary<string, object>
7177
/// </summary>
7278
private Type _instanceType;
7379

74-
/// <summary>
75-
/// String Dictionary that contains the extra dynamic values
76-
/// stored on this object/instance
77-
/// </summary>
78-
/// <remarks>Using PropertyBag to support XML Serialization of the dictionary</remarks>
79-
public PropertyBag Properties = new PropertyBag();
80+
/// <summary>
81+
/// Adjusted property list for the wrapped instance type after white/black-list members has been applied.
82+
/// </summary>
83+
private IDictionary<string, FastProperty> _instanceProps;
84+
private readonly static IDictionary<string, FastProperty> EmptyProps = new Dictionary<string, FastProperty>();
8085

81-
/// <summary>
82-
/// This constructor just works off the internal dictionary and any
83-
/// public properties of this object.
84-
///
85-
/// Note you can subclass Expando.
86-
/// </summary>
87-
public HybridExpando()
86+
/// <summary>
87+
/// String Dictionary that contains the extra dynamic values
88+
/// stored on this object/instance
89+
/// </summary>
90+
/// <remarks>Using PropertyBag to support XML Serialization of the dictionary</remarks>
91+
public PropertyBag Properties = new PropertyBag();
92+
93+
private readonly HashSet<string> _optMembers;
94+
private readonly MemberOptMethod _optMethod;
95+
96+
/// <summary>
97+
/// This constructor just works off the internal dictionary and any
98+
/// public properties of this object.
99+
///
100+
/// Note you can subclass Expando.
101+
/// </summary>
102+
public HybridExpando()
88103
{
89104
Initialize(this);
90105
}
@@ -101,12 +116,32 @@ public HybridExpando(object instance)
101116
{
102117
Initialize(instance);
103118
}
104-
105-
protected void Initialize(object instance)
119+
120+
/// <summary>
121+
/// Allows passing in an existing instance variable to 'extend'
122+
/// along with a list of member names to allow or disallow.
123+
/// </summary>
124+
/// <param name="instance"></param>
125+
public HybridExpando(object instance, IEnumerable<string> optMembers, MemberOptMethod optMethod)
126+
{
127+
Initialize(instance);
128+
129+
_optMethod = optMethod;
130+
131+
if (optMembers is HashSet<string> h)
132+
{
133+
_optMembers = h;
134+
}
135+
else
136+
{
137+
_optMembers = new HashSet<string>(optMembers);
138+
}
139+
}
140+
141+
protected void Initialize(object instance)
106142
{
107143
_instance = instance;
108-
if (instance != null)
109-
_instanceType = instance.GetType();
144+
_instanceType = instance?.GetType();
110145
}
111146

112147
protected object WrappedObject
@@ -119,29 +154,26 @@ public override IEnumerable<string> GetDynamicMemberNames()
119154
foreach (var kvp in this.Properties.Keys)
120155
{
121156
yield return kvp;
122-
}
157+
}
123158

124-
if (_instance != null)
159+
foreach (var kvp in GetInstanceProperties())
125160
{
126-
foreach (var kvp in FastProperty.GetProperties(_instance))
161+
if (!this.Properties.ContainsKey(kvp.Key))
127162
{
128-
if (!this.Properties.ContainsKey(kvp.Key))
129-
{
130-
yield return kvp.Key;
131-
}
163+
yield return kvp.Key;
132164
}
133165
}
134166
}
135167

136168

137-
/// <summary>
138-
/// Try to retrieve a member by name first from instance properties
139-
/// followed by the collection entries.
140-
/// </summary>
141-
/// <param name="binder"></param>
142-
/// <param name="result"></param>
143-
/// <returns></returns>
144-
public override bool TryGetMember(GetMemberBinder binder, out object result)
169+
/// <summary>
170+
/// Try to retrieve a member by name first from instance properties
171+
/// followed by the collection entries.
172+
/// </summary>
173+
/// <param name="binder"></param>
174+
/// <param name="result"></param>
175+
/// <returns></returns>
176+
public override bool TryGetMember(GetMemberBinder binder, out object result)
145177
{
146178
return TryGetMemberCore(binder.Name, out result);
147179
}
@@ -259,8 +291,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
259291
/// <returns></returns>
260292
protected bool GetProperty(object instance, string name, out object result)
261293
{
262-
var fastProp = _instanceType != null ? FastProperty.GetProperty(_instanceType, name, PropertyCachingStrategy.EagerCached) : null;
263-
if (fastProp != null)
294+
if (GetInstanceProperties().TryGetValue(name, out var fastProp))
264295
{
265296
result = fastProp.GetValue(instance ?? this);
266297
return true;
@@ -279,12 +310,11 @@ protected bool GetProperty(object instance, string name, out object result)
279310
/// <returns></returns>
280311
protected bool SetProperty(object instance, string name, object value)
281312
{
282-
var fastProp = _instanceType != null ? FastProperty.GetProperty(_instanceType, name, PropertyCachingStrategy.EagerCached) : null;
283-
if (fastProp != null)
313+
if (GetInstanceProperties().TryGetValue(name, out var fastProp))
284314
{
285315
fastProp.SetValue(instance ?? this, value);
286316
return true;
287-
}
317+
}
288318

289319
return false;
290320
}
@@ -361,9 +391,9 @@ public IEnumerable<KeyValuePair<string, object>> GetProperties(bool includeInsta
361391
yield return kvp;
362392
}
363393

364-
if (includeInstanceProperties && _instance != null)
394+
if (includeInstanceProperties)
365395
{
366-
foreach (var prop in FastProperty.GetProperties(_instance).Values)
396+
foreach (var prop in GetInstanceProperties().Values)
367397
{
368398
if (!this.Properties.ContainsKey(prop.Name))
369399
{
@@ -373,6 +403,30 @@ public IEnumerable<KeyValuePair<string, object>> GetProperties(bool includeInsta
373403
}
374404
}
375405

406+
private IDictionary<string, FastProperty> GetInstanceProperties()
407+
{
408+
if (_instance == null)
409+
{
410+
return EmptyProps;
411+
}
412+
413+
if (_instanceProps == null)
414+
{
415+
var props = FastProperty.GetProperties(_instance) as IDictionary<string, FastProperty>;
416+
417+
if (_optMembers != null)
418+
{
419+
props = props
420+
.Where(x => _optMethod == MemberOptMethod.Allow ? _optMembers.Contains(x.Key) : !_optMembers.Contains(x.Key))
421+
.ToDictionary(x => x.Key, x => x.Value);
422+
}
423+
424+
_instanceProps = props;
425+
}
426+
427+
return _instanceProps;
428+
}
429+
376430
/// <summary>
377431
/// Checks whether a property exists in the Property collection
378432
/// or as a property on the instance
@@ -398,10 +452,10 @@ public bool Contains(string propertyName, bool includeInstanceProperties = false
398452
return true;
399453
}
400454

401-
if (includeInstanceProperties && _instance != null)
455+
if (includeInstanceProperties)
402456
{
403-
return FastProperty.GetProperties(_instance).ContainsKey(propertyName);
404-
}
457+
return GetInstanceProperties().ContainsKey(propertyName);
458+
}
405459

406460
return false;
407461
}

src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplate.cs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Web.Mvc;
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Web.Mvc;
23
using SmartStore.Core.Domain.Localization;
34
using SmartStore.Core.Domain.Media;
45
using SmartStore.Core.Domain.Stores;
@@ -15,15 +16,24 @@ public partial class MessageTemplate : BaseEntity, ILocalizedEntity, IStoreMappi
1516
/// </summary>
1617
public string Name { get; set; }
1718

18-
/// <summary>
19-
/// Gets or sets the BCC Email addresses
20-
/// </summary>
21-
public string BccEmailAddresses { get; set; }
19+
[StringLength(500), Required]
20+
public string To { get; set; }
2221

23-
/// <summary>
24-
/// Gets or sets the subject
25-
/// </summary>
26-
public string Subject { get; set; }
22+
[StringLength(500)]
23+
public string ReplyTo { get; set; }
24+
25+
[MaxLength]
26+
public string LastModelTree { get; set; }
27+
28+
/// <summary>
29+
/// Gets or sets the BCC Email addresses
30+
/// </summary>
31+
public string BccEmailAddresses { get; set; }
32+
33+
/// <summary>
34+
/// Gets or sets the subject
35+
/// </summary>
36+
public string Subject { get; set; }
2737

2838
/// <summary>
2939
/// Gets or sets the body

src/Libraries/SmartStore.Core/SmartStore.Core.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@
462462
<Compile Include="Search\SearchSort.cs" />
463463
<Compile Include="Security\SmartStorePrincipal.cs" />
464464
<Compile Include="Templating\DefaultTemplateManager.cs" />
465+
<Compile Include="Templating\Events.cs" />
465466
<Compile Include="Templating\ITemplate.cs" />
466467
<Compile Include="Templating\ITemplateEngine.cs" />
467468
<Compile Include="Templating\ITemplateManager.cs" />
@@ -474,6 +475,7 @@
474475
<Compile Include="Templating\Liquid\Drops\ObjectDrop.cs" />
475476
<Compile Include="Templating\Liquid\Drops\SafeDropBase.cs" />
476477
<Compile Include="Templating\Liquid\Drops\TestDrop.cs" />
478+
<Compile Include="Templating\Liquid\Tags\Zone.cs" />
477479
<Compile Include="Templating\Liquid\Tags\T.cs" />
478480
<Compile Include="Themes\DefaultThemeRegistry.cs" />
479481
<Compile Include="Themes\InheritedThemeFileResult.cs" />
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace SmartStore.Templating
5+
{
6+
/// <summary>
7+
/// Published when a template zone is about to be rendered.
8+
/// By subscribing to this event, implementors can inject custom
9+
/// content to specific template zones.
10+
/// </summary>
11+
public sealed class ZoneRenderingEvent
12+
{
13+
private IList<Snippet> _snippets;
14+
15+
public ZoneRenderingEvent(string zoneName, IDictionary<string, object> model)
16+
{
17+
ZoneName = zoneName;
18+
Model = model;
19+
}
20+
21+
/// <summary>
22+
/// The name of the zone which is rendered.
23+
/// </summary>
24+
public string ZoneName { get; private set; }
25+
26+
/// <summary>
27+
/// The template model
28+
/// </summary>
29+
public IDictionary<string, object> Model { get; private set; }
30+
31+
/// <summary>
32+
/// Specifies the custom content which the template engine should parse and inject.
33+
/// </summary>
34+
/// <param name="content">The content</param>
35+
public void InjectContent(string content)
36+
{
37+
if (content.HasValue())
38+
{
39+
AddSnippet(new Snippet { Content = content, Parse = true });
40+
}
41+
}
42+
43+
/// <summary>
44+
/// Specifies the custom content to inject.
45+
/// </summary>
46+
/// <param name="content">The content</param>
47+
/// <param name="parse">This should be <c>true</c> if the content contains template syntax.</param>
48+
public void InjectContent(string content, bool parse)
49+
{
50+
if (content.HasValue())
51+
{
52+
AddSnippet(new Snippet { Content = content, Parse = parse });
53+
}
54+
}
55+
56+
private void AddSnippet(Snippet snippet)
57+
{
58+
if (_snippets == null)
59+
_snippets = new List<Snippet>();
60+
61+
_snippets.Add(snippet);
62+
}
63+
64+
internal IList<Snippet> Snippets
65+
{
66+
get => _snippets;
67+
}
68+
69+
internal class Snippet
70+
{
71+
public string Content { get; set; }
72+
public bool Parse { get; set; }
73+
}
74+
}
75+
}

src/Libraries/SmartStore.Core/Templating/ITemplate.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.IO;
4-
using System.Linq;
5-
using System.Text;
6-
using System.Threading.Tasks;
72

83
namespace SmartStore.Templating
94
{

src/Libraries/SmartStore.Core/Templating/Liquid/Drops/SafeDropBase.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ internal interface ISafeObject
88
{
99
}
1010

11-
internal abstract class SafeDropBase : ILiquidizable, IIndexable, IContextAware, ISafeObject
11+
internal abstract class SafeDropBase : ILiquidizable, IIndexable, ISafeObject
1212
{
1313
private readonly IDictionary<string, object> _safeObjects = new Dictionary<string, object>();
1414

@@ -26,8 +26,6 @@ protected object GetOrCreateSafeObject(string name)
2626
return safeObject;
2727
}
2828

29-
public Context Context { get; set; }
30-
3129
public abstract bool ContainsKey(object key);
3230

3331
public object this[object key]

0 commit comments

Comments
 (0)