diff --git a/src/Umbraco.Core/Models/IMacro.cs b/src/Umbraco.Core/Models/IMacro.cs
new file mode 100644
index 0000000000..586b4aae82
--- /dev/null
+++ b/src/Umbraco.Core/Models/IMacro.cs
@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Defines a Macro
+ ///
+ internal interface IMacro : IAggregateRoot
+ {
+ ///
+ /// Gets or sets the alias of the Macro
+ ///
+ [DataMember]
+ string Alias { get; set; }
+
+ ///
+ /// Gets or sets the name of the Macro
+ ///
+ [DataMember]
+ string Name { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether the Macro can be used in an Editor
+ ///
+ [DataMember]
+ bool UseInEditor { get; set; }
+
+ ///
+ /// Gets or sets the Cache Duration for the Macro
+ ///
+ [DataMember]
+ int CacheDuration { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether the Macro should be Cached by Page
+ ///
+ [DataMember]
+ bool CacheByPage { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether the Macro should be Cached Personally
+ ///
+ [DataMember]
+ bool CacheByMember { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor
+ ///
+ [DataMember]
+ bool DontRender { get; set; }
+
+ ///
+ /// Gets or sets the path to user control or the Control Type to render
+ ///
+ [DataMember]
+ string ControlType { get; set; }
+
+ ///
+ /// Gets or sets the name of the assembly, which should be used by the Macro
+ ///
+ /// Will usually only be filled if the ScriptFile is a Usercontrol
+ [DataMember]
+ string ControlAssembly { get; set; }
+
+ ///
+ /// Gets or set the path to the Python file in use
+ ///
+ /// Optional: Can only be one of three Script, Python or Xslt
+ [DataMember]
+ string ScriptPath { get; set; }
+
+ ///
+ /// Gets or sets the path to the Xslt file in use
+ ///
+ /// Optional: Can only be one of three Script, Python or Xslt
+ [DataMember]
+ string XsltPath { get; set; }
+
+ ///
+ /// Gets or sets a list of Macro Properties
+ ///
+ [DataMember]
+ MacroPropertyCollection Properties { get; }
+
+ ///
+ /// Returns an enum based on the properties on the Macro
+ ///
+ ///
+ MacroTypes MacroType();
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/IMacroProperty.cs b/src/Umbraco.Core/Models/IMacroProperty.cs
new file mode 100644
index 0000000000..13e3183c06
--- /dev/null
+++ b/src/Umbraco.Core/Models/IMacroProperty.cs
@@ -0,0 +1,39 @@
+using System.Runtime.Serialization;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Defines a Property for a Macro
+ ///
+ internal interface IMacroProperty : IValueObject
+ {
+ ///
+ /// Gets or sets the Alias of the Property
+ ///
+ [DataMember]
+ string Alias { get; set; }
+
+ ///
+ /// Gets or sets the Name of the Property
+ ///
+ [DataMember]
+ string Name { get; set; }
+
+ ///
+ /// Gets or sets the Sort Order of the Property
+ ///
+ [DataMember]
+ int SortOrder { get; set; }
+
+ ///
+ /// Gets or sets the Type for this Property
+ ///
+ ///
+ /// The MacroPropertyTypes acts as a plugin for Macros.
+ /// All types was previously contained in the database, but has been ported to code.
+ ///
+ [DataMember]
+ IMacroPropertyType PropertyType { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/IMacroPropertyType.cs b/src/Umbraco.Core/Models/IMacroPropertyType.cs
new file mode 100644
index 0000000000..55002a3d82
--- /dev/null
+++ b/src/Umbraco.Core/Models/IMacroPropertyType.cs
@@ -0,0 +1,28 @@
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Defines a PropertyType (plugin) for a Macro
+ ///
+ internal interface IMacroPropertyType
+ {
+ ///
+ /// Gets the unique Alias of the Property Type
+ ///
+ string Alias { get; }
+
+ ///
+ /// Gets the name of the Assembly used to render the Property Type
+ ///
+ string RenderingAssembly { get; }
+
+ ///
+ /// Gets the name of the Type used to render the Property Type
+ ///
+ string RenderingType { get; }
+
+ ///
+ /// Gets the Base Type for storing the PropertyType (Int32, String, Boolean)
+ ///
+ MacroPropertyTypeBaseTypes BaseType { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs
new file mode 100644
index 0000000000..fbe49e65b4
--- /dev/null
+++ b/src/Umbraco.Core/Models/Macro.cs
@@ -0,0 +1,400 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Text.RegularExpressions;
+using Umbraco.Core.IO;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Represents a Macro
+ ///
+ [Serializable]
+ [DataContract(IsReference = true)]
+ internal class Macro : Entity, IMacro
+ {
+ public Macro()
+ {
+ _properties = new MacroPropertyCollection();
+ _properties.CollectionChanged += PropertiesChanged;
+ _addedProperties = new List();
+ _removedProperties = new List();
+ }
+
+ ///
+ /// Creates an item with pre-filled properties
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Macro(int id, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath)
+ : this()
+ {
+ Id = id;
+ UseInEditor = useInEditor;
+ CacheDuration = cacheDuration;
+ Alias = alias;
+ Name = name;
+ ControlType = controlType;
+ ControlAssembly = controlAssembly;
+ XsltPath = xsltPath;
+ CacheByPage = cacheByPage;
+ CacheByMember = cacheByMember;
+ DontRender = dontRender;
+ ScriptPath = scriptPath;
+ }
+
+ ///
+ /// Creates an instance for persisting a new item
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Macro(string @alias, string name,
+ string controlType = "",
+ string controlAssembly = "",
+ string xsltPath = "",
+ string scriptPath = "",
+ bool cacheByPage = false,
+ bool cacheByMember = false,
+ bool dontRender = true,
+ bool useInEditor = false,
+ int cacheDuration = 0)
+ : this()
+ {
+ UseInEditor = useInEditor;
+ CacheDuration = cacheDuration;
+ Alias = alias;
+ Name = name;
+ ControlType = controlType;
+ ControlAssembly = controlAssembly;
+ XsltPath = xsltPath;
+ CacheByPage = cacheByPage;
+ CacheByMember = cacheByMember;
+ DontRender = dontRender;
+ ScriptPath = scriptPath;
+ }
+
+ private string _alias;
+ private string _name;
+ private bool _useInEditor;
+ private int _cacheDuration;
+ private bool _cacheByPage;
+ private bool _cacheByMember;
+ private bool _dontRender;
+ private string _scriptFile;
+ private string _scriptAssembly;
+ private string _python;
+ private string _xslt;
+ private readonly MacroPropertyCollection _properties;
+ private readonly List _addedProperties;
+ private readonly List _removedProperties;
+
+ private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias);
+ private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name);
+ private static readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo(x => x.UseInEditor);
+ private static readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheDuration);
+ private static readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByPage);
+ private static readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByMember);
+ private static readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo(x => x.DontRender);
+ private static readonly PropertyInfo ControlPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ControlType);
+ private static readonly PropertyInfo ControlAssemblySelector = ExpressionHelper.GetPropertyInfo(x => x.ControlAssembly);
+ private static readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ScriptPath);
+ private static readonly PropertyInfo XsltPathSelector = ExpressionHelper.GetPropertyInfo(x => x.XsltPath);
+ private static readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties);
+
+ protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ OnPropertyChanged(PropertiesSelector);
+
+ if (e.Action == NotifyCollectionChangedAction.Add)
+ {
+ var alias = e.NewItems.Cast().Select(x => x.Alias).First();
+
+ //remove from the removed/added props (since people could add/remove all they want in one request)
+ _removedProperties.RemoveAll(s => s == alias);
+ _addedProperties.RemoveAll(s => s == alias);
+
+ //add to the added props
+ _addedProperties.Add(alias);
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Remove)
+ {
+ var alias = e.OldItems.Cast().Select(x => x.Alias).First();
+
+ //remove from the removed/added props (since people could add/remove all they want in one request)
+ _removedProperties.RemoveAll(s => s == alias);
+ _addedProperties.RemoveAll(s => s == alias);
+
+ //add to the added props
+ _removedProperties.Add(alias);
+ }
+ }
+
+ ///
+ /// Used internally to check if we need to add a section in the repository to the db
+ ///
+ internal IEnumerable AddedProperties
+ {
+ get { return _addedProperties; }
+ }
+
+ ///
+ /// Used internally to check if we need to remove a section in the repository to the db
+ ///
+ internal IEnumerable RemovedProperties
+ {
+ get { return _removedProperties; }
+ }
+
+ ///
+ /// Gets or sets the alias of the Macro
+ ///
+ [DataMember]
+ public string Alias
+ {
+ get { return _alias; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _alias = value;
+ return _alias;
+ }, _alias, AliasSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets the name of the Macro
+ ///
+ [DataMember]
+ public string Name
+ {
+ get { return _name; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _name = value;
+ return _name;
+ }, _name, NameSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets a boolean indicating whether the Macro can be used in an Editor
+ ///
+ [DataMember]
+ public bool UseInEditor
+ {
+ get { return _useInEditor; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _useInEditor = value;
+ return _useInEditor;
+ }, _useInEditor, UseInEditorSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets the Cache Duration for the Macro
+ ///
+ [DataMember]
+ public int CacheDuration
+ {
+ get { return _cacheDuration; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _cacheDuration = value;
+ return _cacheDuration;
+ }, _cacheDuration, CacheDurationSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets a boolean indicating whether the Macro should be Cached by Page
+ ///
+ [DataMember]
+ public bool CacheByPage
+ {
+ get { return _cacheByPage; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _cacheByPage = value;
+ return _cacheByPage;
+ }, _cacheByPage, CacheByPageSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets a boolean indicating whether the Macro should be Cached Personally
+ ///
+ [DataMember]
+ public bool CacheByMember
+ {
+ get { return _cacheByMember; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _cacheByMember = value;
+ return _cacheByMember;
+ }, _cacheByMember, CacheByMemberSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor
+ ///
+ [DataMember]
+ public bool DontRender
+ {
+ get { return _dontRender; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _dontRender = value;
+ return _dontRender;
+ }, _dontRender, DontRenderSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets the path to user control or the Control Type to render
+ ///
+ [DataMember]
+ public string ControlType
+ {
+ get { return _scriptFile; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _scriptFile = value;
+ return _scriptFile;
+ }, _scriptFile, ControlPathSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets the name of the assembly, which should be used by the Macro
+ ///
+ /// Will usually only be filled if the ControlType is a Usercontrol
+ [DataMember]
+ public string ControlAssembly
+ {
+ get { return _scriptAssembly; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _scriptAssembly = value;
+ return _scriptAssembly;
+ }, _scriptAssembly, ControlAssemblySelector);
+ }
+ }
+
+ ///
+ /// Gets or set the path to the Python file in use
+ ///
+ /// Optional: Can only be one of three Script, Python or Xslt
+ [DataMember]
+ public string ScriptPath
+ {
+ get { return _python; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _python = value;
+ return _python;
+ }, _python, ScriptPathSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets the path to the Xslt file in use
+ ///
+ /// Optional: Can only be one of three Script, Python or Xslt
+ [DataMember]
+ public string XsltPath
+ {
+ get { return _xslt; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _xslt = value;
+ return _xslt;
+ }, _xslt, XsltPathSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets a list of Macro Properties
+ ///
+ [DataMember]
+ public MacroPropertyCollection Properties
+ {
+ get { return _properties; }
+ }
+
+ ///
+ /// Returns an enum based on the properties on the Macro
+ ///
+ ///
+ public MacroTypes MacroType()
+ {
+ if (string.IsNullOrEmpty(XsltPath) == false)
+ return MacroTypes.Xslt;
+
+ if (string.IsNullOrEmpty(ScriptPath) == false)
+ {
+ //we need to check if the file path saved is a virtual path starting with ~/Views/MacroPartials, if so then this is
+ //a partial view macro, not a script macro
+ //we also check if the file exists in ~/App_Plugins/[Packagename]/Views/MacroPartials, if so then it is also a partial view.
+ return (ScriptPath.InvariantStartsWith(SystemDirectories.MvcViews + "/MacroPartials/")
+ || (Regex.IsMatch(ScriptPath, "~/App_Plugins/.+?/Views/MacroPartials", RegexOptions.Compiled | RegexOptions.IgnoreCase)))
+ ? MacroTypes.PartialView
+ : MacroTypes.Script;
+ }
+
+ if (string.IsNullOrEmpty(ControlType) == false && ControlType.InvariantContains(".ascx"))
+ return MacroTypes.UserControl;
+
+ if (string.IsNullOrEmpty(ControlType) == false && string.IsNullOrEmpty(ControlAssembly) == false)
+ return MacroTypes.CustomControl;
+
+ return MacroTypes.Unknown;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/MacroProperty.cs b/src/Umbraco.Core/Models/MacroProperty.cs
new file mode 100644
index 0000000000..199c1e7fe6
--- /dev/null
+++ b/src/Umbraco.Core/Models/MacroProperty.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Reflection;
+using System.Runtime.Serialization;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Represents a Macro Property
+ ///
+ [Serializable]
+ [DataContract(IsReference = true)]
+ internal class MacroProperty : TracksChangesEntityBase, IMacroProperty, IRememberBeingDirty
+ {
+ private string _alias;
+ private string _name;
+ private int _sortOrder;
+ private IMacroPropertyType _propertyType;
+
+ private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias);
+ private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name);
+ private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder);
+ private static readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyType);
+
+ ///
+ /// Gets or sets the Alias of the Property
+ ///
+ [DataMember]
+ public string Alias
+ {
+ get { return _alias; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _alias = value;
+ return _alias;
+ }, _alias, AliasSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets the Name of the Property
+ ///
+ [DataMember]
+ public string Name
+ {
+ get { return _name; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _name = value;
+ return _name;
+ }, _name, NameSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets the Sort Order of the Property
+ ///
+ [DataMember]
+ public int SortOrder
+ {
+ get { return _sortOrder; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _sortOrder = value;
+ return _sortOrder;
+ }, _sortOrder, SortOrderSelector);
+ }
+ }
+
+ ///
+ /// Gets or sets the Type for this Property
+ ///
+ ///
+ /// The MacroPropertyTypes acts as a plugin for Macros.
+ /// All types was previously contained in the database, but has been ported to code.
+ ///
+ [DataMember]
+ public IMacroPropertyType PropertyType
+
+ {
+ get { return _propertyType; }
+ set
+ {
+ SetPropertyValueAndDetectChanges(o =>
+ {
+ _propertyType = value;
+ return _propertyType;
+ }, _propertyType, PropertyTypeSelector);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/MacroPropertyCollection.cs b/src/Umbraco.Core/Models/MacroPropertyCollection.cs
new file mode 100644
index 0000000000..e9614ba72b
--- /dev/null
+++ b/src/Umbraco.Core/Models/MacroPropertyCollection.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// A macro's property collection
+ ///
+ internal class MacroPropertyCollection : ObservableDictionary
+ {
+ public MacroPropertyCollection()
+ : base(property => property.Alias)
+ {
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/MacroPropertyTypeBaseTypes.cs b/src/Umbraco.Core/Models/MacroPropertyTypeBaseTypes.cs
new file mode 100644
index 0000000000..a89946b9a4
--- /dev/null
+++ b/src/Umbraco.Core/Models/MacroPropertyTypeBaseTypes.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Enum for the three allowed BaseTypes
+ ///
+ [Serializable]
+ [DataContract(IsReference = true)]
+ internal enum MacroPropertyTypeBaseTypes
+ {
+ [EnumMember]
+ Int32,
+ [EnumMember]
+ Boolean,
+ [EnumMember]
+ String
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/MacroTypes.cs b/src/Umbraco.Core/Models/MacroTypes.cs
new file mode 100644
index 0000000000..14f67cccfb
--- /dev/null
+++ b/src/Umbraco.Core/Models/MacroTypes.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Enum for the various types of Macros
+ ///
+ [Serializable]
+ [DataContract(IsReference = true)]
+ internal enum MacroTypes
+ {
+ [EnumMember]
+ Xslt = 1,
+ [EnumMember]
+ CustomControl = 2,
+ [EnumMember]
+ UserControl = 3,
+ [EnumMember]
+ Unknown = 4,
+ [EnumMember]
+ Python = 5,
+ [EnumMember]
+ Script = 6,
+ [EnumMember]
+ PartialView = 7
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Rdbms/MacroDto.cs b/src/Umbraco.Core/Models/Rdbms/MacroDto.cs
index c3c0c1d525..6409f46294 100644
--- a/src/Umbraco.Core/Models/Rdbms/MacroDto.cs
+++ b/src/Umbraco.Core/Models/Rdbms/MacroDto.cs
@@ -1,4 +1,5 @@
-using Umbraco.Core.Persistence;
+using System.Collections.Generic;
+using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
@@ -54,5 +55,8 @@ namespace Umbraco.Core.Models.Rdbms
[Column("macroPython")]
[NullSetting(NullSetting = NullSettings.Null)]
public string Python { get; set; }
+
+ [ResultColumn]
+ public List MacroPropertyDtos { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs
index c5741a74fc..dd352c2c86 100644
--- a/src/Umbraco.Core/Models/ServerRegistration.cs
+++ b/src/Umbraco.Core/Models/ServerRegistration.cs
@@ -6,100 +6,6 @@ using Umbraco.Core.Sync;
namespace Umbraco.Core.Models
{
- internal class Macro : Entity, IAggregateRoot
- {
- ///
- /// Creates an item with pre-filled properties
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public Macro(int id, bool useInEditor, int refreshRate, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cachePersonalized, bool dontRender, string scriptPath)
- {
- Id = id;
- UseInEditor = useInEditor;
- RefreshRate = refreshRate;
- Alias = alias;
- Name = name;
- ControlType = controlType;
- ControlAssembly = controlAssembly;
- XsltPath = xsltPath;
- CacheByPage = cacheByPage;
- CachePersonalized = cachePersonalized;
- DontRender = dontRender;
- ScriptPath = scriptPath;
- }
-
- ///
- /// Creates an instance for persisting a new item
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public Macro(string @alias, string name,
- string controlType = "",
- string controlAssembly = "",
- string xsltPath = "",
- string scriptPath = "",
- bool cacheByPage = false,
- bool cachePersonalized = false,
- bool dontRender = true,
- bool useInEditor = false,
- int refreshRate = 0)
- {
- UseInEditor = useInEditor;
- RefreshRate = refreshRate;
- Alias = alias;
- Name = name;
- ControlType = controlType;
- ControlAssembly = controlAssembly;
- XsltPath = xsltPath;
- CacheByPage = cacheByPage;
- CachePersonalized = cachePersonalized;
- DontRender = dontRender;
- ScriptPath = scriptPath;
- }
-
- public bool UseInEditor { get; set; }
-
- public int RefreshRate { get; set; }
-
- public string Alias { get; set; }
-
- public string Name { get; set; }
-
- public string ControlType { get; set; }
-
- public string ControlAssembly { get; set; }
-
- public string XsltPath { get; set; }
-
- public bool CacheByPage { get; set; }
-
- public bool CachePersonalized { get; set; }
-
- public bool DontRender { get; set; }
-
- public string ScriptPath { get; set; }
- }
-
internal class ServerRegistration : Entity, IServerAddress, IAggregateRoot
{
private string _serverAddress;
diff --git a/src/Umbraco.Core/ObservableDictionary.cs b/src/Umbraco.Core/ObservableDictionary.cs
new file mode 100644
index 0000000000..e7fc899718
--- /dev/null
+++ b/src/Umbraco.Core/ObservableDictionary.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Umbraco.Core
+{
+ ///
+ /// An ObservableDictionary
+ ///
+ ///
+ /// Assumes that the key will not change and is unique for each element in the collection.
+ /// Collection is not thread-safe, so calls should be made single-threaded.
+ ///
+ /// The type of elements contained in the BindableCollection
+ /// The type of the indexing key
+ internal class ObservableDictionary : ObservableCollection
+ {
+ protected Dictionary Indecies = new Dictionary();
+ protected Func KeySelector;
+
+ ///
+ /// Create new ObservableDictionary
+ ///
+ /// Selector function to create key from value
+ public ObservableDictionary(Func keySelector)
+ : base()
+ {
+ if (keySelector == null) throw new ArgumentException("keySelector");
+ KeySelector = keySelector;
+ }
+
+ #region Protected Methods
+ protected override void InsertItem(int index, TValue item)
+ {
+ var key = KeySelector(item);
+ if (Indecies.ContainsKey(key))
+ throw new DuplicateKeyException(key.ToString());
+
+ if (index != this.Count)
+ {
+ foreach (var k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList())
+ {
+ Indecies[k]++;
+ }
+ }
+
+ base.InsertItem(index, item);
+ Indecies[key] = index;
+
+ }
+
+ protected override void ClearItems()
+ {
+ base.ClearItems();
+ Indecies.Clear();
+ }
+
+
+ protected override void RemoveItem(int index)
+ {
+ var item = this[index];
+ var key = KeySelector(item);
+
+ base.RemoveItem(index);
+
+ Indecies.Remove(key);
+
+ foreach (var k in Indecies.Keys.Where(k => Indecies[k] > index).ToList())
+ {
+ Indecies[k]--;
+ }
+ }
+ #endregion
+
+ public virtual bool ContainsKey(TKey key)
+ {
+ return Indecies.ContainsKey(key);
+ }
+
+ ///
+ /// Gets or sets the element with the specified key. If setting a new value, new value must have same key.
+ ///
+ /// Key of element to replace
+ ///
+ public virtual TValue this[TKey key]
+ {
+
+ get { return this[Indecies[key]]; }
+ set
+ {
+ //confirm key matches
+ if (!KeySelector(value).Equals(key))
+ throw new InvalidOperationException("Key of new value does not match");
+
+ if (!Indecies.ContainsKey(key))
+ {
+ this.Add(value);
+ }
+ else
+ {
+ this[Indecies[key]] = value;
+ }
+ }
+ }
+
+ ///
+ /// Replaces element at given key with new value. New value must have same key.
+ ///
+ /// Key of element to replace
+ /// New value
+ ///
+ ///
+ /// False if key not found
+ public virtual bool Replace(TKey key, TValue value)
+ {
+ if (!Indecies.ContainsKey(key)) return false;
+ //confirm key matches
+ if (!KeySelector(value).Equals(key))
+ throw new InvalidOperationException("Key of new value does not match");
+
+ this[Indecies[key]] = value;
+ return true;
+
+ }
+
+ public virtual bool Remove(TKey key)
+ {
+ if (!Indecies.ContainsKey(key)) return false;
+
+ this.RemoveAt(Indecies[key]);
+ return true;
+
+ }
+
+ internal class DuplicateKeyException : Exception
+ {
+
+ public string Key { get; private set; }
+ public DuplicateKeyException(string key)
+ : base("Attempted to insert duplicate key " + key + " in collection")
+ {
+ Key = key;
+ }
+ }
+
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs
index 1a64ef7cf5..613dae6f2e 100644
--- a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs
@@ -4,11 +4,11 @@ using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Factories
{
- internal class MacroFactory : IEntityFactory
+ internal class MacroFactory : IEntityFactory
{
#region Implementation of IEntityFactory
- public Macro BuildEntity(MacroDto dto)
+ public IMacro BuildEntity(MacroDto dto)
{
var model = new Macro(dto.Id, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.Python);
//on initial construction we don't want to have dirty properties tracked
@@ -17,17 +17,17 @@ namespace Umbraco.Core.Persistence.Factories
return model;
}
- public MacroDto BuildDto(Macro entity)
+ public MacroDto BuildDto(IMacro entity)
{
var dto = new MacroDto()
{
Alias = entity.Alias,
CacheByPage = entity.CacheByPage,
- CachePersonalized = entity.CachePersonalized,
+ CachePersonalized = entity.CacheByMember,
DontRender = entity.DontRender,
Name = entity.Name,
Python = entity.ScriptPath,
- RefreshRate = entity.RefreshRate,
+ RefreshRate = entity.CacheDuration,
ScriptAssembly = entity.ControlAssembly,
ScriptType = entity.ControlType,
UseInEditor = entity.UseInEditor,
diff --git a/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs
index 5db94eaf12..46cc9cf869 100644
--- a/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs
+++ b/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs
@@ -5,6 +5,7 @@ using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Mappers
{
[MapperFor(typeof(Macro))]
+ [MapperFor(typeof(IMacro))]
internal sealed class MacroMapper : BaseMapper
{
private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary();
@@ -28,12 +29,12 @@ namespace Umbraco.Core.Persistence.Mappers
CacheMap(src => src.Id, dto => dto.Id);
CacheMap(src => src.Alias, dto => dto.Alias);
CacheMap(src => src.CacheByPage, dto => dto.CacheByPage);
- CacheMap(src => src.CachePersonalized, dto => dto.CachePersonalized);
+ CacheMap(src => src.CacheByMember, dto => dto.CachePersonalized);
CacheMap(src => src.ControlAssembly, dto => dto.ScriptAssembly);
CacheMap(src => src.ControlType, dto => dto.ScriptType);
CacheMap(src => src.DontRender, dto => dto.DontRender);
CacheMap(src => src.Name, dto => dto.Name);
- CacheMap(src => src.RefreshRate, dto => dto.RefreshRate);
+ CacheMap(src => src.CacheDuration, dto => dto.RefreshRate);
CacheMap(src => src.ScriptPath, dto => dto.Python);
CacheMap(src => src.UseInEditor, dto => dto.UseInEditor);
CacheMap(src => src.XsltPath, dto => dto.Xslt);
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMacroRepository.cs
new file mode 100644
index 0000000000..7dab5eefe6
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMacroRepository.cs
@@ -0,0 +1,9 @@
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.Persistence.Repositories
+{
+ internal interface IMacroRepository : IRepositoryQueryable
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs
index f0ed6ae0fb..3aa0ea07f3 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs
@@ -7,11 +7,12 @@ using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Caching;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
- internal class MacroRepository : PetaPocoRepositoryBase
+ internal class MacroRepository : PetaPocoRepositoryBase, IMacroRepository
{
public MacroRepository(IDatabaseUnitOfWork work) : base(work)
{
@@ -21,7 +22,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
}
- protected override Macro PerformGet(int id)
+ protected override IMacro PerformGet(int id)
{
var sql = GetBaseQuery(false);
sql.Where(GetBaseWhereClause(), new { Id = id });
@@ -35,12 +36,12 @@ namespace Umbraco.Core.Persistence.Repositories
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
- entity.ResetDirtyProperties(false);
+ ((TracksChangesEntityBase)entity).ResetDirtyProperties(false);
return entity;
}
- protected override IEnumerable PerformGetAll(params int[] ids)
+ protected override IEnumerable PerformGetAll(params int[] ids)
{
if (ids.Any())
{
@@ -59,10 +60,10 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
- protected override IEnumerable PerformGetByQuery(IQuery query)
+ protected override IEnumerable PerformGetByQuery(IQuery query)
{
var sqlClause = GetBaseQuery(false);
- var translator = new SqlTranslator(sqlClause, query);
+ var translator = new SqlTranslator(sqlClause, query);
var sql = translator.Translate();
var dtos = Database.Fetch(sql);
@@ -76,8 +77,24 @@ namespace Umbraco.Core.Persistence.Repositories
protected override Sql GetBaseQuery(bool isCount)
{
var sql = new Sql();
- sql.Select(isCount ? "COUNT(*)" : "*")
- .From();
+ if (isCount)
+ {
+ sql.Select("COUNT(*)").From();
+ }
+ else
+ {
+ return GetBaseQuery("*");
+ }
+ return sql;
+ }
+
+ private static Sql GetBaseQuery(string columns)
+ {
+ var sql = new Sql();
+ sql.Select(columns)
+ .From()
+ .LeftJoin()
+ .On(left => left.Id, right => right.Macro);
return sql;
}
@@ -100,7 +117,7 @@ namespace Umbraco.Core.Persistence.Repositories
get { throw new NotImplementedException(); }
}
- protected override void PersistNewItem(Macro entity)
+ protected override void PersistNewItem(IMacro entity)
{
((Entity)entity).AddingEntity();
@@ -110,10 +127,17 @@ namespace Umbraco.Core.Persistence.Repositories
var id = Convert.ToInt32(Database.Insert(dto));
entity.Id = id;
+ foreach (var propDto in dto.MacroPropertyDtos)
+ {
+ //need to set the id explicitly here
+ propDto.Macro = id;
+ Database.Insert(propDto);
+ }
+
((ICanBeDirty)entity).ResetDirtyProperties();
}
- protected override void PersistUpdatedItem(Macro entity)
+ protected override void PersistUpdatedItem(IMacro entity)
{
((Entity)entity).UpdatingEntity();
@@ -122,6 +146,33 @@ namespace Umbraco.Core.Persistence.Repositories
Database.Update(dto);
+ //update the sections if they've changed
+ var macro = (Macro)entity;
+ if (macro.IsPropertyDirty("Properties"))
+ {
+ //for any that exist on the object, we need to determine if we need to update or insert
+ foreach (var propDto in dto.MacroPropertyDtos)
+ {
+ if (macro.AddedProperties.Contains(propDto.Alias))
+ {
+ //we need to insert since this was added
+ Database.Insert(propDto);
+ }
+ else
+ {
+ Database.Update(propDto);
+ }
+ }
+
+ //now we need to delete any props that have been removed
+ foreach (var propAlias in macro.RemovedProperties)
+ {
+ //delete the property
+ Database.Delete("WHERE macro=@macroId AND macroPropertyAlias=@propAlias",
+ new { macroId = macro.Id, propAlias = propAlias });
+ }
+ }
+
((ICanBeDirty)entity).ResetDirtyProperties();
}
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 7b4f5f2baa..2a85bcaf13 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -315,6 +315,14 @@
+
+
+
+
+
+
+
+
@@ -370,6 +378,7 @@
+
@@ -398,6 +407,7 @@
+
diff --git a/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs
index fbd5bb30a1..49cce1de95 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs
@@ -88,12 +88,12 @@ namespace Umbraco.Tests.Persistence.Repositories
Assert.That(macro.HasIdentity, Is.True);
Assert.That(macro.Alias, Is.EqualTo("test1"));
Assert.That(macro.CacheByPage, Is.EqualTo(false));
- Assert.That(macro.CachePersonalized, Is.EqualTo(false));
+ Assert.That(macro.CacheByMember, Is.EqualTo(false));
Assert.That(macro.ControlAssembly, Is.EqualTo("MyAssembly1"));
Assert.That(macro.ControlType, Is.EqualTo("~/usercontrol/test1.ascx"));
Assert.That(macro.DontRender, Is.EqualTo(true));
Assert.That(macro.Name, Is.EqualTo("Test1"));
- Assert.That(macro.RefreshRate, Is.EqualTo(0));
+ Assert.That(macro.CacheDuration, Is.EqualTo(0));
Assert.That(macro.ScriptPath, Is.EqualTo("~/views/macropartials/test1.cshtml"));
Assert.That(macro.UseInEditor, Is.EqualTo(false));
Assert.That(macro.XsltPath, Is.EqualTo("test1.xslt"));
@@ -128,7 +128,7 @@ namespace Umbraco.Tests.Persistence.Repositories
using (var repository = new MacroRepository(unitOfWork))
{
// Act
- var query = Query.Builder.Where(x => x.Alias.ToUpper() == "TEST1");
+ var query = Query.Builder.Where(x => x.Alias.ToUpper() == "TEST1");
var result = repository.GetByQuery(query);
// Assert
@@ -145,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Repositories
using (var repository = new MacroRepository(unitOfWork))
{
// Act
- var query = Query.Builder.Where(x => x.Name.StartsWith("Test"));
+ var query = Query.Builder.Where(x => x.Name.StartsWith("Test"));
int count = repository.Count(query);
// Assert
@@ -183,9 +183,9 @@ namespace Umbraco.Tests.Persistence.Repositories
// Act
var macro = repository.Get(2);
macro.Name = "Hello";
- macro.RefreshRate = 1234;
+ macro.CacheDuration = 1234;
macro.CacheByPage = true;
- macro.CachePersonalized = true;
+ macro.CacheByMember = true;
macro.ControlAssembly = "";
macro.ControlType = "";
macro.DontRender = false;
@@ -201,9 +201,9 @@ namespace Umbraco.Tests.Persistence.Repositories
// Assert
Assert.That(macroUpdated, Is.Not.Null);
Assert.That(macroUpdated.Name, Is.EqualTo("Hello"));
- Assert.That(macroUpdated.RefreshRate, Is.EqualTo(1234));
+ Assert.That(macroUpdated.CacheDuration, Is.EqualTo(1234));
Assert.That(macroUpdated.CacheByPage, Is.EqualTo(true));
- Assert.That(macroUpdated.CachePersonalized, Is.EqualTo(true));
+ Assert.That(macroUpdated.CacheByMember, Is.EqualTo(true));
Assert.That(macroUpdated.ControlAssembly, Is.EqualTo(""));
Assert.That(macroUpdated.ControlType, Is.EqualTo(""));
Assert.That(macroUpdated.DontRender, Is.EqualTo(false));
@@ -266,7 +266,7 @@ namespace Umbraco.Tests.Persistence.Repositories
{
repository.AddOrUpdate(new Macro("test1", "Test1", "~/usercontrol/test1.ascx", "MyAssembly1", "test1.xslt", "~/views/macropartials/test1.cshtml"));
repository.AddOrUpdate(new Macro("test2", "Test2", "~/usercontrol/test2.ascx", "MyAssembly2", "test2.xslt", "~/views/macropartials/test2.cshtml"));
- repository.AddOrUpdate(new Macro("test3", "Test3", "~/usercontrol/test3.ascx", "MyAssembly3", "test3.xslt", "~/views/macropartials/test3.cshtml"));
+ repository.AddOrUpdate(new Macro("test3", "Tet3", "~/usercontrol/test3.ascx", "MyAssembly3", "test3.xslt", "~/views/macropartials/test3.cshtml"));
unitOfWork.Commit();
}