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(); }