diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index f76ff0439f..43a5ec04aa 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -29,6 +29,8 @@ namespace Umbraco.Core.Cache public const string MemberBusinessLogicCacheKey = "MemberCacheItem_"; public const string TemplateFrontEndCacheKey = "template"; + + [Obsolete("This is no longer used and will be removed from the codebase in the future")] public const string TemplateBusinessLogicCacheKey = "UmbracoTemplateCache"; public const string UserContextCacheKey = "UmbracoUserContext"; diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs index a8307044a1..5c729dba87 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs @@ -68,6 +68,9 @@ namespace Umbraco.Core.Cache public virtual void ClearCacheObjectTypes(string typeName) { + var type = TypeFinder.GetTypeByName(typeName); + if (type == null) return; + var isInterface = type.IsInterface; using (WriteLock) { foreach (var entry in GetDictionaryEntries() @@ -77,7 +80,10 @@ namespace Umbraco.Core.Cache // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null var value = GetSafeLazyValue((Lazy)x.Value, true); - return value == null || value.GetType().ToString().InvariantEquals(typeName); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); }) .ToArray()) RemoveEntry((string) entry.Key); @@ -87,6 +93,7 @@ namespace Umbraco.Core.Cache public virtual void ClearCacheObjectTypes() { var typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; using (WriteLock) { foreach (var entry in GetDictionaryEntries() @@ -97,7 +104,10 @@ namespace Umbraco.Core.Cache // compare on exact type, don't use "is" // get non-created as NonCreatedValue & exceptions as null var value = GetSafeLazyValue((Lazy)x.Value, true); - return value == null || value.GetType() == typeOfT; + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); }) .ToArray()) RemoveEntry((string) entry.Key); @@ -107,6 +117,7 @@ namespace Umbraco.Core.Cache public virtual void ClearCacheObjectTypes(Func predicate) { var typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; var plen = CacheItemPrefix.Length + 1; using (WriteLock) { @@ -119,9 +130,12 @@ namespace Umbraco.Core.Cache // get non-created as NonCreatedValue & exceptions as null var value = GetSafeLazyValue((Lazy)x.Value, true); if (value == null) return true; - return value.GetType() == typeOfT - // run predicate on the 'public key' part only, ie without prefix - && predicate(((string)x.Key).Substring(plen), (T)value); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return (isInterface ? (value is T) : (value.GetType() == typeOfT)) + // run predicate on the 'public key' part only, ie without prefix + && predicate(((string) x.Key).Substring(plen), (T) value); })) RemoveEntry((string) entry.Key); } diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index c5870f26e8..a8033ed091 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -20,9 +20,15 @@ namespace Umbraco.Core.Cache private readonly System.Web.Caching.Cache _cache; + /// + /// Used for debugging + /// + internal Guid InstanceId { get; private set; } + public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache) { _cache = cache; + InstanceId = Guid.NewGuid(); } protected override IEnumerable GetDictionaryEntries() diff --git a/src/Umbraco.Core/Cache/ICacheProvider.cs b/src/Umbraco.Core/Cache/ICacheProvider.cs index 6cec619be8..0d2bb1bdb6 100644 --- a/src/Umbraco.Core/Cache/ICacheProvider.cs +++ b/src/Umbraco.Core/Cache/ICacheProvider.cs @@ -8,13 +8,51 @@ namespace Umbraco.Core.Cache /// public interface ICacheProvider { + /// + /// Removes all items from the cache. + /// void ClearAllCache(); + + /// + /// Removes an item from the cache, identified by its key. + /// + /// The key of the item. void ClearCacheItem(string key); + + /// + /// Removes items from the cache, of a specified type. + /// + /// The name of the type to remove. + /// + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + /// Performs a case-sensitive search. + /// void ClearCacheObjectTypes(string typeName); + + /// + /// Removes items from the cache, of a specified type. + /// + /// The type of the items to remove. + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). void ClearCacheObjectTypes(); + + /// + /// Removes items from the cache, of a specified type, satisfying a predicate. + /// + /// The type of the items to remove. + /// The predicate to satisfy. + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). void ClearCacheObjectTypes(Func predicate); + void ClearCacheByKeySearch(string keyStartsWith); void ClearCacheByKeyExpression(string regexString); + IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); IEnumerable GetCacheItemsByKeyExpression(string regexString); diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index edf1ba5aa6..509943fe3e 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Reflection; using System.Runtime.Caching; using System.Text.RegularExpressions; using System.Threading; @@ -16,15 +17,22 @@ namespace Umbraco.Core.Cache /// internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider { + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); internal ObjectCache MemoryCache; // an object that represent a value that has not been created yet protected readonly object ValueNotCreated = new object(); + /// + /// Used for debugging + /// + internal Guid InstanceId { get; private set; } + public ObjectCacheRuntimeCacheProvider() { MemoryCache = new MemoryCache("in-memory"); + InstanceId = Guid.NewGuid(); } protected object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false) @@ -64,6 +72,9 @@ namespace Umbraco.Core.Cache public virtual void ClearCacheObjectTypes(string typeName) { + var type = TypeFinder.GetTypeByName(typeName); + if (type == null) return; + var isInterface = type.IsInterface; using (new WriteLock(_locker)) { foreach (var key in MemoryCache @@ -73,7 +84,10 @@ namespace Umbraco.Core.Cache // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null var value = GetSafeLazyValue((Lazy)x.Value, true); - return value == null || value.GetType().ToString().InvariantEquals(typeName); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); }) .Select(x => x.Key) .ToArray()) // ToArray required to remove @@ -86,6 +100,7 @@ namespace Umbraco.Core.Cache using (new WriteLock(_locker)) { var typeOfT = typeof (T); + var isInterface = typeOfT.IsInterface; foreach (var key in MemoryCache .Where(x => { @@ -93,7 +108,11 @@ namespace Umbraco.Core.Cache // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null var value = GetSafeLazyValue((Lazy)x.Value, true); - return value == null || value.GetType() == typeOfT; + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); + }) .Select(x => x.Key) .ToArray()) // ToArray required to remove @@ -106,6 +125,7 @@ namespace Umbraco.Core.Cache using (new WriteLock(_locker)) { var typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; foreach (var key in MemoryCache .Where(x => { @@ -114,8 +134,11 @@ namespace Umbraco.Core.Cache // get non-created as NonCreatedValue & exceptions as null var value = GetSafeLazyValue((Lazy)x.Value, true); if (value == null) return true; - return value.GetType() == typeOfT - && predicate(x.Key, (T) value); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return (isInterface ? (value is T) : (value.GetType() == typeOfT)) + && predicate(x.Key, (T)value); }) .Select(x => x.Key) .ToArray()) // ToArray required to remove diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index bcf012e42f..dc093c8d20 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -11,6 +11,17 @@ namespace Umbraco.Core /// public static class Conventions { + public static class PublicAccess + { + public const string MemberUsernameRuleType = "MemberUsername"; + public const string MemberRoleRuleType = "MemberRole"; + + [Obsolete("No longer supported, this is here for backwards compatibility only")] + public const string MemberIdRuleType = "MemberId"; + [Obsolete("No longer supported, this is here for backwards compatibility only")] + public const string MemberGroupIdRuleType = "MemberGroupId"; + } + public static class Localization { /// diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs index ef85b41ec9..4ff65459d8 100644 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ b/src/Umbraco.Core/IO/SystemFiles.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.IO { public class SystemFiles { - + [Obsolete("This file is no longer used and should not be accessed!")] public static string AccessXml { get diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 9133377f5f..a4894b9b75 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -318,6 +318,17 @@ namespace Umbraco.Core.Models [DataMember] internal PublishedState PublishedState { get; set; } + /// + /// Gets or sets the unique identifier of the published version, if any. + /// + [IgnoreDataMember] + public Guid PublishedVersionGuid { get; internal set; } + + /// + /// Gets a value indicating whether the content has a published version. + /// + public bool HasPublishedVersion { get { return PublishedVersionGuid != default(Guid); } } + /// /// Changes the Trashed state of the content object /// diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 3c6a892900..652c957bf7 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -641,12 +641,10 @@ namespace Umbraco.Core.Models /// /// /// True if the content has any published versiom otherwise False + [Obsolete("Use the HasPublishedVersion property.", false)] public static bool HasPublishedVersion(this IContent content) { - if (content.HasIdentity == false) - return false; - - return ApplicationContext.Current.Services.ContentService.HasPublishedVersion(content.Id); + return content.HasPublishedVersion; } #region Tag methods diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 3121326f60..2715e5fe3a 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -20,12 +20,7 @@ namespace Umbraco.Core.Models /// bool Published { get; } - /// - /// Language of the data contained within this Content object. - /// - /// - /// Left internal until multilingual support is implemented. - /// + [Obsolete("This will be removed in future versions")] string Language { get; set; } /// @@ -78,5 +73,15 @@ namespace Umbraco.Core.Models /// /// IContent DeepCloneWithResetIdentities(); + + /// + /// Gets a value indicating whether the content has a published version. + /// + bool HasPublishedVersion { get; } + + /// + /// Gets the unique identifier of the published version, if any. + /// + Guid PublishedVersionGuid { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index a19b7d98c2..f79ce29436 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -236,7 +236,7 @@ namespace Umbraco.Core.Models /// Gets of Sets the Help text for the current PropertyType /// [DataMember] - [Obsolete("Not used anywhere in the UI")] + [Obsolete("Not used anywhere, will be removed in future versions")] public string HelpText { get { return _helpText; } diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs new file mode 100644 index 0000000000..27d1cd2121 --- /dev/null +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -0,0 +1,175 @@ +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 Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + [Serializable] + [DataContract(IsReference = true)] + public class PublicAccessEntry : Entity, IAggregateRoot + { + private readonly ObservableCollection _ruleCollection; + private int _protectedNodeId; + private int _noAccessNodeId; + private int _loginNodeId; + private readonly List _removedRules = new List(); + + public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent noAccessNode, IEnumerable ruleCollection) + { + LoginNodeId = loginNode.Id; + NoAccessNodeId = noAccessNode.Id; + _protectedNodeId = protectedNode.Id; + + _ruleCollection = new ObservableCollection(ruleCollection); + _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; + } + + public PublicAccessEntry(Guid id, int protectedNodeId, int loginNodeId, int noAccessNodeId, IEnumerable ruleCollection) + { + Key = id; + Id = Key.GetHashCode(); + + LoginNodeId = loginNodeId; + NoAccessNodeId = noAccessNodeId; + _protectedNodeId = protectedNodeId; + + _ruleCollection = new ObservableCollection(ruleCollection); + _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; + } + + void _ruleCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(AllowedSectionsSelector); + + //if (e.Action == NotifyCollectionChangedAction.Add) + //{ + // var item = e.NewItems.Cast().First(); + + // if (_addedSections.Contains(item) == false) + // { + // _addedSections.Add(item); + // } + //} + + if (e.Action == NotifyCollectionChangedAction.Remove) + { + var item = e.OldItems.Cast().First(); + + if (_removedRules.Contains(item.Key) == false) + { + _removedRules.Add(item.Key); + } + + } + } + + private static readonly PropertyInfo ProtectedNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ProtectedNodeId); + private static readonly PropertyInfo LoginNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.LoginNodeId); + private static readonly PropertyInfo NoAccessNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NoAccessNodeId); + private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Rules); + + internal IEnumerable RemovedRules + { + get { return _removedRules; } + } + + public IEnumerable Rules + { + get { return _ruleCollection; } + } + + public PublicAccessRule AddRule(string ruleValue, string ruleType) + { + var rule = new PublicAccessRule + { + AccessEntryId = Key, + RuleValue = ruleValue, + RuleType = ruleType + }; + _ruleCollection.Add(rule); + return rule; + } + + public void RemoveRule(PublicAccessRule rule) + { + _ruleCollection.Remove(rule); + } + + public void ClearRules() + { + foreach (var rule in _ruleCollection) + { + RemoveRule(rule); + } + } + + /// + /// Method to call on entity saved when first added + /// + internal override void AddingEntity() + { + if (Key == default(Guid)) + { + Key = Guid.NewGuid(); + } + base.AddingEntity(); + } + + [DataMember] + public int LoginNodeId + { + get { return _loginNodeId; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _loginNodeId = value; + return _loginNodeId; + }, _loginNodeId, LoginNodeIdSelector); + } + } + + [DataMember] + public int NoAccessNodeId + { + get { return _noAccessNodeId; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _noAccessNodeId = value; + return _noAccessNodeId; + }, _noAccessNodeId, NoAccessNodeIdSelector); + } + } + + [DataMember] + public int ProtectedNodeId + { + get { return _protectedNodeId; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _protectedNodeId = value; + return _protectedNodeId; + }, _protectedNodeId, ProtectedNodeIdSelector); + } + } + + public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + { + _removedRules.Clear(); + base.ResetDirtyProperties(rememberPreviouslyChangedProperties); + foreach (var publicAccessRule in _ruleCollection) + { + publicAccessRule.ResetDirtyProperties(rememberPreviouslyChangedProperties); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublicAccessRule.cs b/src/Umbraco.Core/Models/PublicAccessRule.cs new file mode 100644 index 0000000000..484652e8bd --- /dev/null +++ b/src/Umbraco.Core/Models/PublicAccessRule.cs @@ -0,0 +1,71 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + [Serializable] + [DataContract(IsReference = true)] + public class PublicAccessRule : Entity + { + private string _ruleValue; + private string _ruleType; + + public PublicAccessRule(Guid id, Guid accessEntryId) + { + AccessEntryId = accessEntryId; + Key = id; + Id = Key.GetHashCode(); + } + + public PublicAccessRule() + { + } + + private static readonly PropertyInfo RuleValueSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleValue); + private static readonly PropertyInfo RuleTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleType); + + public Guid AccessEntryId { get; internal set; } + + /// + /// Method to call on entity saved when first added + /// + internal override void AddingEntity() + { + if (Key == default(Guid)) + { + Key = Guid.NewGuid(); + } + base.AddingEntity(); + } + + public string RuleValue + { + get { return _ruleValue; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _ruleValue = value; + return _ruleValue; + }, _ruleValue, RuleValueSelector); + } + } + + public string RuleType + { + get { return _ruleType; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _ruleType = value; + return _ruleType; + }, _ruleType, RuleTypeSelector); + } + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/AccessDto.cs b/src/Umbraco.Core/Models/Rdbms/AccessDto.cs index 486b755095..ba2cb6d767 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/AccessDto.cs @@ -1,19 +1,24 @@ -using Umbraco.Core.Persistence; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Security.AccessControl; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoAccess")] - [PrimaryKey("id")] + [PrimaryKey("id", autoIncrement = false)] [ExplicitColumns] internal class AccessDto { [Column("id")] - [PrimaryKeyColumn(Name = "PK_umbracoAccess")] - public int Id { get; set; } + [PrimaryKeyColumn(Name = "PK_umbracoAccess", AutoIncrement = false)] + public Guid Id { get; set; } [Column("nodeId")] [ForeignKey(typeof(NodeDto), Name = "FK_umbracoAccess_umbracoNode_id")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoAccess_nodeId")] public int NodeId { get; set; } [Column("loginNodeId")] @@ -22,6 +27,17 @@ namespace Umbraco.Core.Models.Rdbms [Column("noAccessNodeId")] [ForeignKey(typeof(NodeDto), Name = "FK_umbracoAccess_umbracoNode_id2")] - public int AccessDeniedNodeId { get; set; } + public int NoAccessNodeId { get; set; } + + [Column("createDate")] + [Constraint(Default = "getdate()")] + public DateTime CreateDate { get; set; } + + [Column("updateDate")] + [Constraint(Default = "getdate()")] + public DateTime UpdateDate { get; set; } + + [ResultColumn] + public List Rules { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs b/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs index 6b006fabb7..a98dfb9450 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs @@ -1,25 +1,35 @@ +using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoAccessRule")] - [PrimaryKey("id")] + [PrimaryKey("id", autoIncrement = false)] [ExplicitColumns] internal class AccessRuleDto { [Column("id")] - [PrimaryKeyColumn(Name = "PK_umbracoAccessRule")] - public int Id { get; set; } + [PrimaryKeyColumn(Name = "PK_umbracoAccessRule", AutoIncrement = false)] + public Guid Id { get; set; } [Column("accessId")] [ForeignKey(typeof(AccessDto), Name = "FK_umbracoAccessRule_umbracoAccess_id")] - public int AccessId { get; set; } + public Guid AccessId { get; set; } - [Column("claim")] - public string Claim { get; set; } + [Column("ruleValue")] + [Index(IndexTypes.UniqueNonClustered, ForColumns = "ruleValue,ruleType,accessId", Name = "IX_umbracoAccessRule")] + public string RuleValue { get; set; } - [Column("claimType")] - public string ClaimType { get; set; } + [Column("ruleType")] + public string RuleType { get; set; } + + [Column("createDate")] + [Constraint(Default = "getdate()")] + public DateTime CreateDate { get; set; } + + [Column("updateDate")] + [Constraint(Default = "getdate()")] + public DateTime UpdateDate { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs index 5b49c3e46d..de087667b2 100644 --- a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs @@ -53,5 +53,8 @@ namespace Umbraco.Core.Models.Rdbms [ResultColumn] public ContentVersionDto ContentVersionDto { get; set; } + + [ResultColumn] + public DocumentPublishedReadOnlyDto DocumentPublishedReadOnlyDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs new file mode 100644 index 0000000000..4a7e359d91 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs @@ -0,0 +1,23 @@ +using System; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("cmsDocument")] + [PrimaryKey("versionId", autoIncrement = false)] + [ExplicitColumns] + internal class DocumentPublishedReadOnlyDto + { + [Column("nodeId")] + public int NodeId { get; set; } + + [Column("published")] + public bool Published { get; set; } + + [Column("versionId")] + public Guid VersionId { get; set; } + + [Column("newest")] + public bool Newest { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs index f9aadf4963..33c1a73059 100644 --- a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs @@ -40,9 +40,9 @@ namespace Umbraco.Core.Models.Rdbms public int SortOrder { get; set; } [Column("uniqueID")] - [NullSetting(NullSetting = NullSettings.Null)] - [Index(IndexTypes.NonClustered, Name = "IX_umbracoNodeUniqueID")] - public Guid? UniqueId { get; set; } + [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoNodeUniqueID")] + public Guid UniqueId { get; set; } [Column("text")] [NullSetting(NullSetting = NullSettings.Null)] diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs index 70ded5e161..f2ca705e8b 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs @@ -32,11 +32,6 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] public string Name { get; set; } - [Column("helpText")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(1000)] - public string HelpText { get; set; } - [Column("sortOrder")] [Constraint(Default = "0")] public int SortOrder { get; set; } diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs index 1b6145f3ad..2f448860b0 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs @@ -26,9 +26,6 @@ namespace Umbraco.Core.Models.Rdbms [Column("Name")] public string Name { get; set; } - [Column("helpText")] - public string HelpText { get; set; } - [Column("PropertyTypeSortOrder")] public int SortOrder { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index 0208a7a128..d21aea33e6 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -32,10 +32,7 @@ namespace Umbraco.Core.Persistence.Factories var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType) { Id = _id, - Key = - dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.HasValue - ? dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.Value - : _id.ToGuid(), + Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId, Name = dto.Text, NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text, Path = dto.ContentVersionDto.ContentDto.NodeDto.Path, @@ -51,7 +48,8 @@ namespace Umbraco.Core.Persistence.Factories ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null, ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null, Version = dto.ContentVersionDto.VersionId, - PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished + PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished, + PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId }; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 23c9270906..fdd2759d76 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -22,10 +22,7 @@ namespace Umbraco.Core.Persistence.Factories var contentType = new ContentType(dto.ContentTypeDto.NodeDto.ParentId) { Id = dto.ContentTypeDto.NodeDto.NodeId, - Key = - dto.ContentTypeDto.NodeDto.UniqueId.HasValue - ? dto.ContentTypeDto.NodeDto.UniqueId.Value - : dto.ContentTypeDto.NodeDto.NodeId.ToGuid(), + Key = dto.ContentTypeDto.NodeDto.UniqueId, Alias = dto.ContentTypeDto.Alias, Name = dto.ContentTypeDto.NodeDto.Text, Icon = dto.ContentTypeDto.Icon, diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs index 7b57800277..6fd6500552 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs @@ -24,10 +24,7 @@ namespace Umbraco.Core.Persistence.Factories CreateDate = dto.NodeDto.CreateDate, DatabaseType = dto.DbType.EnumParse(true), Id = dto.DataTypeId, - Key = - dto.NodeDto.UniqueId.HasValue - ? dto.NodeDto.UniqueId.Value - : dto.DataTypeId.ToGuid(), + Key = dto.NodeDto.UniqueId, Level = dto.NodeDto.Level, UpdateDate = dto.NodeDto.CreateDate, Name = dto.NodeDto.Text, diff --git a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs index cf285cbf66..2d8edcac52 100644 --- a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs @@ -32,10 +32,7 @@ namespace Umbraco.Core.Persistence.Factories var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType) { Id = _id, - Key = - dto.ContentDto.NodeDto.UniqueId.HasValue - ? dto.ContentDto.NodeDto.UniqueId.Value - : _id.ToGuid(), + Key = dto.ContentDto.NodeDto.UniqueId, Path = dto.ContentDto.NodeDto.Path, CreatorId = dto.ContentDto.NodeDto.UserId.Value, Level = dto.ContentDto.NodeDto.Level, diff --git a/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs index b63512e1b6..98048cd3a7 100644 --- a/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs @@ -21,10 +21,7 @@ namespace Umbraco.Core.Persistence.Factories var contentType = new MediaType(dto.NodeDto.ParentId) { Id = dto.NodeDto.NodeId, - Key = - dto.NodeDto.UniqueId.HasValue - ? dto.NodeDto.UniqueId.Value - : dto.NodeDto.NodeId.ToGuid(), + Key = dto.NodeDto.UniqueId, Alias = dto.Alias, Name = dto.NodeDto.Text, Icon = dto.Icon, diff --git a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs index 42e09b2f9c..a35c472f24 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs @@ -35,10 +35,7 @@ namespace Umbraco.Core.Persistence.Factories dto.Email,dto.LoginName,dto.Password, _contentType) { Id = _id, - Key = - dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.HasValue - ? dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.Value - : _id.ToGuid(), + Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId, Path = dto.ContentVersionDto.ContentDto.NodeDto.Path, CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value, Level = dto.ContentVersionDto.ContentDto.NodeDto.Level, diff --git a/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs index 17dbba3001..9544d170e2 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Factories { CreateDate = dto.CreateDate, Id = dto.NodeId, - Key = dto.UniqueId.Value, + Key = dto.UniqueId, Name = dto.Text }; diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 3db2fd8ba1..a2c310d2bf 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -112,7 +112,6 @@ namespace Umbraco.Core.Persistence.Factories Description = typeDto.Description, Id = typeDto.Id.Value, Name = typeDto.Name, - HelpText = typeDto.HelpText, Mandatory = typeDto.Mandatory, SortOrder = typeDto.SortOrder, ValidationRegExp = typeDto.ValidationRegExp, @@ -163,7 +162,6 @@ namespace Umbraco.Core.Persistence.Factories Alias = typeDto.Alias, DataTypeDefinitionId = typeDto.DataTypeId, Description = typeDto.Description, - HelpText = typeDto.HelpText, Id = typeDto.Id.Value, Mandatory = typeDto.Mandatory, Name = typeDto.Name, diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index 4764f41544..c7cd2094a1 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -68,7 +68,6 @@ namespace Umbraco.Core.Persistence.Factories propertyType.Description = typeDto.Description; propertyType.Id = typeDto.Id; propertyType.Name = typeDto.Name; - propertyType.HelpText = typeDto.HelpText; propertyType.Mandatory = typeDto.Mandatory; propertyType.SortOrder = typeDto.SortOrder; propertyType.ValidationRegExp = typeDto.ValidationRegExp; @@ -125,7 +124,6 @@ namespace Umbraco.Core.Persistence.Factories ContentTypeId = _id, DataTypeId = propertyType.DataTypeDefinitionId, Description = propertyType.Description, - HelpText = propertyType.HelpText, Mandatory = propertyType.Mandatory, Name = propertyType.Name, SortOrder = propertyType.SortOrder, diff --git a/src/Umbraco.Core/Persistence/Factories/PublicAccessEntryFactory.cs b/src/Umbraco.Core/Persistence/Factories/PublicAccessEntryFactory.cs new file mode 100644 index 0000000000..a576ef31c4 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/PublicAccessEntryFactory.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class PublicAccessEntryFactory + { + public PublicAccessEntry BuildEntity(AccessDto dto) + { + var entity = new PublicAccessEntry(dto.Id, dto.NodeId, dto.LoginNodeId, dto.NoAccessNodeId, + dto.Rules.Select(x => new PublicAccessRule(x.Id, x.AccessId) + { + RuleValue = x.RuleValue, + RuleType = x.RuleType, + CreateDate = x.CreateDate, + UpdateDate = x.UpdateDate + })) + { + CreateDate = dto.CreateDate, + UpdateDate = dto.UpdateDate + }; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + return entity; + } + + public AccessDto BuildDto(PublicAccessEntry entity) + { + var dto = new AccessDto + { + Id = entity.Key, + NoAccessNodeId = entity.NoAccessNodeId, + LoginNodeId = entity.LoginNodeId, + NodeId = entity.ProtectedNodeId, + CreateDate = entity.CreateDate, + UpdateDate = entity.UpdateDate, + Rules = entity.Rules.Select(x => new AccessRuleDto + { + AccessId = x.AccessEntryId, + Id = x.Key, + RuleValue = x.RuleValue, + RuleType = x.RuleType, + CreateDate = x.CreateDate, + UpdateDate = x.UpdateDate + }).ToList() + }; + + return dto; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index 1124d2e9a8..1e59c8e920 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -39,7 +39,7 @@ namespace Umbraco.Core.Persistence.Factories { CreateDate = dto.NodeDto.CreateDate, Id = dto.NodeId, - Key = dto.NodeDto.UniqueId.Value, + Key = dto.NodeDto.UniqueId, Path = dto.NodeDto.Path }; diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 1607f92be1..660cc95029 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -75,7 +75,7 @@ namespace Umbraco.Core.Persistence.Factories CreateDate = dto.CreateDate, CreatorId = dto.UserId.Value, Id = dto.NodeId, - Key = dto.UniqueId.Value, + Key = dto.UniqueId, Level = dto.Level, Name = dto.Text, NodeObjectTypeId = dto.NodeObjectType.Value, diff --git a/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs new file mode 100644 index 0000000000..170a8f0fc6 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs @@ -0,0 +1,36 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(PublicAccessEntry))] + public sealed class AccessMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + public AccessMapper() + { + BuildMap(); + } + + #region Overrides of BaseMapper + + internal override ConcurrentDictionary PropertyInfoCache + { + get { return PropertyInfoCacheInstance; } + } + + internal override void BuildMap() + { + CacheMap(src => src.Key, dto => dto.Id); + CacheMap(src => src.LoginNodeId, dto => dto.LoginNodeId); + CacheMap(src => src.NoAccessNodeId, dto => dto.NoAccessNodeId); + CacheMap(src => src.ProtectedNodeId, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.UpdateDate, dto => dto.UpdateDate); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs index 775cb690d4..216d68b7cc 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs @@ -35,7 +35,6 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Alias, dto => dto.Alias); CacheMap(src => src.DataTypeDefinitionId, dto => dto.DataTypeId); CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.HelpText, dto => dto.HelpText); CacheMap(src => src.Mandatory, dto => dto.Mandatory); CacheMap(src => src.Name, dto => dto.Name); CacheMap(src => src.SortOrder, dto => dto.SortOrder); diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index a641a4055e..27f5ec434d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -181,23 +181,23 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateCmsPropertyTypeData() { - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, DataTypeId = -90, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, DataTypeId = -38, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, DataTypeId = -90, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, DataTypeId = -38, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); //membership property types - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, HelpText = null, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, HelpText = null, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, HelpText = null, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, HelpText = null, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, HelpText = null, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, HelpText = null, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); //TODO: The member editor doesn't currently support providers that have question/answer so we'll leave these out for now. // Also, it's worth noting that the built in ASP.Net providers encrypt the answer so that admins cannot see it for added security which is something we should actually do! diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs index cf68ebd842..f1d739a503 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs @@ -61,6 +61,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert.Expressions private string GetQuotedValue(object val) { var type = val.GetType(); + switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs index 572e54de05..16c0a923ae 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs @@ -1,11 +1,12 @@ using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero { - [Migration("7.3.0", 5, GlobalSettings.UmbracoMigrationName)] + [Migration("7.3.0", 6, GlobalSettings.UmbracoMigrationName)] public class AddPublicAccessTables : MigrationBase { public AddPublicAccessTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) @@ -20,20 +21,31 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe if (tables.InvariantContains("umbracoAccess")) return; Create.Table("umbracoAccess") - .WithColumn("id").AsInt32().NotNullable().Identity().PrimaryKey("PK_umbracoAccess") + .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_umbracoAccess") .WithColumn("nodeId").AsInt32().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id", "umbracoNode", "id") .WithColumn("loginNodeId").AsInt32().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id1", "umbracoNode", "id") - .WithColumn("noAccessNodeId").AsInt32().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id2", "umbracoNode", "id"); + .WithColumn("noAccessNodeId").AsInt32().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id2", "umbracoNode", "id") + .WithColumn("createDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime) + .WithColumn("updateDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); + + //unique constraint on node id = 1:1 + Create.Index("IX_umbracoAccess_nodeId").OnTable("umbracoAccess").OnColumn("nodeId").Unique(); Create.Table("umbracoAccessRule") - .WithColumn("id").AsInt32().NotNullable().Identity().PrimaryKey("PK_umbracoAccessRule") - .WithColumn("accessId").AsInt32().NotNullable().ForeignKey("FK_umbracoAccessRule_umbracoAccess_id", "umbracoAccess", "id") - .WithColumn("claim").AsString().NotNullable() - .WithColumn("claimType").AsString().NotNullable(); + .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_umbracoAccessRule") + .WithColumn("accessId").AsGuid().NotNullable().ForeignKey("FK_umbracoAccessRule_umbracoAccess_id", "umbracoAccess", "id") + .WithColumn("ruleValue").AsString().NotNullable() + .WithColumn("ruleType").AsString().NotNullable() + .WithColumn("createDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime) + .WithColumn("updateDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); - //Create.PrimaryKey("PK_cmsContentType2ContentType") - // .OnTable("cmsContentType2ContentType") - // .Columns(new[] { "parentContentTypeId", "childContentTypeId" }); + //unique constraint on node + ruleValue + ruleType + Create.Index("IX_umbracoAccessRule").OnTable("umbracoAccessRule") + .OnColumn("accessId").Ascending() + .OnColumn("ruleValue").Ascending() + .OnColumn("ruleType").Ascending() + .WithOptions() + .Unique(); } public override void Down() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs index 800da45729..c9db282d85 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs @@ -16,14 +16,20 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe public override void Up() { - Insert.IntoTable("umbracoRelationType").Row(new + var exists = Context.Database.FirstOrDefault("WHERE alias=@alias", new {alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias}); + if (exists == null) { - dual = false, - parentObjectType = Guid.Parse(Constants.ObjectTypes.Document), - childObjectType = Guid.Parse(Constants.ObjectTypes.Document), - name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName, - alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias - }); + Insert.IntoTable("umbracoRelationType").Row(new + { + dual = false, + parentObjectType = Guid.Parse(Constants.ObjectTypes.Document), + childObjectType = Guid.Parse(Constants.ObjectTypes.Document), + name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName, + alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias + }); + } + + } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs new file mode 100644 index 0000000000..328322d7e2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs @@ -0,0 +1,123 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + [Migration("7.3.0", 7, GlobalSettings.UmbracoMigrationName)] + public class MovePublicAccessXmlDataToDb : MigrationBase + { + + + public override void Up() + { + //Don't lookup data if the table doesn't exist + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("umbracoAccess")) + { + //don't run if data is already there. + var dataExists = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + if (dataExists.Any()) return; + } + + var xmlFile = IOHelper.MapPath(SystemFiles.AccessXml); + using (var fileStream = File.OpenRead(xmlFile)) + { + var xml = XDocument.Load(fileStream); + + foreach (var page in xml.Root.Elements("page")) + { + var pageId = (int?) page.Attribute("id"); + var loginPageId = (int?) page.Attribute("loginPage"); + var noRightsPageId = (int?)page.Attribute("noRightsPage"); + if (pageId.HasValue == false || loginPageId.HasValue == false || noRightsPageId.HasValue == false) + continue; + + //ensure this page exists! + var umbracoNode = Context.Database.FirstOrDefault("WHERE id = @Id", new {Id = pageId.Value}); + if (umbracoNode != null) + { + var loginNode = Context.Database.FirstOrDefault("WHERE id = @Id", new { Id = loginPageId.Value }); + if (loginNode != null) + { + var noRightsPage = Context.Database.FirstOrDefault("WHERE id = @Id", new { Id = noRightsPageId.Value }); + if (noRightsPage != null) + { + var accessId = Guid.NewGuid(); + Insert.IntoTable("umbracoAccess").Row(new + { + id = accessId, + nodeId = umbracoNode.NodeId, + loginNodeId = loginNode.NodeId, + noAccessNodeId = noRightsPage.NodeId, + createDate = DateTime.Now, + updateDate = DateTime.Now + }); + + //if a memberId has been specified, then add that as a rule + var memberId = (string) page.Attribute("memberId"); + if (memberId.IsNullOrWhiteSpace() == false) + { + Insert.IntoTable("umbracoAccessRule").Row(new + { + id = Guid.NewGuid(), + accessId = accessId, + ruleValue = memberId, + ruleType = Constants.Conventions.PublicAccess.MemberUsernameRuleType, + createDate = DateTime.Now, + updateDate = DateTime.Now + }); + } + + //now there should be a member group(s) defined here + var memberGroupElements = page.Elements("group"); + foreach (var memberGroupElement in memberGroupElements) + { + var memberGroup = (string)memberGroupElement.Attribute("id"); + if (memberGroup.IsNullOrWhiteSpace() == false) + { + //create a member group rule + Insert.IntoTable("umbracoAccessRule").Row(new + { + id = Guid.NewGuid(), + accessId = accessId, + ruleValue = memberGroup, + ruleType = Constants.Conventions.PublicAccess.MemberRoleRuleType, + createDate = DateTime.Now, + updateDate = DateTime.Now + }); + } + } + + } + else + { + Logger.Warn("No umbracoNode could be found with id " + noRightsPageId.Value); + } + } + else + { + Logger.Warn("No umbracoNode could be found with id " + loginPageId.Value); + } + + } + else + { + Logger.Warn("No umbracoNode could be found with id " + pageId.Value); + } + } + + } + + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs new file mode 100644 index 0000000000..3f01442367 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs @@ -0,0 +1,23 @@ +using System.Linq; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + [Migration("7.3.0", 8, GlobalSettings.UmbracoMigrationName)] + public class RemoveHelpTextColumn : MigrationBase + { + public override void Up() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + + if (columns.Any(x => x.ColumnName.InvariantEquals("helpText") && x.TableName.InvariantEquals("cmsPropertyType"))) + { + Delete.Column("helpText").FromTable("cmsPropertyType"); + } + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs new file mode 100644 index 0000000000..bcef3f6379 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs @@ -0,0 +1,46 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + [Migration("7.3.0", 5, GlobalSettings.UmbracoMigrationName)] + public class UpdateUniqueIdToHaveCorrectIndexType : MigrationBase + { + //see: http://issues.umbraco.org/issue/U4-6188, http://issues.umbraco.org/issue/U4-6187 + public override void Up() + { + + + var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) + .Select(x => new DbIndexDefinition() + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + + //must be non-nullable + Alter.Column("uniqueID").OnTable("umbracoNode").AsGuid().NotNullable(); + + //make sure it already exists + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodeUniqueID"))) + { + Delete.Index("IX_umbracoNodeUniqueID").OnTable("umbracoNode"); + } + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNode_uniqueID")) == false) + { + //must be a uniqe index + Create.Index("IX_umbracoNode_uniqueID").OnTable("umbracoNode").OnColumn("uniqueID").Unique(); + } + + + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index f3293dc555..c54e9abc82 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -813,41 +813,49 @@ namespace Umbraco.Core.Persistence public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } + public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } // Multi Query public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql, args); } public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3)}, cb, sql, args); } public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4)}, cb, sql, args); } + public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, cb, sql, args); } // Multi Fetch (SQL builder) public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } + public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } // Multi Query (SQL builder) public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); } public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); } public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); } + public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, cb, sql.SQL, sql.Arguments); } // Multi Fetch (Simple) public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } + public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } // Multi Query (Simple) public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql, args); } public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); } public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); } + public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql, args); } // Multi Fetch (Simple) (SQL builder) public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } + public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } // Multi Query (Simple) (SQL builder) public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments); } public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments); } public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments); } + public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql.SQL, sql.Arguments); } // Automagically guess the property relationships between various POCOs and create a delegate that will set them up Delegate GetAutoMapper(Type[] types) diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index 9789f45ad4..4c8d72ceec 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -13,27 +13,59 @@ namespace Umbraco.Core.Persistence { public static class PetaPocoExtensions { - + // NOTE + // + // proper way to do it with TSQL and SQLCE + // IF EXISTS (SELECT ... FROM table WITH (UPDLOCK,HOLDLOCK)) WHERE ...) + // BEGIN + // UPDATE table SET ... WHERE ... + // END + // ELSE + // BEGIN + // INSERT INTO table (...) VALUES (...) + // END + // + // works in READ COMMITED, TSQL & SQLCE lock the constraint even if it does not exist, so INSERT is OK + // + // proper way to do it with MySQL + // IF EXISTS (SELECT ... FROM table WHERE ... FOR UPDATE) + // BEGIN + // UPDATE table SET ... WHERE ... + // END + // ELSE + // BEGIN + // INSERT INTO table (...) VALUES (...) + // END + // + // MySQL locks the constraint ONLY if it exists, so INSERT may fail... + // in theory, happens in READ COMMITTED but not REPEATABLE READ + // http://www.percona.com/blog/2012/08/28/differences-between-read-committed-and-repeatable-read-transaction-isolation-levels/ + // but according to + // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html + // it won't work for exact index value (only ranges) so really... + // + // MySQL should do + // INSERT INTO table (...) VALUES (...) ON DUPLICATE KEY UPDATE ... + // + // also the lock is released when the transaction is committed + // not sure if that can have unexpected consequences on our code? + // + // so... for the time being, let's do with that somewhat crazy solution below... /// - /// This will handle the issue of inserting data into a table when there can be a violation of a primary key or unique constraint which - /// can occur when two threads are trying to insert data at the exact same time when the data violates this constraint. + /// Safely inserts a record, or updates if it exists, based on a unique constraint. /// /// /// - /// - /// Returns the action that executed, either an insert or an update - /// - /// NOTE: If an insert occurred and a PK value got generated, the poco object passed in will contain the updated value. - /// + /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object + /// passed in will contain the updated value. /// - /// In different databases, there are a few raw SQL options like MySql's ON DUPLICATE KEY UPDATE or MSSQL's MERGE WHEN MATCHED, but since we are - /// also supporting SQLCE for which this doesn't exist we cannot simply rely on the underlying database to help us here. So we'll actually need to - /// try to be as proficient as possible when we know this can occur and manually handle the issue. - /// - /// We do this by first trying to Update the record, this will return the number of rows affected. If it is zero then we insert, if it is one, then - /// we know the update was successful and the row was already inserted by another thread. If the rowcount is zero and we insert and get an exception, - /// that's due to a race condition, in which case we need to retry and update. + /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE + /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting + /// isolation levels globally. We want to keep it simple for the time being and manage it manually. + /// We handle it by trying to update, then insert, etc. until something works, or we get bored. + /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value + /// once T1 and T2 have completed. Whereas here, it could contain T1's value. /// internal static RecordPersistenceType InsertOrUpdate(this Database db, T poco) where T : class @@ -42,66 +74,69 @@ namespace Umbraco.Core.Persistence } /// - /// This will handle the issue of inserting data into a table when there can be a violation of a primary key or unique constraint which - /// can occur when two threads are trying to insert data at the exact same time when the data violates this constraint. + /// Safely inserts a record, or updates if it exists, based on a unique constraint. /// /// /// /// /// If the entity has a composite key they you need to specify the update command explicitly - /// - /// Returns the action that executed, either an insert or an update - /// - /// NOTE: If an insert occurred and a PK value got generated, the poco object passed in will contain the updated value. - /// + /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object + /// passed in will contain the updated value. /// - /// In different databases, there are a few raw SQL options like MySql's ON DUPLICATE KEY UPDATE or MSSQL's MERGE WHEN MATCHED, but since we are - /// also supporting SQLCE for which this doesn't exist we cannot simply rely on the underlying database to help us here. So we'll actually need to - /// try to be as proficient as possible when we know this can occur and manually handle the issue. - /// - /// We do this by first trying to Update the record, this will return the number of rows affected. If it is zero then we insert, if it is one, then - /// we know the update was successful and the row was already inserted by another thread. If the rowcount is zero and we insert and get an exception, - /// that's due to a race condition, in which case we need to retry and update. + /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE + /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting + /// isolation levels globally. We want to keep it simple for the time being and manage it manually. + /// We handle it by trying to update, then insert, etc. until something works, or we get bored. + /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value + /// once T1 and T2 have completed. Whereas here, it could contain T1's value. /// internal static RecordPersistenceType InsertOrUpdate(this Database db, - T poco, - string updateCommand, + T poco, + string updateCommand, object updateArgs) where T : class { - if (poco == null) throw new ArgumentNullException("poco"); + if (poco == null) + throw new ArgumentNullException("poco"); + // try to update var rowCount = updateCommand.IsNullOrWhiteSpace() ? db.Update(poco) - : db.Update(updateCommand, updateArgs); - - if (rowCount > 0) return RecordPersistenceType.Update; - - try - { - db.Insert(poco); - return RecordPersistenceType.Insert; - } - //TODO: Need to find out if this is the same exception that will occur for all databases... pretty sure it will be - catch (SqlException ex) - { - //This will occur if the constraint was violated and this record was already inserted by another thread, - //at this exact same time, in this case we need to do an update - - rowCount = updateCommand.IsNullOrWhiteSpace() - ? db.Update(poco) - : db.Update(updateCommand, updateArgs); - - if (rowCount == 0) - { - //this would be strange! in this case the only circumstance would be that at the exact same time, 3 threads executed, one - // did the insert and the other somehow managed to do a delete precisely before this update was executed... now that would - // be real crazy. In that case we need to throw an exception. - throw new DataException("Record could not be inserted or updated"); - } - + : db.Update(updateCommand, updateArgs); + if (rowCount > 0) return RecordPersistenceType.Update; + + // failed: does not exist, need to insert + // RC1 race cond here: another thread may insert a record with the same constraint + + var i = 0; + while (i++ < 4) + { + try + { + // try to insert + db.Insert(poco); + return RecordPersistenceType.Insert; + } + catch (SqlException) // TODO: need to find out if all db will throw that exception - probably OK + { + // failed: exists (due to race cond RC1) + // RC2 race cond here: another thread may remove the record + + // try to update + rowCount = updateCommand.IsNullOrWhiteSpace() + ? db.Update(poco) + : db.Update(updateCommand, updateArgs); + if (rowCount > 0) + return RecordPersistenceType.Update; + + // failed: does not exist (due to race cond RC2), need to insert + // loop + } } + + // this can go on forever... have to break at some point and report an error. + throw new DataException("Record could not be inserted or updated."); } /// diff --git a/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs b/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs new file mode 100644 index 0000000000..bcb33f3597 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Relators +{ + internal class AccessRulesRelator + { + internal AccessDto Current; + + internal AccessDto Map(AccessDto a, AccessRuleDto p) + { + // Terminating call. Since we can return null from this function + // we need to be ready for PetaPoco to callback later with null + // parameters + if (a == null) + return Current; + + // Is this the same AccessDto as the current one we're processing + if (Current != null && Current.Id == a.Id) + { + // Yes, just add this AccessRuleDto to the current AccessDto's collection + if (p.Id != default(Guid)) + { + Current.Rules.Add(p); + } + + + // Return null to indicate we're not done with this AccessDto yet + return null; + } + + // This is a different AccessDto to the current one, or this is the + // first time through and we don't have a Tab yet + + // Save the current AccessDto + var prev = Current; + + // Setup the new current AccessDto + Current = a; + Current.Rules = new List(); + if (p.Id != default(Guid)) + { + Current.Rules.Add(p); + } + + // Return the now populated previous AccessDto (or null if first time through) + return prev; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 38cc03262d..e92e77283d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -65,7 +65,7 @@ namespace Umbraco.Core.Persistence.Repositories .Where(SqlSyntax, x => x.Newest) .OrderByDescending(SqlSyntax, x => x.VersionDate); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; @@ -107,6 +107,12 @@ namespace Umbraco.Core.Persistence.Repositories protected override Sql GetBaseQuery(bool isCount) { + var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", + SqlSyntax.GetQuotedTableName("cmsDocument"), + SqlSyntax.GetQuotedTableName("cmsDocument2"), + SqlSyntax.GetQuotedColumnName("nodeId"), + SqlSyntax.GetQuotedColumnName("published")); + var sql = new Sql(); sql.Select(isCount ? "COUNT(*)" : "*") .From(SqlSyntax) @@ -116,6 +122,13 @@ namespace Umbraco.Core.Persistence.Repositories .On(SqlSyntax, left => left.NodeId, right => right.NodeId) .InnerJoin(SqlSyntax) .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + + // cannot do this because PetaPoco does not know how to alias the table + //.LeftOuterJoin() + //.On(left => left.NodeId, right => right.NodeId) + // so have to rely on writing our own SQL + .Append(sqlx/*, new { @published = true }*/) + .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -142,7 +155,8 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsContentVersion WHERE ContentId = @Id", "DELETE FROM cmsContentXml WHERE nodeId = @Id", "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" + "DELETE FROM umbracoNode WHERE id = @Id", + "DELETE FROM umbracoAccess WHERE nodeId = @Id" }; return list; } @@ -241,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); sql.OrderByDescending(SqlSyntax, x => x.VersionDate); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; @@ -306,6 +320,22 @@ namespace Umbraco.Core.Persistence.Repositories #region Unit of Work Implementation + protected override void PersistDeletedItem(IContent entity) + { + //We need to clear out all access rules but we need to do this in a manual way since + // nothing in that table is joined to a content id + var subQuery = new Sql() + .Select("umbracoAccessRule.accessId") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.AccessId, right => right.Id) + .Where(dto => dto.NodeId == entity.Id); + Database.Execute(SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); + + //now let the normal delete clauses take care of everything else + base.PersistDeletedItem(entity); + } + protected override void PersistNewItem(IContent entity) { ((Content)entity).AddingEntity(); @@ -344,7 +374,6 @@ namespace Umbraco.Core.Persistence.Repositories entity.SortOrder = sortOrder; entity.Level = level; - //Assign the same permissions to it as the parent node // http://issues.umbraco.org/issue/U4-2161 var permissionsRepo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); @@ -404,6 +433,19 @@ namespace Umbraco.Core.Persistence.Repositories UpdatePropertyTags(entity, _tagRepository); } + // published => update published version infos, else leave it blank + if (entity.Published) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = dto.VersionId, + Newest = true, + NodeId = dto.NodeId, + Published = true + }; + ((Content)entity).PublishedVersionGuid = dto.VersionId; + } + entity.ResetDirtyProperties(); } @@ -561,6 +603,31 @@ namespace Umbraco.Core.Persistence.Repositories ClearEntityTags(entity, _tagRepository); } + // published => update published version infos, + // else if unpublished then clear published version infos + if (entity.Published) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = dto.VersionId, + Newest = true, + NodeId = dto.NodeId, + Published = true + }; + ((Content)entity).PublishedVersionGuid = dto.VersionId; + } + else if (publishedStateChanged) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = default(Guid), + Newest = false, + NodeId = dto.NodeId, + Published = false + }; + ((Content)entity).PublishedVersionGuid = default(Guid); + } + entity.ResetDirtyProperties(); } @@ -584,7 +651,7 @@ namespace Umbraco.Core.Persistence.Repositories .OrderBy(SqlSyntax, x => x.SortOrder); //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); foreach (var dto in dtos) { @@ -742,7 +809,7 @@ namespace Umbraco.Core.Persistence.Repositories private IEnumerable ProcessQuery(Sql sql) { //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); //nothing found if (dtos.Any() == false) return Enumerable.Empty(); diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index db8a5ff839..23ded5e5dd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -454,7 +454,6 @@ AND umbracoNode.id <> @id", propType.Description = dto.Description; propType.Id = dto.Id; propType.Name = dto.Name; - propType.HelpText = dto.HelpText; propType.Mandatory = dto.Mandatory; propType.SortOrder = dto.SortOrder; propType.ValidationRegExp = dto.ValidationRegExp; @@ -970,13 +969,13 @@ AND umbracoNode.id <> @id", // NOTE: MySQL requires a SELECT * FROM the inner union in order to be able to sort . lame. var sqlBuilder = new StringBuilder(@"SELECT PG.contenttypeNodeId as contentTypeId, - PT.ptId, PT.ptAlias, PT.ptDesc,PT.ptHelpText,PT.ptMandatory,PT.ptName,PT.ptSortOrder,PT.ptRegExp, + PT.ptId, PT.ptAlias, PT.ptDesc,PT.ptMandatory,PT.ptName,PT.ptSortOrder,PT.ptRegExp, PT.dtId,PT.dtDbType,PT.dtPropEdAlias, PG.id as pgId, PG.parentGroupId as pgParentGroupId, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText FROM cmsPropertyTypeGroup as PG LEFT JOIN ( - SELECT PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, PT.helpText as ptHelpText, + SELECT PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, PT.propertyTypeGroupId as ptGroupId, DT.dbType as dtDbType, DT.nodeId as dtId, DT.propertyEditorAlias as dtPropEdAlias @@ -990,7 +989,7 @@ AND umbracoNode.id <> @id", UNION SELECT PT.contentTypeId as contentTypeId, - PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, PT.helpText as ptHelpText, + PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, DT.nodeId as dtId, DT.dbType as dtDbType, DT.propertyEditorAlias as dtPropEdAlias, PG.id as pgId, PG.parentGroupId as pgParentGroupId, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPublicAccessRepository.cs new file mode 100644 index 0000000000..86a2c0739b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPublicAccessRepository.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IPublicAccessRepository : IRepositoryQueryable + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index d380786ffe..73f42e4860 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -100,7 +100,7 @@ namespace Umbraco.Core.Persistence.Repositories } sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", - "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.helpText", "cmsPropertyType.mandatory", + "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", diff --git a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs index 0cd21d3739..72b50619d9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data.SqlServerCe; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; @@ -87,8 +88,13 @@ namespace Umbraco.Core.Persistence.Repositories var deletes = GetDeleteClauses(); foreach (var delete in deletes) { - Database.Execute(delete, new {Id = entity.Id}); + Database.Execute(delete, new { Id = GetEntityId(entity) }); } } + + protected virtual TId GetEntityId(TEntity entity) + { + return (TId)(object)entity.Id; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs new file mode 100644 index 0000000000..295ff8b408 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Relators; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + internal class PublicAccessRepository : PetaPocoRepositoryBase, IPublicAccessRepository + { + public PublicAccessRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { + _options = new RepositoryCacheOptions + { + //We want to ensure that a zero count gets cached, even if there is nothing in the db we don't want it to lookup nothing each time + GetAllCacheAllowZeroCount = true, + //Set to 1000 just to ensure that all of them are cached, The GetAll on this repository gets called *A lot*, we want max performance + GetAllCacheThresholdLimit = 1000, + //Override to false so that a Count check against the db is NOT performed when doing a GetAll without params, we just want to + // return the raw cache without validation. The GetAll on this repository gets called *A lot*, we want max performance + GetAllCacheValidateCount = false + }; + } + + private readonly RepositoryCacheOptions _options; + + protected override PublicAccessEntry PerformGet(Guid id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + + var taskDto = Database.Fetch(new AccessRulesRelator().Map, sql).FirstOrDefault(); + if (taskDto == null) + return null; + + var factory = new PublicAccessEntryFactory(); + var entity = factory.BuildEntity(taskDto); + return entity; + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + var sql = GetBaseQuery(false); + + if (ids.Any()) + { + sql.Where("umbracoAccess.id IN (@ids)", new { ids = ids }); + } + + var factory = new PublicAccessEntryFactory(); + var dtos = Database.Fetch(new AccessRulesRelator().Map, sql); + return dtos.Select(factory.BuildEntity); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + var factory = new PublicAccessEntryFactory(); + var dtos = Database.Fetch(new AccessRulesRelator().Map, sql); + return dtos.Select(factory.BuildEntity); + } + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + sql.Select("*") + .From(SqlSyntax) + .LeftJoin(SqlSyntax) + .On(SqlSyntax, left => left.Id, right => right.AccessId); + + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoAccess.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM umbracoAccessRule WHERE accessId = @Id", + "DELETE FROM umbracoAccess WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + + /// + /// Returns the repository cache options + /// + protected override RepositoryCacheOptions RepositoryCacheOptions + { + get { return _options; } + } + + + protected override void PersistNewItem(PublicAccessEntry entity) + { + entity.AddingEntity(); + entity.Rules.ForEach(x => x.AddingEntity()); + + var factory = new PublicAccessEntryFactory(); + var dto = factory.BuildDto(entity); + + Database.Insert(dto); + //update the id so HasEntity is correct + entity.Id = entity.Key.GetHashCode(); + + foreach (var rule in dto.Rules) + { + rule.AccessId = entity.Key; + Database.Insert(rule); + } + + entity.ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(PublicAccessEntry entity) + { + entity.UpdatingEntity(); + entity.Rules.Where(x => x.HasIdentity).ForEach(x => x.UpdatingEntity()); + entity.Rules.Where(x => x.HasIdentity == false).ForEach(x => x.AddingEntity()); + + var factory = new PublicAccessEntryFactory(); + var dto = factory.BuildDto(entity); + + Database.Update(dto); + + foreach (var rule in entity.Rules) + { + if (rule.HasIdentity) + { + var count = Database.Update(dto.Rules.Single(x => x.Id == rule.Key)); + if (count == 0) + { + throw new InvalidOperationException("No rows were updated for the access rule"); + } + } + else + { + Database.Insert(new AccessRuleDto + { + Id = rule.Key, + AccessId = dto.Id, + RuleValue = rule.RuleValue, + RuleType = rule.RuleType, + CreateDate = rule.CreateDate, + UpdateDate = rule.UpdateDate + }); + //update the id so HasEntity is correct + rule.Id = rule.Key.GetHashCode(); + } + } + foreach (var removedRule in entity.RemovedRules) + { + Database.Delete("WHERE id=@Id", new {Id = removedRule}); + } + + entity.ResetDirtyProperties(); + } + + protected override Guid GetEntityId(PublicAccessEntry entity) + { + return entity.Key; + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 2f875cce50..037bab7a3b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -80,6 +80,8 @@ namespace Umbraco.Core.Persistence.Repositories { } + private readonly RepositoryCacheOptions _cacheOptions = new RepositoryCacheOptions(); + /// /// Used to create a new query instance /// @@ -164,23 +166,44 @@ namespace Umbraco.Core.Persistence.Repositories if (ids.Any()) { var entities = ids.Select(x => RuntimeCache.GetCacheItem(GetCacheIdKey(x))).ToArray(); - + if (ids.Count().Equals(entities.Count()) && entities.Any(x => x == null) == false) - return entities.Select(x => (TEntity)x); + return entities; } else { - var allEntities = RuntimeCache.GetCacheItemsByKeySearch(GetCacheTypeKey()).ToArray(); + var allEntities = RuntimeCache.GetCacheItemsByKeySearch(GetCacheTypeKey()) + .WhereNotNull() + .ToArray(); if (allEntities.Any()) { - //Get count of all entities of current type (TEntity) to ensure cached result is correct - var query = Query.Where(x => x.Id != 0); - int totalCount = PerformCount(query); - if (allEntities.Count() == totalCount) - return allEntities.Select(x => (TEntity)x); + if (RepositoryCacheOptions.GetAllCacheValidateCount) + { + //Get count of all entities of current type (TEntity) to ensure cached result is correct + var query = Query.Where(x => x.Id != 0); + int totalCount = PerformCount(query); + + if (allEntities.Count() == totalCount) + return allEntities; + } + else + { + return allEntities; + } } + else if (RepositoryCacheOptions.GetAllCacheAllowZeroCount) + { + //if the repository allows caching a zero count, then check the zero count cache + var zeroCount = RuntimeCache.GetCacheItem(GetCacheTypeKey()); + if (zeroCount != null && zeroCount.Any() == false) + { + //there is a zero count cache so return an empty list + return Enumerable.Empty(); + } + } + } var entityCollection = PerformGetAll(ids) @@ -192,7 +215,15 @@ namespace Umbraco.Core.Persistence.Repositories // coming back here we don't want to chuck it all into memory, this added cache here // is more for convenience when paging stuff temporarily - if (entityCollection.Length > 100) return entityCollection; + if (entityCollection.Length > RepositoryCacheOptions.GetAllCacheThresholdLimit) + return entityCollection; + + if (entityCollection.Length == 0 && RepositoryCacheOptions.GetAllCacheAllowZeroCount) + { + //there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache + // to signify that there is a zero count cache + RuntimeCache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {}); + } foreach (var entity in entityCollection) { @@ -206,6 +237,14 @@ namespace Umbraco.Core.Persistence.Repositories return entityCollection; } + /// + /// Returns the repository cache options + /// + protected virtual RepositoryCacheOptions RepositoryCacheOptions + { + get { return _cacheOptions; } + } + protected abstract IEnumerable PerformGetByQuery(IQuery query); /// /// Gets a list of entities by the passed in query @@ -260,12 +299,16 @@ namespace Umbraco.Core.Persistence.Repositories { PersistNewItem((TEntity)entity); RuntimeCache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity); + //If there's a GetAll zero count cache, ensure it is cleared + RuntimeCache.ClearCacheItem(GetCacheTypeKey()); } catch (Exception) { //if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way // that we cache entities: http://issues.umbraco.org/issue/U4-4259 RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Id)); + //If there's a GetAll zero count cache, ensure it is cleared + RuntimeCache.ClearCacheItem(GetCacheTypeKey()); throw; } @@ -281,13 +324,16 @@ namespace Umbraco.Core.Persistence.Repositories { PersistUpdatedItem((TEntity)entity); RuntimeCache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity); + //If there's a GetAll zero count cache, ensure it is cleared + RuntimeCache.ClearCacheItem(GetCacheTypeKey()); } catch (Exception) { //if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way // that we cache entities: http://issues.umbraco.org/issue/U4-4259 - //RepositoryCache.Delete(typeof(TEntity), entity); RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Id)); + //If there's a GetAll zero count cache, ensure it is cleared + RuntimeCache.ClearCacheItem(GetCacheTypeKey()); throw; } @@ -301,6 +347,8 @@ namespace Umbraco.Core.Persistence.Repositories { PersistDeletedItem((TEntity)entity); RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Id)); + //If there's a GetAll zero count cache, ensure it is cleared + RuntimeCache.ClearCacheItem(GetCacheTypeKey()); } #endregion diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs new file mode 100644 index 0000000000..9ac8aa6abd --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs @@ -0,0 +1,36 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + internal class RepositoryCacheOptions + { + /// + /// Constructor sets defaults + /// + public RepositoryCacheOptions() + { + GetAllCacheValidateCount = true; + GetAllCacheAllowZeroCount = false; + GetAllCacheThresholdLimit = 100; + } + + /// + /// True/false as to validate the total item count when all items are returned from cache, the default is true but this + /// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the normal + /// GetAll method. + /// + /// + /// setting this to return false will improve performance of GetAll cache with no params but should only be used + /// for specific circumstances + /// + public bool GetAllCacheValidateCount { get; set; } + + /// + /// True if the GetAll method will cache that there are zero results so that the db is not hit when there are no results found + /// + public bool GetAllCacheAllowZeroCount { get; set; } + + /// + /// The threshold entity count for which the GetAll method will cache entities + /// + public int GetAllCacheThresholdLimit { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index ee794d217c..f8ed226872 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -33,6 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ITemplatesSection _templateConfig; private readonly ViewHelper _viewHelper; private readonly MasterPageHelper _masterPageHelper; + private readonly RepositoryCacheOptions _cacheOptions; internal TemplateRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IFileSystem masterpageFileSystem, IFileSystem viewFileSystem, ITemplatesSection templateConfig, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -42,9 +43,27 @@ namespace Umbraco.Core.Persistence.Repositories _templateConfig = templateConfig; _viewHelper = new ViewHelper(_viewsFileSystem); _masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); + + _cacheOptions = new RepositoryCacheOptions + { + //Allow a zero count cache entry because GetAll() gets used quite a lot and we want to ensure + // if there are no templates, that it doesn't keep going to the db. + GetAllCacheAllowZeroCount = true, + //GetAll gets called a lot, we want to ensure that all templates are in the cache, default is 100 which + // would normally be fine but we'll increase it in case people have a ton of templates. + GetAllCacheThresholdLimit = 500 + }; + } + + + /// + /// Returns the repository cache options + /// + protected override RepositoryCacheOptions RepositoryCacheOptions + { + get { return _cacheOptions; } } - #region Overrides of RepositoryBase protected override ITemplate PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 5455b24900..4da3ec6061 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -46,6 +46,13 @@ namespace Umbraco.Core.Persistence return new NotificationsRepository(uow, _sqlSyntax); } + public virtual IPublicAccessRepository CreatePublicAccessRepository(IDatabaseUnitOfWork uow) + { + return new PublicAccessRepository(uow, + _cacheHelper, + _logger, _sqlSyntax); + } + public virtual ITaskRepository CreateTaskRepository(IDatabaseUnitOfWork uow) { return new TaskRepository(uow, @@ -55,7 +62,9 @@ namespace Umbraco.Core.Persistence public virtual IAuditRepository CreateAuditRepository(IDatabaseUnitOfWork uow) { - return new AuditRepository(uow, _cacheHelper, _logger, _sqlSyntax, _mappingResolver); + return new AuditRepository(uow, + CacheHelper.CreateDisabledCacheHelper(), //never cache + _logger, _sqlSyntax); } public virtual ITagRepository CreateTagRepository(IDatabaseUnitOfWork uow) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index b5cf2036f7..98c0e84218 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -611,6 +611,19 @@ namespace Umbraco.Core.Services return version.FirstOrDefault(x => x.Published == true); } + /// + /// Gets the published version of a item. + /// + /// The content item. + /// The published version, if any; otherwise, null. + public IContent GetPublishedVersion(IContent content) + { + if (content.Published) return content; + return content.HasPublishedVersion + ? GetByVersion(content.PublishedVersionGuid) + : null; + } + /// /// Gets a collection of objects, which reside at the first level / root /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index f5579896f4..6187cfc391 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -286,6 +286,13 @@ namespace Umbraco.Core.Services /// An item IContent GetPublishedVersion(int id); + /// + /// Gets the published version of a item. + /// + /// The content item. + /// The published version, if any; otherwise, null. + IContent GetPublishedVersion(IContent content); + /// /// Checks whether an item has any children /// diff --git a/src/Umbraco.Core/Services/IPublicAccessService.cs b/src/Umbraco.Core/Services/IPublicAccessService.cs new file mode 100644 index 0000000000..29bdb42f8b --- /dev/null +++ b/src/Umbraco.Core/Services/IPublicAccessService.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Security; + +namespace Umbraco.Core.Services +{ + public interface IPublicAccessService : IService + { + + /// + /// Gets all defined entries and associated rules + /// + /// + IEnumerable GetAll(); + + /// + /// Gets the entry defined for the content item's path + /// + /// + /// Returns null if no entry is found + PublicAccessEntry GetEntryForContent(IContent content); + + /// + /// Gets the entry defined for the content item based on a content path + /// + /// + /// Returns null if no entry is found + PublicAccessEntry GetEntryForContent(string contentPath); + + /// + /// Returns true if the content has an entry for it's path + /// + /// + /// + Attempt IsProtected(IContent content); + + /// + /// Returns true if the content has an entry based on a content path + /// + /// + /// + Attempt IsProtected(string contentPath); + + /// + /// Adds/updates a rule, if an entry doesn't exist one will be created with the new rule + /// + /// + /// + /// + /// + PublicAccessEntry AddOrUpdateRule(IContent content, string ruleType, string ruleValue); + + /// + /// Removes a rule + /// + /// + /// + /// + void RemoveRule(IContent content, string ruleType, string ruleValue); + + /// + /// Saves the entry + /// + /// + void Save(PublicAccessEntry entry); + + /// + /// Deletes the entry and all associated rules + /// + /// + void Delete(PublicAccessEntry entry); + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 5f09e99b7c..938e31dc1b 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -654,12 +654,6 @@ namespace Umbraco.Core.Services ValidationRegExp = property.Element("Validation").Value }; - var helpTextElement = property.Element("HelpText"); - if (helpTextElement != null) - { - propertyType.HelpText = helpTextElement.Value; - } - var tab = property.Element("Tab").Value; if (string.IsNullOrEmpty(tab)) { diff --git a/src/Umbraco.Core/Services/PublicAccessService.cs b/src/Umbraco.Core/Services/PublicAccessService.cs new file mode 100644 index 0000000000..b8e95a33a9 --- /dev/null +++ b/src/Umbraco.Core/Services/PublicAccessService.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Services +{ + public class PublicAccessService : RepositoryService, IPublicAccessService + { + public PublicAccessService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger) + : base(provider, repositoryFactory, logger) + { + } + + /// + /// Gets all defined entries and associated rules + /// + /// + public IEnumerable GetAll() + { + using (var repo = RepositoryFactory.CreatePublicAccessRepository(UowProvider.GetUnitOfWork())) + { + return repo.GetAll(); + } + } + + /// + /// Gets the entry defined for the content item's path + /// + /// + /// Returns null if no entry is found + public PublicAccessEntry GetEntryForContent(IContent content) + { + return GetEntryForContent(content.Path.EnsureEndsWith("," + content.Id)); + } + + /// + /// Gets the entry defined for the content item based on a content path + /// + /// + /// Returns null if no entry is found + /// + /// NOTE: This method get's called *very* often! This will return the results from cache + /// + public PublicAccessEntry GetEntryForContent(string contentPath) + { + //Get all ids in the path for the content item and ensure they all + // parse to ints that are not -1. + var ids = contentPath.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => + { + int val; + if (int.TryParse(x, out val)) + { + return val; + } + return -1; + }) + .Where(x => x != -1) + .ToList(); + + //start with the deepest id + ids.Reverse(); + + using (var repo = RepositoryFactory.CreatePublicAccessRepository(UowProvider.GetUnitOfWork())) + { + //This will retrieve from cache! + var entries = repo.GetAll().ToArray(); + + foreach (var id in ids) + { + var found = entries.FirstOrDefault(x => x.ProtectedNodeId == id); + if (found != null) return found; + } + } + + return null; + } + + /// + /// Returns true if the content has an entry for it's path + /// + /// + /// + public Attempt IsProtected(IContent content) + { + var result = GetEntryForContent(content); + return Attempt.If(result != null, result); + } + + /// + /// Returns true if the content has an entry based on a content path + /// + /// + /// + public Attempt IsProtected(string contentPath) + { + var result = GetEntryForContent(contentPath); + return Attempt.If(result != null, result); + } + + /// + /// Adds/updates a rule + /// + /// + /// + /// + /// + public PublicAccessEntry AddOrUpdateRule(IContent content, string ruleType, string ruleValue) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreatePublicAccessRepository(uow)) + { + var entry = repo.GetAll().FirstOrDefault(x => x.ProtectedNodeId == content.Id); + if (entry == null) return null; + + var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); + if (existingRule == null) + { + entry.AddRule(ruleValue, ruleType); + } + else + { + existingRule.RuleType = ruleType; + existingRule.RuleValue = ruleValue; + } + + repo.AddOrUpdate(entry); + + uow.Commit(); + + return entry; + } + } + + /// + /// Removes a rule + /// + /// + /// + /// + public void RemoveRule(IContent content, string ruleType, string ruleValue) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreatePublicAccessRepository(uow)) + { + var entry = repo.GetAll().FirstOrDefault(x => x.ProtectedNodeId == content.Id); + if (entry == null) return; + + var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); + if (existingRule == null) return; + + entry.RemoveRule(existingRule); + + repo.AddOrUpdate(entry); + + uow.Commit(); + } + } + + /// + /// Saves the entry + /// + /// + public void Save(PublicAccessEntry entry) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreatePublicAccessRepository(uow)) + { + repo.AddOrUpdate(entry); + uow.Commit(); + } + } + + /// + /// Deletes the entry and all associated rules + /// + /// + public void Delete(PublicAccessEntry entry) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreatePublicAccessRepository(uow)) + { + repo.Delete(entry); + uow.Commit(); + } + } + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs new file mode 100644 index 0000000000..0b682b2b6e --- /dev/null +++ b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Security; + +namespace Umbraco.Core.Services +{ + /// + /// Extension methods for the IPublicAccessService + /// + public static class PublicAccessServiceExtensions + { + + public static bool RenameMemberGroupRoleRules(this IPublicAccessService publicAccessService, string oldRolename, string newRolename) + { + var hasChange = false; + if (oldRolename == newRolename) return false; + + var allEntries = publicAccessService.GetAll(); + + foreach (var entry in allEntries) + { + //get rules that match + var roleRules = entry.Rules + .Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) + .Where(x => x.RuleValue == oldRolename); + var save = false; + foreach (var roleRule in roleRules) + { + //a rule is being updated so flag this entry to be saved + roleRule.RuleValue = newRolename; + save = true; + } + if (save) + { + hasChange = true; + publicAccessService.Save(entry); + } + } + + return hasChange; + } + + public static bool HasAccess(this IPublicAccessService publicAccessService, int documentId, IContentService contentService, IEnumerable currentMemberRoles) + { + var content = contentService.GetById(documentId); + if (content == null) return true; + + var entry = publicAccessService.GetEntryForContent(content); + if (entry == null) return true; + + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType + && currentMemberRoles.Contains(x.RuleValue)); + } + + [Obsolete("this is only used for backward compat")] + internal static bool HasAccess(this IPublicAccessService publicAccessService, int documentId, object providerUserKey, IContentService contentService, MembershipProvider membershipProvider, RoleProvider roleProvider) + { + var content = contentService.GetById(documentId); + if (content == null) return true; + + var entry = publicAccessService.GetEntryForContent(content); + if (entry == null) return true; + + var member = membershipProvider.GetUser(providerUserKey, false); + if (member == null) return false; + + var roles = roleProvider.GetRolesForUser(member.UserName); + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType + && roles.Contains(x.RuleValue)); + } + + public static bool HasAccess(this IPublicAccessService publicAccessService, string path, MembershipUser member, RoleProvider roleProvider) + { + var entry = publicAccessService.GetEntryForContent(path.EnsureEndsWith(path)); + if (entry == null) return true; + + var roles = roleProvider.GetRolesForUser(member.UserName); + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType + && roles.Contains(x.RuleValue)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 1bc4590303..4150b0b578 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -19,6 +19,7 @@ namespace Umbraco.Core.Services /// public class ServiceContext { + private Lazy _publicAccessService; private Lazy _taskService; private Lazy _domainService; private Lazy _auditService; @@ -68,6 +69,7 @@ namespace Umbraco.Core.Services /// /// /// + /// public ServiceContext( IContentService contentService = null, IMediaService mediaService = null, @@ -90,7 +92,8 @@ namespace Umbraco.Core.Services IAuditService auditService = null, IDomainService domainService = null, ITaskService taskService = null, - IMacroService macroService = null) + IMacroService macroService = null, + IPublicAccessService publicAccessService = null) { if (auditService != null) _auditService = new Lazy(() => auditService); if (localizedTextService != null) _localizedTextService = new Lazy(() => localizedTextService); @@ -114,6 +117,7 @@ namespace Umbraco.Core.Services if (domainService != null) _domainService = new Lazy(() => domainService); if (taskService != null) _taskService = new Lazy(() => taskService); if (macroService != null) _macroService = new Lazy(() => macroService); + if (publicAccessService != null) _publicAccessService = new Lazy(() => publicAccessService); } internal ServiceContext( @@ -146,6 +150,9 @@ namespace Umbraco.Core.Services var provider = dbUnitOfWorkProvider; var fileProvider = fileUnitOfWorkProvider; + if (_publicAccessService == null) + _publicAccessService = new Lazy(() => new PublicAccessService(provider, repositoryFactory, logger)); + if (_taskService == null) _taskService = new Lazy(() => new TaskService(provider, repositoryFactory, logger)); @@ -221,6 +228,14 @@ namespace Umbraco.Core.Services } + /// + /// Gets the + /// + public IPublicAccessService PublicAccessService + { + get { return _publicAccessService.Value; } + } + /// /// Gets the /// diff --git a/src/Umbraco.Core/Services/TaskService.cs b/src/Umbraco.Core/Services/TaskService.cs index 74c0a66e4f..bf510f3258 100644 --- a/src/Umbraco.Core/Services/TaskService.cs +++ b/src/Umbraco.Core/Services/TaskService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index 6b257fecfe..d621f18a3c 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -699,7 +699,14 @@ namespace Umbraco.Core #endregion - + public static Type GetTypeByName(string typeName) + { + var type = Type.GetType(typeName); + if (type != null) return type; + return AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.GetType(typeName)) + .FirstOrDefault(x => x != null); + } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 24831f2360..318610ccb4 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -306,30 +306,14 @@ + + - - - - - - - - - - - - - - - - - - @@ -342,6 +326,22 @@ + + + + + + + + + + + + + + + + @@ -360,6 +360,19 @@ + + + + + + + + + + + + + @@ -372,7 +385,10 @@ + + + diff --git a/src/Umbraco.Core/XmlExtensions.cs b/src/Umbraco.Core/XmlExtensions.cs index 02ebc07490..8784cbfb30 100644 --- a/src/Umbraco.Core/XmlExtensions.cs +++ b/src/Umbraco.Core/XmlExtensions.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; +using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; @@ -13,6 +16,31 @@ namespace Umbraco.Core /// internal static class XmlExtensions { + /// + /// Saves the xml document async + /// + /// + /// + /// + public static async Task SaveAsync(this XmlDocument xdoc, string filename) + { + if (xdoc.DocumentElement == null) + throw new XmlException("Cannot save xml document, there is no root element"); + + using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize: 4096, useAsync: true)) + using (var xmlWriter = XmlWriter.Create(fs, new XmlWriterSettings + { + Async = true, + Encoding = Encoding.UTF8, + Indent = true + })) + { + //NOTE: There are no nice methods to write it async, only flushing it async. We + // could implement this ourselves but it'd be a very manual process. + xdoc.WriteTo(xmlWriter); + await xmlWriter.FlushAsync(); + } + } public static bool HasAttribute(this XmlAttributeCollection attributes, string attributeName) { diff --git a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs index 7bde2ffddd..035a76d052 100644 --- a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs @@ -11,6 +11,7 @@ namespace Umbraco.Tests.FrontEnd [TestFixture] public class UmbracoHelperTests { + [Test] public void Truncate_Simple() { diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index e7fdfea54d..dcb590889b 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -3,10 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; +using System.Web.Security; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; +using Umbraco.Core.Dictionary; +using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -15,6 +18,8 @@ using Umbraco.Core.Services; using Moq; using Umbraco.Tests.TestHelpers; using Umbraco.Web; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; namespace Umbraco.Tests { @@ -76,16 +81,75 @@ namespace Umbraco.Tests public void Can_Get_Umbraco_Context() { var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); - ApplicationContext.EnsureContext(appCtx, true); var umbCtx = UmbracoContext.EnsureContext( - new Mock().Object, + Mock.Of(), appCtx, + new Mock(null, null).Object, Mock.Of(), + Enumerable.Empty(), true); Assert.AreEqual(umbCtx, UmbracoContext.Current); } + [Test] + public void Can_Mock_Umbraco_Helper() + { + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + + var helper = new UmbracoHelper(umbCtx, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new UrlProvider(umbCtx, new[] {Mock.Of()}, UrlProviderMode.Auto), Mock.Of(), + Mock.Of(), + new MembershipHelper(umbCtx, Mock.Of(), Mock.Of())); + + Assert.Pass(); + } + + [Test] + public void Can_Mock_Umbraco_Helper_Get_Url() + { + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + + var urlHelper = new Mock(); + urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns("/hello/world/1234"); + + var helper = new UmbracoHelper(umbCtx, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new UrlProvider(umbCtx, new[] + { + urlHelper.Object + }, UrlProviderMode.Auto), Mock.Of(), + Mock.Of(), + new MembershipHelper(umbCtx, Mock.Of(), Mock.Of())); + + Assert.AreEqual("/hello/world/1234", helper.Url(1234)); + } } } diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index d8dea6b04d..12bc90ca2e 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -483,7 +483,6 @@ namespace Umbraco.Tests.Models Alias = "subtitle", Name = "Subtitle", Description = "Optional subtitle", - HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 @@ -504,7 +503,7 @@ namespace Umbraco.Tests.Models // Act var propertyType = new PropertyType("test", DataTypeDatabaseType.Ntext) { - Alias = "subtitle", Name = "Subtitle", Description = "Optional subtitle", HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 + Alias = "subtitle", Name = "Subtitle", Description = "Optional subtitle", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }; contentType.PropertyGroups["Content"].PropertyTypes.Add(propertyType); content.Properties.Add(new Property(propertyType){Value = "This is a subtitle Test"}); @@ -527,7 +526,6 @@ namespace Umbraco.Tests.Models Alias = "subtitle", Name = "Subtitle", Description = "Optional subtitle", - HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 @@ -555,7 +553,7 @@ namespace Umbraco.Tests.Models // Act - note that the PropertyType's properties like SortOrder is not updated through the Content object var propertyType = new PropertyType("test", DataTypeDatabaseType.Ntext) { - Alias = "title", Name = "Title", Description = "Title description added", HelpText = "", Mandatory = false, SortOrder = 10, DataTypeDefinitionId = -88 + Alias = "title", Name = "Title", Description = "Title description added", Mandatory = false, SortOrder = 10, DataTypeDefinitionId = -88 }; content.Properties.Add(new Property(propertyType)); @@ -746,7 +744,6 @@ namespace Umbraco.Tests.Models Alias = "subtitle", Name = "Subtitle", Description = "Optional subtitle", - HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 @@ -773,7 +770,6 @@ namespace Umbraco.Tests.Models Alias = "coauthor", Name = "Co-Author", Description = "Name of the Co-Author", - HelpText = "", Mandatory = false, SortOrder = 4, DataTypeDefinitionId = -88 @@ -806,7 +802,6 @@ namespace Umbraco.Tests.Models Alias = "coauthor", Name = "Co-Author", Description = "Name of the Co-Author", - HelpText = "", Mandatory = false, SortOrder = 4, DataTypeDefinitionId = -88 @@ -841,7 +836,6 @@ namespace Umbraco.Tests.Models Alias = "coauthor", Name = "Co-Author", Description = "Name of the Co-Author", - HelpText = "", Mandatory = false, SortOrder = 4, DataTypeDefinitionId = -88 @@ -855,7 +849,6 @@ namespace Umbraco.Tests.Models Alias = "author", Name = "Author", Description = "Name of the Author", - HelpText = "", Mandatory = false, SortOrder = 4, DataTypeDefinitionId = -88 diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index b62252e987..194dbc119b 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Models } //add a property type without a property group contentType.PropertyTypeCollection.Add( - new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title2", Name = "Title2", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title2", Name = "Title2", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); contentType.AllowedTemplates = new[] { new Template("-1,2", "Name", "name") { Id = 200 }, new Template("-1,3", "Name2", "name2") { Id = 201 } }; contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy(() => 888), 8, "sub"), new ContentTypeSort(new Lazy(() => 889), 9, "sub2") }; diff --git a/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs index b4fd390ac4..566b90b157 100644 --- a/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs @@ -1,16 +1,28 @@ +using System; using System.CodeDom; +using System.Linq; using System.Web; using System.Web.Mvc; +using System.Web.Routing; +using System.Web.Security; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Dictionary; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Profiling; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; namespace Umbraco.Tests.Mvc { @@ -21,11 +33,13 @@ namespace Umbraco.Tests.Mvc public void Can_Construct_And_Get_Result() { var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); - ApplicationContext.EnsureContext(appCtx, true); var umbCtx = UmbracoContext.EnsureContext( new Mock().Object, appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), true); var ctrl = new TestSurfaceController(umbCtx); @@ -44,6 +58,9 @@ namespace Umbraco.Tests.Mvc var umbCtx = UmbracoContext.EnsureContext( new Mock().Object, appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), true); var ctrl = new TestSurfaceController(umbCtx); @@ -54,12 +71,18 @@ namespace Umbraco.Tests.Mvc [Test] public void Umbraco_Helper_Not_Null() { - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); - ApplicationContext.EnsureContext(appCtx, true); + var appCtx = new ApplicationContext( + new DatabaseContext(new Mock().Object, Mock.Of(), Mock.Of(), "test"), + MockHelper.GetMockedServiceContext(), + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); var umbCtx = UmbracoContext.EnsureContext( new Mock().Object, appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), true); var ctrl = new TestSurfaceController(umbCtx); @@ -70,58 +93,102 @@ namespace Umbraco.Tests.Mvc [Test] public void Can_Lookup_Content() { - //init app context - var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); - //TODO: Need to either make this public or make all methods on the UmbracoHelper or - // in v7 the PublishedContentQuery object virtual so we can just mock the methods - - var contentCaches = new Mock(); - - //init content resolver - //TODO: This is not public so people cannot actually do this! - - PublishedCachesResolver.Current = new PublishedCachesResolver(contentCaches.Object); - - //init umb context - var umbCtx = UmbracoContext.EnsureContext( new Mock().Object, appCtx, + new Mock(null, null).Object, + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "AutoLegacy")), + Enumerable.Empty(), true); - //setup the mock + var helper = new UmbracoHelper( + umbCtx, + Mock.Of(), + Mock.Of(query => query.TypedContent(It.IsAny()) == + //return mock of IPublishedContent for any call to GetById + Mock.Of(content => content.Id == 2)), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new UrlProvider(umbCtx, Enumerable.Empty()), + Mock.Of(), + Mock.Of(), + new MembershipHelper(umbCtx, Mock.Of(), Mock.Of())); - contentCaches.Setup(caches => caches.CreateContextualContentCache(It.IsAny())) - .Returns(new ContextualPublishedContentCache( - Mock.Of(cache => - cache.GetById(It.IsAny(), false, It.IsAny()) == - //return mock of IPublishedContent for any call to GetById - Mock.Of(content => content.Id == 2)), - umbCtx)); + var ctrl = new TestSurfaceController(umbCtx, helper); + var result = ctrl.GetContent(2) as PublishedContentResult; - + Assert.IsNotNull(result); + Assert.AreEqual(2, result.Content.Id); + } - - - using (var uTest = new DisposableUmbracoTest(appCtx)) + [Test] + public void Mock_Current_Page() + { + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + + var webRoutingSettings = Mock.Of(section => section.UrlProviderMode == "AutoLegacy"); + + var umbCtx = UmbracoContext.EnsureContext( + new Mock().Object, + appCtx, + new Mock(null, null).Object, + Mock.Of(section => section.WebRouting == webRoutingSettings), + Enumerable.Empty(), + true); + + var content = Mock.Of(publishedContent => publishedContent.Id == 12345); + + var contextBase = umbCtx.HttpContext; + var pcr = new PublishedContentRequest(new Uri("http://localhost/test"), + umbCtx.RoutingContext, + webRoutingSettings, + s => Enumerable.Empty()) { - var ctrl = new TestSurfaceController(uTest.UmbracoContext); - var result = ctrl.GetContent(2) as PublishedContentResult; + PublishedContent = content + }; - Assert.IsNotNull(result); - Assert.AreEqual(2, result.Content.Id); - } + var routeDefinition = new RouteDefinition + { + PublishedContentRequest = pcr + }; + + var routeData = new RouteData(); + routeData.DataTokens.Add("umbraco-route-def", routeDefinition); + + var ctrl = new TestSurfaceController(umbCtx, new UmbracoHelper()); + ctrl.ControllerContext = new ControllerContext(contextBase, routeData, ctrl); + + var result = ctrl.GetContentFromCurrentPage() as PublishedContentResult; + + Assert.AreEqual(12345, result.Content.Id); } public class TestSurfaceController : SurfaceController { + private readonly UmbracoHelper _umbracoHelper; + public TestSurfaceController(UmbracoContext umbracoContext) : base(umbracoContext) { } + public TestSurfaceController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) + : base(umbracoContext) + { + _umbracoHelper = umbracoHelper; + } + + /// + /// Returns an UmbracoHelper object + /// + public override UmbracoHelper Umbraco + { + get { return _umbracoHelper ?? base.Umbraco; } + } + public ActionResult Index() { return View(); @@ -133,6 +200,13 @@ namespace Umbraco.Tests.Mvc return new PublishedContentResult(content); } + + public ActionResult GetContentFromCurrentPage() + { + var content = CurrentPage; + + return new PublishedContentResult(content); + } } public class PublishedContentResult : ActionResult diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index e34cc45c69..75c24ee3db 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -131,7 +131,6 @@ namespace Umbraco.Tests.Persistence.Repositories Alias = "subtitle", Name = "Subtitle", Description = "Optional Subtitle", - HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 @@ -338,7 +337,6 @@ namespace Umbraco.Tests.Persistence.Repositories Alias = "urlAlias", Name = "Url Alias", Description = "", - HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 @@ -446,7 +444,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var propertyGroup = contentType.PropertyGroups.First(x => x.Name == "Meta"); - propertyGroup.PropertyTypes.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metaAuthor", Name = "Meta Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + propertyGroup.PropertyTypes.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metaAuthor", Name = "Meta Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); repository.AddOrUpdate(contentType); unitOfWork.Commit(); @@ -473,7 +471,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); var propertyGroup = contentType.PropertyGroups.First(x => x.Name == "Meta"); - propertyGroup.PropertyTypes.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metaAuthor", Name = "Meta Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + propertyGroup.PropertyTypes.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metaAuthor", Name = "Meta Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); repository.AddOrUpdate(contentType); unitOfWork.Commit(); @@ -512,7 +510,7 @@ namespace Umbraco.Tests.Persistence.Repositories contentType.RemovePropertyType("keywords"); //Add PropertyType var propertyGroup = contentType.PropertyGroups.First(x => x.Name == "Meta"); - propertyGroup.PropertyTypes.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metaAuthor", Name = "Meta Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + propertyGroup.PropertyTypes.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metaAuthor", Name = "Meta Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); repository.AddOrUpdate(contentType); unitOfWork.Commit(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index 6b4636ec28..9bc727aeb1 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -75,7 +75,6 @@ namespace Umbraco.Tests.Persistence.Repositories Alias = "subtitle", Name = "Subtitle", Description = "Optional Subtitle", - HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 298781dd40..7e2c54b08f 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -394,7 +394,7 @@ namespace Umbraco.Tests.Persistence.Repositories "cmsPropertyData.dataDate", "cmsPropertyData.dataInt", "cmsPropertyData.dataNtext", "cmsPropertyData.dataNvarchar", "cmsPropertyType.id", "cmsPropertyType.Alias", "cmsPropertyType.Description", "cmsPropertyType.Name", "cmsPropertyType.mandatory", "cmsPropertyType.validationRegExp", - "cmsPropertyType.helpText", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId", + "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId", "cmsPropertyType.dataTypeId", "cmsDataType.propertyEditorAlias", "cmsDataType.dbType") .From(SqlSyntax) .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs new file mode 100644 index 0000000000..77a252a129 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Web.UI.WebControls; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; +using Content = Umbraco.Core.Models.Content; + +namespace Umbraco.Tests.Persistence.Repositories +{ + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class PublicAccessRepositoryTest : BaseDatabaseFactoryTest + { + [Test] + public void Can_Delete() + { + var content = CreateTestData(3).ToArray(); + + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax)) + { + + var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] + { + new PublicAccessRule + { + RuleValue = "test", + RuleType = "RoleName" + }, + }); + repo.AddOrUpdate(entry); + unitOfWork.Commit(); + + repo.Delete(entry); + unitOfWork.Commit(); + + entry = repo.Get(entry.Key); + Assert.IsNull(entry); + } + } + + [Test] + public void Can_Add() + { + var content = CreateTestData(3).ToArray(); + + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax)) + { + var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] + { + new PublicAccessRule + { + RuleValue = "test", + RuleType = "RoleName" + }, + }); + repo.AddOrUpdate(entry); + unitOfWork.Commit(); + + var found = repo.GetAll().ToArray(); + + Assert.AreEqual(1, found.Count()); + Assert.AreEqual(content[0].Id, found[0].ProtectedNodeId); + Assert.AreEqual(content[1].Id, found[0].LoginNodeId); + Assert.AreEqual(content[2].Id, found[0].NoAccessNodeId); + Assert.IsTrue(found[0].HasIdentity); + Assert.AreNotEqual(default(DateTime), found[0].CreateDate); + Assert.AreNotEqual(default(DateTime), found[0].UpdateDate); + Assert.AreEqual(1, found[0].Rules.Count()); + Assert.AreEqual("test", found[0].Rules.First().RuleValue); + Assert.AreEqual("RoleName", found[0].Rules.First().RuleType); + Assert.AreNotEqual(default(DateTime), found[0].Rules.First().CreateDate); + Assert.AreNotEqual(default(DateTime), found[0].Rules.First().UpdateDate); + Assert.IsTrue(found[0].Rules.First().HasIdentity); + } + } + + [Test] + public void Can_Update() + { + var content = CreateTestData(3).ToArray(); + + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax)) + { + var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] + { + new PublicAccessRule + { + RuleValue = "test", + RuleType = "RoleName" + }, + }); + repo.AddOrUpdate(entry); + unitOfWork.Commit(); + + //re-get + entry = repo.Get(entry.Key); + + entry.Rules.First().RuleValue = "blah"; + entry.Rules.First().RuleType = "asdf"; + repo.AddOrUpdate(entry); + + unitOfWork.Commit(); + + //re-get + entry = repo.Get(entry.Key); + + Assert.AreEqual("blah", entry.Rules.First().RuleValue); + Assert.AreEqual("asdf", entry.Rules.First().RuleType); + } + } + + [Test] + public void Get_By_Id() + { + var content = CreateTestData(3).ToArray(); + + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax)) + { + var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] + { + new PublicAccessRule + { + RuleValue = "test", + RuleType = "RoleName" + }, + }); + repo.AddOrUpdate(entry); + unitOfWork.Commit(); + + //re-get + entry = repo.Get(entry.Key); + + Assert.IsNotNull(entry); + } + } + + [Test] + public void Get_All() + { + var content = CreateTestData(3).ToArray(); + + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax)) + { + var entry1 = new PublicAccessEntry(content[0], content[1], content[2], new[] + { + new PublicAccessRule + { + RuleValue = "test", + RuleType = "RoleName" + }, + }); + repo.AddOrUpdate(entry1); + + var entry2 = new PublicAccessEntry(content[1], content[0], content[2], new[] + { + new PublicAccessRule + { + RuleValue = "test", + RuleType = "RoleName" + }, + }); + repo.AddOrUpdate(entry2); + + unitOfWork.Commit(); + + var found = repo.GetAll().ToArray(); + Assert.AreEqual(2, found.Count()); + } + } + + + [Test] + public void Get_All_With_Id() + { + var content = CreateTestData(3).ToArray(); + + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax)) + { + var entry1 = new PublicAccessEntry(content[0], content[1], content[2], new[] + { + new PublicAccessRule + { + RuleValue = "test", + RuleType = "RoleName" + }, + }); + repo.AddOrUpdate(entry1); + + var entry2 = new PublicAccessEntry(content[1], content[0], content[2], new[] + { + new PublicAccessRule + { + RuleValue = "test", + RuleType = "RoleName" + }, + }); + repo.AddOrUpdate(entry2); + + unitOfWork.Commit(); + + var found = repo.GetAll(entry1.Key).ToArray(); + Assert.AreEqual(1, found.Count()); + } + } + + + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) + { + var templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()); + var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger, SqlSyntax); + contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, templateRepository); + var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository); + return repository; + } + + private IEnumerable CreateTestData(int count) + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository ctRepo; + using (var repo = CreateRepository(unitOfWork, out ctRepo)) + { + var ct = MockedContentTypes.CreateBasicContentType("testing"); + ctRepo.AddOrUpdate(ct); + unitOfWork.Commit(); + var result = new List(); + for (int i = 0; i < count; i++) + { + var c = new Content("test" + i, -1, ct); + repo.AddOrUpdate(c); + result.Add(c); + } + unitOfWork.Commit(); + + return result; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs index efff2a0658..bd7631ddf5 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs @@ -96,6 +96,12 @@ namespace Umbraco.Tests.Persistence.Repositories task.Comment = "blah"; task.Closed = true; + repo.AddOrUpdate(task); + unitOfWork.Commit(); + + //re-get + task = repo.Get(task.Id); + Assert.AreEqual(true, task.Closed); Assert.AreEqual("blah", task.Comment); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs index ff72ad1d1e..90d4b7b661 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs @@ -2,8 +2,10 @@ using System; using System.Collections; using System.Collections.ObjectModel; using System.Globalization; +using System.Web.Security; using Moq; using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; @@ -16,10 +18,8 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ctor_Throws_On_Null_PCR() { - Assert.Throws(() => new PublishedContentRequestEngine( - ServiceContext.DomainService, - ServiceContext.LocalizationService, - ProfilingLogger, + Assert.Throws(() => new PublishedContentRequestEngine( + Mock.Of(), null)); } @@ -29,11 +29,12 @@ namespace Umbraco.Tests.PublishedContent var routeCtx = GetRoutingContext("/test"); var pcre = new PublishedContentRequestEngine( - ServiceContext.DomainService, - ServiceContext.LocalizationService, - ProfilingLogger, + Mock.Of(), new PublishedContentRequest( - routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx)); + routeCtx.UmbracoContext.CleanedUmbracoUrl, + routeCtx, + Mock.Of(), + s => new string[] { })); var result = pcre.ConfigureRequest(); Assert.IsFalse(result); @@ -44,15 +45,13 @@ namespace Umbraco.Tests.PublishedContent { var routeCtx = GetRoutingContext("/test"); - var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx, Mock.Of(), s => new string[] { }); var pc = GetPublishedContentMock(); pcr.PublishedContent = pc.Object; pcr.Culture = new CultureInfo("en-AU"); pcr.SetRedirect("/hello"); var pcre = new PublishedContentRequestEngine( - ServiceContext.DomainService, - ServiceContext.LocalizationService, - ProfilingLogger, + Mock.Of(), pcr); var result = pcre.ConfigureRequest(); @@ -64,14 +63,12 @@ namespace Umbraco.Tests.PublishedContent { var routeCtx = GetRoutingContext("/test"); - var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx, Mock.Of(), s => new string[] { }); var pc = GetPublishedContentMock(); pcr.PublishedContent = pc.Object; pcr.Culture = new CultureInfo("en-AU"); var pcre = new PublishedContentRequestEngine( - ServiceContext.DomainService, - ServiceContext.LocalizationService, - ProfilingLogger, + Mock.Of(), pcr); pcre.ConfigureRequest(); @@ -85,14 +82,12 @@ namespace Umbraco.Tests.PublishedContent { var routeCtx = GetRoutingContext("/test"); - var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx, Mock.Of(), s => new string[] { }); var pc = GetPublishedContentMock(); pcr.Culture = new CultureInfo("en-AU"); pcr.PublishedContent = pc.Object; var pcre = new PublishedContentRequestEngine( - ServiceContext.DomainService, - ServiceContext.LocalizationService, - ProfilingLogger, + Mock.Of(), pcr); pcre.ConfigureRequest(); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByNiceUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByNiceUrlWithDomainsTests.cs index c527ea0cdd..2bbe6f8678 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByNiceUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByNiceUrlWithDomainsTests.cs @@ -145,7 +145,7 @@ namespace Umbraco.Tests.Routing [TestCase("http://domain1.com/fr/1001-2-1", 100121, "fr-FR")] [TestCase("http://domain1.com/1001-3", 10013, "en-US")] - [TestCase("http://domain2.com/1002", 1002, "en-US")] + [TestCase("http://domain2.com/1002", 1002, null)] [TestCase("http://domain3.com/", 1003, "en-US")] [TestCase("http://domain3.com/en", 10031, "en-US")] @@ -155,12 +155,15 @@ namespace Umbraco.Tests.Routing [TestCase("http://domain3.com/1003-3", 10033, "en-US")] [TestCase("https://domain1.com/", 1001, "en-US")] - [TestCase("https://domain3.com/", 1001, "en-US")] // because domain3 is explicitely set on http + [TestCase("https://domain3.com/", 1001, null)] // because domain3 is explicitely set on http public void Lookup_NestedDomains(string url, int expectedId, string expectedCulture) { SetDomains4(); + // defaults depend on test environment + expectedCulture = expectedCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + SettingsForTests.HideTopLevelNodeFromPath = true; var routingContext = GetRoutingContext(url); diff --git a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs index b86d8f8af1..5bb2820e44 100644 --- a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Routing [TestCase("http://domain1.com/fr", "fr-FR", 10012)] // domain takes over local wildcard at 10012 [TestCase("http://domain1.com/fr/1001-2-1", "fr-FR", 100121)] // domain takes over local wildcard at 10012 - [TestCase("/1003", "en-US", 1003)] // default culture (no domain) + [TestCase("/1003", null, 1003)] // default culture (no domain) [TestCase("/1003/1003-1", "nl-NL", 10031)] // wildcard on 10031 applies [TestCase("/1003/1003-1/1003-1-1", "nl-NL", 100311)] // wildcard on 10031 applies #endregion @@ -187,6 +187,9 @@ namespace Umbraco.Tests.Routing { SetDomains2(); + // defaults depend on test environment + expectedCulture = expectedCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + var routingContext = GetRoutingContext(inputUrl); var url = routingContext.UmbracoContext.CleanedUmbracoUrl; //very important to use the cleaned up umbraco url var pcr = new PublishedContentRequest(url, routingContext); diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs index 8b1981bc9e..ab83294496 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs @@ -1,7 +1,7 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -13,73 +13,272 @@ namespace Umbraco.Tests.Scheduling [TestFixture] public class BackgroundTaskRunnerTests { - - - [Test] - public void Startup_And_Shutdown() + private static void AssertRunnerStopsRunning(BackgroundTaskRunner runner, int timeoutMilliseconds = 2000) + where T : class, IBackgroundTask { - BackgroundTaskRunner tManager; - using (tManager = new BackgroundTaskRunner(true, true)) - { - tManager.StartUp(); - } + const int period = 200; - NUnit.Framework.Assert.IsFalse(tManager.IsRunning); + var i = 0; + var m = timeoutMilliseconds/period; + while (runner.IsRunning && i++ < m) + Thread.Sleep(period); + Assert.IsFalse(runner.IsRunning, "Runner is still running."); } [Test] - public void Startup_Starts_Automatically() + public void ShutdownWaitWhenRunning() { - BackgroundTaskRunner tManager; - using (tManager = new BackgroundTaskRunner(true, true)) + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true })) { - tManager.Add(new MyTask()); - NUnit.Framework.Assert.IsTrue(tManager.IsRunning); + Assert.IsTrue(runner.IsRunning); + Thread.Sleep(800); // for long + Assert.IsTrue(runner.IsRunning); + runner.Shutdown(false, true); // -force +wait + AssertRunnerStopsRunning(runner); + Assert.IsTrue(runner.IsCompleted); } } [Test] - public void Task_Runs() + public void ShutdownWhenRunning() { - var myTask = new MyTask(); - var waitHandle = new ManualResetEvent(false); - BackgroundTaskRunner tManager; - using (tManager = new BackgroundTaskRunner(true, true)) + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) { - tManager.TaskCompleted += (sender, task) => waitHandle.Set(); + // do NOT try to do this because the code must run on the UI thread which + // is not availably, and so the thread never actually starts - wondering + // what it means for ASP.NET? + //runner.TaskStarting += (sender, args) => Console.WriteLine("starting {0:c}", DateTime.Now); + //runner.TaskCompleted += (sender, args) => Console.WriteLine("completed {0:c}", DateTime.Now); - tManager.Add(myTask); - - //wait for ITasks to complete - waitHandle.WaitOne(); - - NUnit.Framework.Assert.IsTrue(myTask.Ended != default(DateTime)); + Assert.IsFalse(runner.IsRunning); + runner.Add(new MyTask(5000)); + Assert.IsTrue(runner.IsRunning); // is running the task + runner.Shutdown(false, false); // -force -wait + Assert.IsTrue(runner.IsCompleted); + Assert.IsTrue(runner.IsRunning); // still running that task + Thread.Sleep(3000); + Assert.IsTrue(runner.IsRunning); // still running that task + AssertRunnerStopsRunning(runner, 10000); } } [Test] - public void Many_Tasks_Run() + public void ShutdownFlushesTheQueue() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + Assert.IsFalse(runner.IsRunning); + runner.Add(new MyTask(5000)); + runner.Add(new MyTask()); + var t = new MyTask(); + runner.Add(t); + Assert.IsTrue(runner.IsRunning); // is running the first task + runner.Shutdown(false, false); // -force -wait + AssertRunnerStopsRunning(runner, 10000); + Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run + } + } + + [Test] + public void ShutdownForceTruncatesTheQueue() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + Assert.IsFalse(runner.IsRunning); + runner.Add(new MyTask(5000)); + runner.Add(new MyTask()); + var t = new MyTask(); + runner.Add(t); + Assert.IsTrue(runner.IsRunning); // is running the first task + runner.Shutdown(true, false); // +force -wait + AssertRunnerStopsRunning(runner, 10000); + Assert.AreEqual(DateTime.MinValue, t.Ended); // t has not run + } + } + + [Test] + public void ShutdownThenForce() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + Assert.IsFalse(runner.IsRunning); + runner.Add(new MyTask(5000)); + runner.Add(new MyTask()); + runner.Add(new MyTask()); + Assert.IsTrue(runner.IsRunning); // is running the task + runner.Shutdown(false, false); // -force -wait + Assert.IsTrue(runner.IsCompleted); + Assert.IsTrue(runner.IsRunning); // still running that task + Thread.Sleep(3000); + Assert.IsTrue(runner.IsRunning); // still running that task + runner.Shutdown(true, false); // +force -wait + AssertRunnerStopsRunning(runner, 20000); + } + } + + [Test] + public void Create_IsRunning() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + Assert.IsFalse(runner.IsRunning); + } + } + + [Test] + public void Create_AutoStart_IsRunning() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { AutoStart = true })) + { + Assert.IsTrue(runner.IsRunning); + AssertRunnerStopsRunning(runner); // though not for long + } + } + + [Test] + public void Create_AutoStartAndKeepAlive_IsRunning() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true })) + { + Assert.IsTrue(runner.IsRunning); + Thread.Sleep(800); // for long + Assert.IsTrue(runner.IsRunning); + // dispose will stop it + } + } + + [Test] + public void Dispose_IsRunning() + { + BackgroundTaskRunner runner; + using (runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true })) + { + Assert.IsTrue(runner.IsRunning); + // dispose will stop it + } + + AssertRunnerStopsRunning(runner); + Assert.Throws(() => runner.Add(new MyTask())); + } + + [Test] + public void Startup_IsRunning() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + Assert.IsFalse(runner.IsRunning); + runner.StartUp(); + Assert.IsTrue(runner.IsRunning); + AssertRunnerStopsRunning(runner); // though not for long + } + } + + [Test] + public void Startup_KeepAlive_IsRunning() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true })) + { + Assert.IsFalse(runner.IsRunning); + runner.StartUp(); + Assert.IsTrue(runner.IsRunning); + // dispose will stop it + } + } + + [Test] + public void Create_AddTask_IsRunning() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + runner.Add(new MyTask()); + Assert.IsTrue(runner.IsRunning); + Thread.Sleep(800); // task takes 500ms + Assert.IsFalse(runner.IsRunning); + } + } + + [Test] + public void Create_KeepAliveAndAddTask_IsRunning() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true })) + { + runner.Add(new MyTask()); + Assert.IsTrue(runner.IsRunning); + Thread.Sleep(800); // task takes 500ms + Assert.IsTrue(runner.IsRunning); + // dispose will stop it + } + } + + [Test] + public async void WaitOnRunner_OneTask() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + var task = new MyTask(); + Assert.IsTrue(task.Ended == default(DateTime)); + runner.Add(task); + await runner; // wait 'til it's not running anymore + Assert.IsTrue(task.Ended != default(DateTime)); // task is done + AssertRunnerStopsRunning(runner); // though not for long + } + } + + [Test] + public async void WaitOnRunner_Tasks() + { + var tasks = new List(); + for (var i = 0; i < 10; i++) + tasks.Add(new MyTask()); + + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = false, LongRunning = true, PreserveRunningTask = true })) + { + tasks.ForEach(runner.Add); + + await runner; // wait 'til it's not running anymore + + // check that tasks are done + Assert.IsTrue(tasks.All(x => x.Ended != default(DateTime))); + + Assert.AreEqual(TaskStatus.RanToCompletion, runner.TaskStatus); + Assert.IsFalse(runner.IsRunning); + Assert.IsFalse(runner.IsDisposed); + } + } + + [Test] + public void WaitOnTask() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + var task = new MyTask(); + var waitHandle = new ManualResetEvent(false); + runner.TaskCompleted += (sender, t) => waitHandle.Set(); + Assert.IsTrue(task.Ended == default(DateTime)); + runner.Add(task); + waitHandle.WaitOne(); // wait 'til task is done + Assert.IsTrue(task.Ended != default(DateTime)); // task is done + AssertRunnerStopsRunning(runner); // though not for long + } + } + + [Test] + public void WaitOnTasks() { var tasks = new Dictionary(); for (var i = 0; i < 10; i++) - { tasks.Add(new MyTask(), new ManualResetEvent(false)); - } - BackgroundTaskRunner tManager; - using (tManager = new BackgroundTaskRunner(true, true)) + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) { - tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set(); + runner.TaskCompleted += (sender, task) => tasks[task.Task].Set(); + foreach (var t in tasks) runner.Add(t.Key); - tasks.ForEach(t => tManager.Add(t.Key)); - - //wait for all ITasks to complete + // wait 'til tasks are done, check that tasks are done WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray()); + Assert.IsTrue(tasks.All(x => x.Key.Ended != default(DateTime))); - foreach (var task in tasks) - { - NUnit.Framework.Assert.IsTrue(task.Key.Ended != default(DateTime)); - } + AssertRunnerStopsRunning(runner); // though not for long } } @@ -99,7 +298,7 @@ namespace Umbraco.Tests.Scheduling IDictionary tasks = getTasks(); BackgroundTaskRunner tManager; - using (tManager = new BackgroundTaskRunner(true, true)) + using (tManager = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { LongRunning = true, KeepAlive = true })) { tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set(); @@ -111,7 +310,7 @@ namespace Umbraco.Tests.Scheduling foreach (var task in tasks) { - NUnit.Framework.Assert.IsTrue(task.Key.Ended != default(DateTime)); + Assert.IsTrue(task.Key.Ended != default(DateTime)); } //execute another batch after a bit @@ -125,71 +324,11 @@ namespace Umbraco.Tests.Scheduling foreach (var task in tasks) { - NUnit.Framework.Assert.IsTrue(task.Key.Ended != default(DateTime)); + Assert.IsTrue(task.Key.Ended != default(DateTime)); } } } - [Test] - public void Task_Queue_Will_Be_Completed_Before_Shutdown() - { - var tasks = new Dictionary(); - for (var i = 0; i < 10; i++) - { - tasks.Add(new MyTask(), new ManualResetEvent(false)); - } - - BackgroundTaskRunner tManager; - using (tManager = new BackgroundTaskRunner(true, true)) - { - tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set(); - - tasks.ForEach(t => tManager.Add(t.Key)); - - ////wait for all ITasks to complete - //WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray()); - - tManager.Stop(false); - //immediate stop will block until complete - but since we are running on - // a single thread this doesn't really matter as the above will just process - // until complete. - tManager.Stop(true); - - NUnit.Framework.Assert.AreEqual(0, tManager.TaskCount); - } - } - - //NOTE: These tests work in .Net 4.5 but in this current version we don't have the correct - // async/await signatures with GetAwaiter, so am just commenting these out in this version - - [Test] - public async void Non_Persistent_Runner_Will_End_After_Queue_Empty() - { - var tasks = new List(); - for (var i = 0; i < 10; i++) - { - tasks.Add(new MyTask()); - } - - BackgroundTaskRunner tManager; - using (tManager = new BackgroundTaskRunner(persistentThread: false, dedicatedThread:true)) - { - tasks.ForEach(t => tManager.Add(t)); - - //wait till the thread is done - await tManager; - - foreach (var task in tasks) - { - Assert.IsTrue(task.Ended != default(DateTime)); - } - - Assert.AreEqual(TaskStatus.RanToCompletion, tManager.TaskStatus); - Assert.IsFalse(tManager.IsRunning); - Assert.IsFalse(tManager.IsDisposed); - } - } - [Test] public async void Non_Persistent_Runner_Will_Start_New_Threads_When_Required() { @@ -205,10 +344,9 @@ namespace Umbraco.Tests.Scheduling List tasks = getTasks(); - BackgroundTaskRunner tManager; - using (tManager = new BackgroundTaskRunner(persistentThread: false, dedicatedThread: true)) + using (var tManager = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { LongRunning = true, PreserveRunningTask = true })) { - tasks.ForEach(t => tManager.Add(t)); + tasks.ForEach(tManager.Add); //wait till the thread is done await tManager; @@ -226,7 +364,7 @@ namespace Umbraco.Tests.Scheduling tasks = getTasks(); //add more tasks - tasks.ForEach(t => tManager.Add(t)); + tasks.ForEach(tManager.Add); //wait till the thread is done await tManager; @@ -241,41 +379,339 @@ namespace Umbraco.Tests.Scheduling Assert.IsFalse(tManager.IsDisposed); } } - + + [Test] + public void RecurringTaskTest() + { + // note: can have BackgroundTaskRunner and use it in MyRecurringTask ctor + // because that ctor wants IBackgroundTaskRunner and the generic type + // parameter is contravariant ie defined as IBackgroundTaskRunner so doing the + // following is legal: + // var IBackgroundTaskRunner b = ...; + // var IBackgroundTaskRunner d = b; // legal + + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + var task = new MyRecurringTask(runner, 200, 500); + MyRecurringTask.RunCount = 0; + runner.Add(task); + Thread.Sleep(5000); + Assert.GreaterOrEqual(MyRecurringTask.RunCount, 2); // keeps running, count >= 2 + + // stops recurring + runner.Shutdown(false, false); + AssertRunnerStopsRunning(runner); + + // timer may try to add a task but it won't work because runner is completed + } + } + + [Test] + public void DelayedTaskRuns() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + var task = new MyDelayedTask(200); + runner.Add(task); + Assert.IsTrue(runner.IsRunning); + Thread.Sleep(5000); + Assert.IsTrue(runner.IsRunning); // still waiting for the task to release + Assert.IsFalse(task.HasRun); + task.Release(); + Thread.Sleep(500); + Assert.IsTrue(task.HasRun); + AssertRunnerStopsRunning(runner); // runs task & exit + } + } + + [Test] + public void DelayedTaskStops() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + var task = new MyDelayedTask(200); + runner.Add(task); + Assert.IsTrue(runner.IsRunning); + Thread.Sleep(5000); + Assert.IsTrue(runner.IsRunning); // still waiting for the task to release + Assert.IsFalse(task.HasRun); + runner.Shutdown(false, false); + AssertRunnerStopsRunning(runner); // runs task & exit + Assert.IsTrue(task.HasRun); + } + } + + [Test] + public void DelayedRecurring() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + var task = new MyDelayedRecurringTask(runner, 2000, 1000); + MyDelayedRecurringTask.RunCount = 0; + runner.Add(task); + Thread.Sleep(1000); + Assert.IsTrue(runner.IsRunning); // waiting on delay + Assert.AreEqual(0, MyDelayedRecurringTask.RunCount); + Thread.Sleep(1000); + Assert.AreEqual(1, MyDelayedRecurringTask.RunCount); + Thread.Sleep(5000); + Assert.GreaterOrEqual(MyDelayedRecurringTask.RunCount, 2); // keeps running, count >= 2 + + // stops recurring + runner.Shutdown(false, false); + AssertRunnerStopsRunning(runner); + + // timer may try to add a task but it won't work because runner is completed + } + } + + [Test] + public void FailingTaskSync() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + var exceptions = new ConcurrentQueue(); + runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception); + + var task = new MyFailingTask(false); // -async + runner.Add(task); + Assert.IsTrue(runner.IsRunning); + AssertRunnerStopsRunning(runner); // runs task & exit + + Assert.AreEqual(1, exceptions.Count); // traced and reported + } + } + + [Test] + public void FailingTaskAsync() + { + using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions())) + { + var exceptions = new ConcurrentQueue(); + runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception); + + var task = new MyFailingTask(true); // +async + runner.Add(task); + Assert.IsTrue(runner.IsRunning); + AssertRunnerStopsRunning(runner); // runs task & exit + + Assert.AreEqual(1, exceptions.Count); // traced and reported + } + } + + private class MyFailingTask : IBackgroundTask + { + private readonly bool _isAsync; + + public MyFailingTask(bool isAsync) + { + _isAsync = isAsync; + } + + public void Run() + { + Thread.Sleep(1000); + throw new Exception("Task has thrown."); + } + + public async Task RunAsync(CancellationToken token) + { + await Task.Delay(1000); + throw new Exception("Task has thrown."); + } + + public bool IsAsync + { + get { return _isAsync; } + } + + // fixme - must also test what happens if we throw on dispose! + public void Dispose() + { } + } + + private class MyDelayedRecurringTask : DelayedRecurringTaskBase + { + public MyDelayedRecurringTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds) + : base(runner, delayMilliseconds, periodMilliseconds) + { } + + private MyDelayedRecurringTask(MyDelayedRecurringTask source) + : base(source) + { } + + public static int RunCount { get; set; } + + public override bool IsAsync + { + get { return false; } + } + + public override void PerformRun() + { + // nothing to do at the moment + RunCount += 1; + } + + public override Task PerformRunAsync() + { + throw new NotImplementedException(); + } + + protected override MyDelayedRecurringTask GetRecurring() + { + return new MyDelayedRecurringTask(this); + } + } + + private class MyDelayedTask : ILatchedBackgroundTask + { + private readonly int _runMilliseconds; + private readonly ManualResetEvent _gate; + + public bool HasRun { get; private set; } + + public MyDelayedTask(int runMilliseconds) + { + _runMilliseconds = runMilliseconds; + _gate = new ManualResetEvent(false); + } + + public WaitHandle Latch + { + get { return _gate; } + } + + public bool IsLatched + { + get { return true; } + } + + public bool RunsOnShutdown + { + get { return true; } + } + + public void Run() + { + Thread.Sleep(_runMilliseconds); + HasRun = true; + } + + public void Release() + { + _gate.Set(); + } + + public Task RunAsync(CancellationToken token) + { + throw new NotImplementedException(); + } + + public bool IsAsync + { + get { return false; } + } + + public void Dispose() + { } + } + + private class MyRecurringTask : RecurringTaskBase + { + private readonly int _runMilliseconds; + + public static int RunCount { get; set; } + + public MyRecurringTask(IBackgroundTaskRunner runner, int runMilliseconds, int periodMilliseconds) + : base(runner, periodMilliseconds) + { + _runMilliseconds = runMilliseconds; + } + + private MyRecurringTask(MyRecurringTask source, int runMilliseconds) + : base(source) + { + _runMilliseconds = runMilliseconds; + } + + public override void PerformRun() + { + RunCount += 1; + Thread.Sleep(_runMilliseconds); + } + + public override Task PerformRunAsync() + { + throw new NotImplementedException(); + } + + public override bool IsAsync + { + get { return false; } + } + + protected override MyRecurringTask GetRecurring() + { + return new MyRecurringTask(this, _runMilliseconds); + } + } private class MyTask : BaseTask { + private readonly int _milliseconds; + public MyTask() + : this(500) + { } + + public MyTask(int milliseconds) { + _milliseconds = milliseconds; } - public override void Run() + public override void PerformRun() { - Thread.Sleep(500); - } - - public override void Cancel() - { - + Thread.Sleep(_milliseconds); } } public abstract class BaseTask : IBackgroundTask { + public bool WasCancelled { get; set; } + public Guid UniqueId { get; protected set; } - public abstract void Run(); - public abstract void Cancel(); + public abstract void PerformRun(); + + public void Run() + { + PerformRun(); + Ended = DateTime.Now; + } + + public Task RunAsync(CancellationToken token) + { + throw new NotImplementedException(); + //return Task.Delay(500); + } + + public bool IsAsync + { + get { return false; } + } + + public virtual void Cancel() + { + WasCancelled = true; + } public DateTime Queued { get; set; } public DateTime Started { get; set; } public DateTime Ended { get; set; } public virtual void Dispose() - { - Ended = DateTime.Now; - } + { } } - } } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 0d22d9958c..bda140335f 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1407,6 +1407,100 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Created_HasPublishedVersion_Not() + { + var contentService = ServiceContext.ContentService; + var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0); + content.SetValue("author", "Barack Obama"); + + contentService.Save(content); + + Assert.IsTrue(content.HasIdentity); + Assert.IsFalse(content.HasPublishedVersion); + + content = contentService.GetById(content.Id); + Assert.IsFalse(content.HasPublishedVersion); + } + + [Test] + public void Published_HasPublishedVersion_Self() + { + var contentService = ServiceContext.ContentService; + var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0); + content.SetValue("author", "Barack Obama"); + + contentService.SaveAndPublishWithStatus(content); + + Assert.IsTrue(content.HasIdentity); + Assert.IsTrue(content.HasPublishedVersion); + Assert.AreEqual(content.PublishedVersionGuid, content.Version); + + content = contentService.GetById(content.Id); + Assert.IsTrue(content.HasPublishedVersion); + Assert.AreEqual(content.PublishedVersionGuid, content.Version); + } + + [Test] + public void PublishedWithChanges_HasPublishedVersion_Other() + { + var contentService = ServiceContext.ContentService; + var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0); + content.SetValue("author", "Barack Obama"); + + contentService.SaveAndPublishWithStatus(content); + content.SetValue("author", "James Dean"); + contentService.Save(content); + + Assert.IsTrue(content.HasIdentity); + Assert.IsTrue(content.HasPublishedVersion); + Assert.AreNotEqual(content.PublishedVersionGuid, content.Version); + + content = contentService.GetById(content.Id); + Assert.IsFalse(content.Published); + Assert.IsTrue(content.HasPublishedVersion); + Assert.AreNotEqual(content.PublishedVersionGuid, content.Version); + + var published = contentService.GetPublishedVersion(content); + Assert.IsTrue(published.Published); + Assert.IsTrue(published.HasPublishedVersion); + Assert.AreEqual(published.PublishedVersionGuid, published.Version); + } + + [Test] + public void Unpublished_HasPublishedVersion_Not() + { + var contentService = ServiceContext.ContentService; + var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0); + content.SetValue("author", "Barack Obama"); + + contentService.SaveAndPublishWithStatus(content); + contentService.UnPublish(content); + + Assert.IsTrue(content.HasIdentity); + Assert.IsFalse(content.HasPublishedVersion); + + content = contentService.GetById(content.Id); + Assert.IsFalse(content.HasPublishedVersion); + } + + [Test] + public void HasPublishedVersion_Method() + { + var contentService = ServiceContext.ContentService; + var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0); + content.SetValue("author", "Barack Obama"); + + contentService.Save(content); + Assert.IsTrue(content.HasIdentity); + Assert.IsFalse(content.HasPublishedVersion); + Assert.IsFalse(content.HasPublishedVersion()); + + contentService.SaveAndPublishWithStatus(content); + Assert.IsTrue(content.HasPublishedVersion); + Assert.IsTrue(content.HasPublishedVersion()); + } + private IEnumerable CreateContentHierarchy() { var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index b4a6e41e64..753b4aa84a 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -489,7 +489,7 @@ namespace Umbraco.Tests.Services // Act var duplicatePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var added = composition.AddPropertyType(duplicatePropertyType, "Meta"); @@ -525,7 +525,7 @@ namespace Umbraco.Tests.Services // Act var duplicatePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var addedToBasePage = basePage.AddPropertyType(duplicatePropertyType, "Content"); var addedToAdvancedPage = advancedPage.AddPropertyType(duplicatePropertyType, "Content"); @@ -579,14 +579,14 @@ namespace Umbraco.Tests.Services // Act var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); service.Save(basePage); var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "author", Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); service.Save(contentPage); @@ -597,7 +597,7 @@ namespace Umbraco.Tests.Services //NOTE: It should not be possible to Save 'BasePage' with the Title PropertyType added var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var titleAdded = basePage.AddPropertyType(titlePropertyType, "Content"); @@ -644,28 +644,28 @@ namespace Umbraco.Tests.Services // Act var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); service.Save(basePage); var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "author", Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); service.Save(contentPage); var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "subtitle", Name = "Subtitle", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var subtitleAdded = advancedPage.AddPropertyType(subtitlePropertyType, "Content"); service.Save(advancedPage); var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var titleAdded = seoComposition.AddPropertyType(titlePropertyType, "Content"); service.Save(seoComposition); @@ -724,28 +724,28 @@ namespace Umbraco.Tests.Services // Act var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); service.Save(basePage); var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "author", Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); service.Save(contentPage); var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "subtitle", Name = "Subtitle", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var subtitleAdded = advancedPage.AddPropertyType(subtitlePropertyType, "Content"); service.Save(advancedPage); var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var titleAdded = seoComposition.AddPropertyType(titlePropertyType, "Content"); service.Save(seoComposition); @@ -765,7 +765,7 @@ namespace Umbraco.Tests.Services var testPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "test", Name = "Test", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "test", Name = "Test", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var testAdded = seoComposition.AddPropertyType(testPropertyType, "Content"); service.Save(seoComposition); @@ -795,11 +795,11 @@ namespace Umbraco.Tests.Services // Act var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "subtitle", Name = "Subtitle", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "author", Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content"); var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); @@ -841,22 +841,22 @@ namespace Umbraco.Tests.Services // Act var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var titleAdded = basePage.AddPropertyType(titlePropertyType, "Content"); var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var bodyTextAdded = contentPage.AddPropertyType(bodyTextPropertyType, "Content"); var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "subtitle", Name = "Subtitle", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content"); var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "author", Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorAdded = advancedPage.AddPropertyType(authorPropertyType, "Content"); service.Save(basePage); @@ -914,7 +914,7 @@ namespace Umbraco.Tests.Services // Act var propertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var addedToContentPage = contentPage.AddPropertyType(propertyType, "Content"); @@ -948,12 +948,12 @@ namespace Umbraco.Tests.Services // Act var propertyTypeOne = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "testTextbox", Name = "Test Textbox", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "testTextbox", Name = "Test Textbox", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var firstOneAdded = contentPage.AddPropertyType(propertyTypeOne, "Content_"); var propertyTypeTwo = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "anotherTextbox", Name = "Another Test Textbox", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "anotherTextbox", Name = "Another Test Textbox", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var secondOneAdded = contentPage.AddPropertyType(propertyTypeTwo, "Content"); service.Save(contentPage); @@ -999,11 +999,11 @@ namespace Umbraco.Tests.Services // Act var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "subtitle", Name = "Subtitle", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var bodyTextAdded = contentPage.AddPropertyType(bodyTextPropertyType, "Content_");//Will be added to the parent tab var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content");//Will be added to the "Content Meta" composition @@ -1011,15 +1011,15 @@ namespace Umbraco.Tests.Services var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "author", Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var descriptionPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "description", Name = "Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "description", Name = "Description", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var keywordsPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "keywords", Name = "Keywords", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "keywords", Name = "Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorAdded = advancedPage.AddPropertyType(authorPropertyType, "Content_");//Will be added to an ancestor tab var descriptionAdded = advancedPage.AddPropertyType(descriptionPropertyType, "Contentx");//Will be added to a parent tab @@ -1066,15 +1066,15 @@ namespace Umbraco.Tests.Services // Act var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "subtitle", Name = "Subtitle", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "author", Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var bodyTextAdded = page.AddPropertyType(bodyTextPropertyType, "Content_"); var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content"); @@ -1119,14 +1119,14 @@ namespace Umbraco.Tests.Services // Act var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); service.Save(basePage); var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "author", Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); service.Save(contentPage); @@ -1177,14 +1177,14 @@ namespace Umbraco.Tests.Services // Act var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "author", Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); service.Save(contentPage); var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); service.Save(basePage); @@ -1211,7 +1211,7 @@ namespace Umbraco.Tests.Services var descriptionPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { - Alias = "description", Name = "Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 1,DataTypeDefinitionId = -88 + Alias = "description", Name = "Description", Description = "", Mandatory = false, SortOrder = 1,DataTypeDefinitionId = -88 }; var descriptionAdded = contentType.AddPropertyType(descriptionPropertyType, "Content"); service.Save(contentType); @@ -1242,7 +1242,7 @@ namespace Umbraco.Tests.Services }; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "componentGroup", Name = "Component Group", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "componentGroup", Name = "Component Group", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); component.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Component", SortOrder = 1 }); return component; @@ -1291,7 +1291,7 @@ namespace Umbraco.Tests.Services }; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "hostname", Name = "Hostname", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "hostname", Name = "Hostname", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); site.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Site Settings", SortOrder = 1 }); return site; @@ -1313,9 +1313,9 @@ namespace Umbraco.Tests.Services }; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); diff --git a/src/Umbraco.Tests/TestHelpers/DisposableUmbracoTest.cs b/src/Umbraco.Tests/TestHelpers/DisposableUmbracoTest.cs deleted file mode 100644 index d261487449..0000000000 --- a/src/Umbraco.Tests/TestHelpers/DisposableUmbracoTest.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Web; -using Moq; -using Umbraco.Core; -using Umbraco.Core.ObjectResolution; -using Umbraco.Web; - -namespace Umbraco.Tests.TestHelpers -{ - //NOTE: This is just a POC! Looking at the simplest way to expose some code so people can very easily test - // their Umbraco controllers, etc.... - public class DisposableUmbracoTest : DisposableObject - { - public ApplicationContext ApplicationContext { get; set; } - public UmbracoContext UmbracoContext { get; set; } - - public DisposableUmbracoTest(ApplicationContext applicationContext) - { - //init umb context - var umbctx = UmbracoContext.EnsureContext( - new Mock().Object, - applicationContext, - true); - - Init(applicationContext, umbctx); - } - - public DisposableUmbracoTest(ApplicationContext applicationContext, UmbracoContext umbracoContext) - { - Init(applicationContext, umbracoContext); - } - - private void Init(ApplicationContext applicationContext, UmbracoContext umbracoContext) - { - ApplicationContext = applicationContext; - UmbracoContext = umbracoContext; - - ApplicationContext.Current = applicationContext; - UmbracoContext.Current = umbracoContext; - - Resolution.Freeze(); - } - - protected override void DisposeResources() - { - ApplicationContext.Current = null; - UmbracoContext.Current = null; - Resolution.Reset(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index 39916453dc..1fb7f4604d 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -41,12 +41,12 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); var metaCollection = new PropertyTypeCollection(); - metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -89 }); + metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -89 }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); @@ -72,8 +72,8 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var metaCollection = new PropertyTypeCollection(); - metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metakeywords", Name = "Meta Keywords", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metadescription", Name = "Meta Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -89 }); + metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metakeywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metadescription", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -89 }); contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); @@ -98,7 +98,7 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var metaCollection = new PropertyTypeCollection(); - metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Content", SortOrder = 2 }); @@ -123,8 +123,8 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var metaCollection = new PropertyTypeCollection(); - metaCollection.Add(new PropertyType("seotest", DataTypeDatabaseType.Ntext) { Alias = "seokeywords", Name = "Seo Keywords", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - metaCollection.Add(new PropertyType("seotest", DataTypeDatabaseType.Ntext) { Alias = "seodescription", Name = "Seo Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -89 }); + metaCollection.Add(new PropertyType("seotest", DataTypeDatabaseType.Ntext) { Alias = "seokeywords", Name = "Seo Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + metaCollection.Add(new PropertyType("seotest", DataTypeDatabaseType.Ntext) { Alias = "seodescription", Name = "Seo Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -89 }); contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Seo", SortOrder = 5 }); @@ -149,9 +149,9 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); @@ -175,9 +175,9 @@ namespace Umbraco.Tests.TestHelpers.Entities contentType.Trashed = false; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.TinyMCEAlias, DataTypeDatabaseType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = RandomAlias("author", randomizeAliases) , Name = "Author", Description = "Name of the author", HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.TinyMCEAlias, DataTypeDatabaseType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = RandomAlias("author", randomizeAliases) , Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = propertyGroupName, SortOrder = 1 }); @@ -202,9 +202,9 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = mandatory, SortOrder = 1, DataTypeDefinitionId = -88 }); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = mandatory, SortOrder = 2, DataTypeDefinitionId = -87 }); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", HelpText = "", Mandatory = mandatory, SortOrder = 3, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = mandatory, SortOrder = 1, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = mandatory, SortOrder = 2, DataTypeDefinitionId = -87 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = mandatory, SortOrder = 3, DataTypeDefinitionId = -88 }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); @@ -330,8 +330,8 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Nvarchar) { Alias = "videoFile", Name = "Video File", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType("test", DataTypeDatabaseType.Nvarchar) { Alias = "videoFile", Name = "Video File", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); @@ -356,11 +356,11 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.UploadFieldAlias, DataTypeDatabaseType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.UploadFieldAlias, DataTypeDatabaseType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index f82bdf3b65..95fc96dea3 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -177,6 +177,7 @@ + @@ -463,7 +464,6 @@ - diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx index 196e9de51e..0a424f506f 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx @@ -148,7 +148,7 @@ - +
<%=umbraco.ui.Text("paLoginPageHelp")%> @@ -159,7 +159,7 @@ - +
<%=umbraco.ui.Text("paErrorPageHelp")%> diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index a7ec43b61d..6fe1105a02 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -115,17 +115,15 @@ namespace Umbraco.Web.Cache ContentService.EmptiedRecycleBin += ContentServiceEmptiedRecycleBin; //public access events - Access.AfterSave += Access_AfterSave; + PublicAccessService.Saved += PublicAccessService_Saved; } - - #region Public access event handlers - static void Access_AfterSave(Access sender, SaveEventArgs e) + static void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs e) { DistributedCache.Instance.RefreshPublicAccess(); - } + } #endregion diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index 8de8440f2a..560cc2e484 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -19,11 +19,10 @@ namespace Umbraco.Web.Cache public static void RefreshPublicAccess(this DistributedCache dc) { - dc.RefreshByJson(new Guid(DistributedCache.PublicAccessCacheRefresherId), - PublicAccessCacheRefresher.SerializeToJsonPayload( - Access.GetXmlDocumentCopy())); + dc.RefreshAll(new Guid(DistributedCache.PublicAccessCacheRefresherId)); } + #endregion #region Application tree cache diff --git a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs index 11f8291e6c..aabe95548f 100644 --- a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; @@ -40,6 +41,8 @@ namespace Umbraco.Web.Cache private void ClearCache() { + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + // SD: we need to clear the routes cache here! // // zpqrtbnk: no, not here, in fact the caches should subsribe to refresh events else we diff --git a/src/Umbraco.Web/Cache/PageCacheRefresher.cs b/src/Umbraco.Web/Cache/PageCacheRefresher.cs index ca20d5c2de..73f525383e 100644 --- a/src/Umbraco.Web/Cache/PageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PageCacheRefresher.cs @@ -76,6 +76,7 @@ namespace Umbraco.Web.Cache content.Instance.ClearDocumentCache(id); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); base.Remove(id); } @@ -85,6 +86,7 @@ namespace Umbraco.Web.Cache content.Instance.UpdateDocumentCache(new Document(instance)); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); base.Refresh(instance); } @@ -94,6 +96,7 @@ namespace Umbraco.Web.Cache content.Instance.ClearDocumentCache(new Document(instance)); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); base.Remove(instance); } } diff --git a/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs b/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs index 0c700e9dfa..09b38f1ac3 100644 --- a/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs @@ -4,45 +4,12 @@ using Newtonsoft.Json; using umbraco.cms.businesslogic.web; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Models; namespace Umbraco.Web.Cache { - public sealed class PublicAccessCacheRefresher : JsonCacheRefresherBase + public sealed class PublicAccessCacheRefresher : CacheRefresherBase { - #region Static helpers - - internal static JsonPayload DeserializeFromJsonPayload(string json) - { - return JsonConvert.DeserializeObject(json); - } - - internal static string SerializeToJsonPayload(XmlDocument doc) - { - return JsonConvert.SerializeObject(FromXml(doc)); - } - - internal static JsonPayload FromXml(XmlDocument doc) - { - if (doc == null) return null; - - var payload = new JsonPayload - { - XmlContent = doc.OuterXml - }; - return payload; - } - - #endregion - - #region Sub classes - - internal class JsonPayload - { - public string XmlContent { get; set; } - } - - #endregion - protected override PublicAccessCacheRefresher Instance { get { return this; } @@ -58,20 +25,28 @@ namespace Umbraco.Web.Cache get { return "Public access cache refresher"; } } - public override void Refresh(string jsonPayload) + public override void Refresh(Guid id) { - if (jsonPayload.IsNullOrWhiteSpace()) return; - var deserialized = DeserializeFromJsonPayload(jsonPayload); - if (deserialized == null) return; - var xDoc = new XmlDocument(); - xDoc.LoadXml(deserialized.XmlContent); - ClearCache(xDoc); - base.Refresh(jsonPayload); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + base.Refresh(id); } - private void ClearCache(XmlDocument xDoc) + public override void Refresh(int id) { - Access.UpdateInMemoryDocument(xDoc); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + base.Refresh(id); + } + + public override void RefreshAll() + { + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + base.RefreshAll(); + } + + public override void Remove(int id) + { + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + base.Remove(id); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs index 5ef1fbf468..6270abe7e6 100644 --- a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs @@ -55,9 +55,6 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem( string.Format("{0}{1}", CacheKeys.TemplateFrontEndCacheKey, id)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem( - string.Format("{0}{1}", CacheKeys.TemplateBusinessLogicCacheKey, id)); - //need to clear the runtime cache for template instances //NOTE: This is temp until we implement the correct ApplicationCache and then we can remove the RuntimeCache, etc... ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); diff --git a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs index 1cf661a78f..47a2d73bdb 100644 --- a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs @@ -78,12 +78,14 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); base.RefreshAll(); } public override void Refresh(int id) { ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); content.Instance.UpdateSortOrder(id); base.Refresh(id); } @@ -91,6 +93,7 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); base.Remove(id); } @@ -98,6 +101,7 @@ namespace Umbraco.Web.Cache public override void Refresh(IContent instance) { ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(instance.Id)); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); content.Instance.UpdateSortOrder(instance); base.Refresh(instance); } @@ -105,6 +109,7 @@ namespace Umbraco.Web.Cache public override void Remove(IContent instance) { ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(instance.Id)); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); base.Remove(instance); } @@ -114,6 +119,8 @@ namespace Umbraco.Web.Cache /// public void Refresh(string jsonPayload) { + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + foreach (var payload in DeserializeFromJsonPayload(jsonPayload)) { ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Id)); diff --git a/src/Umbraco.Web/CanvasDesignerUtility.cs b/src/Umbraco.Web/CanvasDesignerUtility.cs index 722b7247c9..d4ad46a4aa 100644 --- a/src/Umbraco.Web/CanvasDesignerUtility.cs +++ b/src/Umbraco.Web/CanvasDesignerUtility.cs @@ -20,6 +20,11 @@ using System.Text.RegularExpressions; namespace Umbraco.Web { + //TODO: This class needs to be overhauled: + // No statics that have dependencies! + // Make into a real class that accept the dependencies required. + // Remove all usages of singletons: HttpContext.Current, ApplicationContext.Current, UmbracoContext.Current, etc... + internal static class CanvasDesignerUtility { diff --git a/src/Umbraco.Web/Editors/MacroController.cs b/src/Umbraco.Web/Editors/MacroController.cs index 7b989bf674..11fef4a25d 100644 --- a/src/Umbraco.Web/Editors/MacroController.cs +++ b/src/Umbraco.Web/Editors/MacroController.cs @@ -86,11 +86,13 @@ namespace Umbraco.Web.Editors UmbracoContext.HttpContext.Items["pageElements"] = legacyPage.Elements; UmbracoContext.HttpContext.Items[global::Umbraco.Core.Constants.Conventions.Url.AltTemplate] = null; + var renderer = new UmbracoComponentRenderer(UmbracoContext); + var result = Request.CreateResponse(); //need to create a specific content result formatted as html since this controller has been configured //with only json formatters. result.Content = new StringContent( - Umbraco.RenderMacro(macro, macroParams, legacyPage).ToString(), + renderer.RenderMacro(macro, macroParams, legacyPage).ToString(), Encoding.UTF8, "text/html"); return result; diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs new file mode 100644 index 0000000000..5ba1d17f4e --- /dev/null +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Web; +using HtmlAgilityPack; + +namespace Umbraco.Web +{ + /// + /// Utility class for working with strings and HTML in views + /// + /// + /// The UmbracoHelper uses this class for it's string methods + /// + public sealed class HtmlStringUtilities + { + /// + /// Replaces text line breaks with html line breaks + /// + /// The text. + /// The text with text line breaks replaced with html linebreaks (
)
+ public string ReplaceLineBreaksForHtml(string text) + { + return text.Replace("\n", "
\n"); + } + + public HtmlString StripHtmlTags(string html, params string[] tags) + { + var doc = new HtmlDocument(); + doc.LoadHtml("

" + html + "

"); + var targets = new List(); + + var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*"); + if (nodes != null) + { + foreach (var node in nodes) + { + //is element + if (node.NodeType != HtmlNodeType.Element) continue; + var filterAllTags = (tags == null || !tags.Any()); + if (filterAllTags || tags.Any(tag => string.Equals(tag, node.Name, StringComparison.CurrentCultureIgnoreCase))) + { + targets.Add(node); + } + } + foreach (var target in targets) + { + HtmlNode content = doc.CreateTextNode(target.InnerText); + target.ParentNode.ReplaceChild(content, target); + } + } + else + { + return new HtmlString(html); + } + return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml); + } + + internal string Join(string seperator, params object[] args) + { + var results = args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg)).ToList(); + return string.Join(seperator, results); + } + + internal string Concatenate(params object[] args) + { + var result = new StringBuilder(); + foreach (var sArg in args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg))) + { + result.Append(sArg); + } + return result.ToString(); + } + + internal string Coalesce(params object[] args) + { + foreach (var sArg in args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg))) + { + return sArg; + } + return string.Empty; + } + + public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) + { + using (var outputms = new MemoryStream()) + { + using (var outputtw = new StreamWriter(outputms)) + { + using (var ms = new MemoryStream()) + { + using (var tw = new StreamWriter(ms)) + { + tw.Write(html); + tw.Flush(); + ms.Position = 0; + var tagStack = new Stack(); + + using (TextReader tr = new StreamReader(ms)) + { + bool isInsideElement = false, + lengthReached = false, + insideTagSpaceEncountered = false, + isTagClose = false; + + int ic = 0, + currentLength = 0, + currentTextLength = 0; + + string currentTag = string.Empty, + tagContents = string.Empty; + + while ((ic = tr.Read()) != -1) + { + bool write = true; + + switch ((char)ic) + { + case '<': + if (!lengthReached) + { + isInsideElement = true; + } + + insideTagSpaceEncountered = false; + currentTag = string.Empty; + tagContents = string.Empty; + isTagClose = false; + if (tr.Peek() == (int)'/') + { + isTagClose = true; + } + break; + + case '>': + isInsideElement = false; + + if (isTagClose && tagStack.Count > 0) + { + string thisTag = tagStack.Pop(); + outputtw.Write(""); + } + if (!isTagClose && currentTag.Length > 0) + { + if (!lengthReached) + { + tagStack.Push(currentTag); + outputtw.Write("<" + currentTag); + if (!string.IsNullOrEmpty(tagContents)) + { + if (tagContents.EndsWith("/")) + { + // No end tag e.g.
. + tagStack.Pop(); + } + + outputtw.Write(tagContents); + write = true; + insideTagSpaceEncountered = false; + } + outputtw.Write(">"); + } + } + // Continue to next iteration of the text reader. + continue; + + default: + if (isInsideElement) + { + if (ic == (int)' ') + { + if (!insideTagSpaceEncountered) + { + insideTagSpaceEncountered = true; + } + } + + if (!insideTagSpaceEncountered) + { + currentTag += (char)ic; + } + } + break; + } + + if (isInsideElement || insideTagSpaceEncountered) + { + write = false; + if (insideTagSpaceEncountered) + { + tagContents += (char)ic; + } + } + + if (!isInsideElement || treatTagsAsContent) + { + currentTextLength++; + } + + if (currentTextLength <= length || (lengthReached && isInsideElement)) + { + if (write) + { + var charToWrite = (char)ic; + outputtw.Write(charToWrite); + currentLength++; + } + } + + if (!lengthReached && currentTextLength >= length) + { + // Reached truncate limit. + if (addElipsis) + { + outputtw.Write("…"); + } + lengthReached = true; + } + + } + + } + } + } + outputtw.Flush(); + outputms.Position = 0; + using (TextReader outputtr = new StreamReader(outputms)) + { + return new HtmlString(outputtr.ReadToEnd().Replace(" ", " ").Trim()); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/IDynamicPublishedContentQuery.cs b/src/Umbraco.Web/IDynamicPublishedContentQuery.cs new file mode 100644 index 0000000000..051ba07ff6 --- /dev/null +++ b/src/Umbraco.Web/IDynamicPublishedContentQuery.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Xml.XPath; +using Umbraco.Core.Xml; + +namespace Umbraco.Web +{ + /// + /// Query methods used for accessing content dynamically in templates + /// + public interface IDynamicPublishedContentQuery + { + dynamic Content(int id); + dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars); + dynamic ContentSingleAtXPath(XPathExpression xpath, params XPathVariable[] vars); + dynamic Content(IEnumerable ids); + dynamic ContentAtXPath(string xpath, params XPathVariable[] vars); + dynamic ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); + dynamic ContentAtRoot(); + + dynamic Media(int id); + dynamic Media(IEnumerable ids); + dynamic MediaAtRoot(); + + /// + /// Searches content + /// + /// + /// + /// + /// + dynamic Search(string term, bool useWildCards = true, string searchProvider = null); + + /// + /// Searhes content + /// + /// + /// + /// + dynamic Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/ITagQuery.cs b/src/Umbraco.Web/ITagQuery.cs new file mode 100644 index 0000000000..48098e1904 --- /dev/null +++ b/src/Umbraco.Web/ITagQuery.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Web.Models; + +namespace Umbraco.Web +{ + public interface ITagQuery + { + /// + /// Returns all content that is tagged with the specified tag value and optional tag group + /// + /// + /// + /// + IEnumerable GetContentByTag(string tag, string tagGroup = null); + + /// + /// Returns all content that has been tagged with any tag in the specified group + /// + /// + /// + IEnumerable GetContentByTagGroup(string tagGroup); + + /// + /// Returns all Media that is tagged with the specified tag value and optional tag group + /// + /// + /// + /// + IEnumerable GetMediaByTag(string tag, string tagGroup = null); + + /// + /// Returns all Media that has been tagged with any tag in the specified group + /// + /// + /// + IEnumerable GetMediaByTagGroup(string tagGroup); + + /// + /// Get every tag stored in the database (with optional group) + /// + IEnumerable GetAllTags(string group = null); + + /// + /// Get all tags for content items (with optional group) + /// + /// + /// + IEnumerable GetAllContentTags(string group = null); + + /// + /// Get all tags for media items (with optional group) + /// + /// + /// + IEnumerable GetAllMediaTags(string group = null); + + /// + /// Get all tags for member items (with optional group) + /// + /// + /// + IEnumerable GetAllMemberTags(string group = null); + + /// + /// Returns all tags attached to a property by entity id + /// + /// + /// + /// + /// + IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null); + + /// + /// Returns all tags attached to an entity (content, media or member) by entity id + /// + /// + /// + /// + IEnumerable GetTagsForEntity(int contentId, string tagGroup = null); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/ITypedPublishedContentQuery.cs b/src/Umbraco.Web/ITypedPublishedContentQuery.cs new file mode 100644 index 0000000000..aefe243a0b --- /dev/null +++ b/src/Umbraco.Web/ITypedPublishedContentQuery.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Xml.XPath; +using Umbraco.Core.Models; +using Umbraco.Core.Xml; + +namespace Umbraco.Web +{ + /// + /// Query methods used for accessing strongly typed content in templates + /// + public interface ITypedPublishedContentQuery + { + IPublishedContent TypedContent(int id); + IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars); + IEnumerable TypedContent(IEnumerable ids); + IEnumerable TypedContentAtXPath(string xpath, params XPathVariable[] vars); + IEnumerable TypedContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); + IEnumerable TypedContentAtRoot(); + + IPublishedContent TypedMedia(int id); + IEnumerable TypedMedia(IEnumerable ids); + IEnumerable TypedMediaAtRoot(); + + /// + /// Searches content + /// + /// + /// + /// + /// + IEnumerable TypedSearch(string term, bool useWildCards = true, string searchProvider = null); + + /// + /// Searhes content + /// + /// + /// + /// + IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/IUmbracoComponentRenderer.cs b/src/Umbraco.Web/IUmbracoComponentRenderer.cs new file mode 100644 index 0000000000..aaff577491 --- /dev/null +++ b/src/Umbraco.Web/IUmbracoComponentRenderer.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Web; +using Umbraco.Core.Models; + +namespace Umbraco.Web +{ + /// + /// Methods used to render umbraco components as HTML in templates + /// + public interface IUmbracoComponentRenderer + { + /// + /// Renders the template for the specified pageId and an optional altTemplateId + /// + /// + /// If not specified, will use the template assigned to the node + /// + IHtmlString RenderTemplate(int pageId, int? altTemplateId = null); + + /// + /// Renders the macro with the specified alias. + /// + /// The alias. + /// + IHtmlString RenderMacro(string alias); + + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The alias. + /// The parameters. + /// + IHtmlString RenderMacro(string alias, object parameters); + + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The alias. + /// The parameters. + /// + IHtmlString RenderMacro(string alias, IDictionary parameters); + + /// + /// Renders an field to the template + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + //// + /// + IHtmlString Field(IPublishedContent currentPage, string fieldAlias, + string altFieldAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", + bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, + RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, + RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, + bool formatAsDate = false, + bool formatAsDateWithTime = false, + string formatAsDateWithTimeSeparator = ""); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 48634ede4e..681c0c368d 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -189,7 +189,7 @@ namespace Umbraco.Web.Models.Mapping { return content.UpdateDate; } - if (content.HasPublishedVersion()) + if (content.HasPublishedVersion) { var published = applicationContext.Services.ContentService.GetPublishedVersion(content.Id); return published.UpdateDate; diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 2c38644314..8caae86dff 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -144,6 +144,11 @@ namespace Umbraco.Web.Models.Mapping var dt = dataTypeService.GetDataTypeDefinitionByName(customDtdName) ?? dataTypeService.GetDataTypeDefinitionById(dtdId); + if (dt == null) + { + throw new InvalidOperationException("No list view data type was found for this document type, ensure that the default list view data types exists and/or that your custom list view data type exists"); + } + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dt.Id); var editor = PropertyEditorResolver.Current.GetByAlias(dt.PropertyEditorAlias); diff --git a/src/Umbraco.Web/Mvc/PluginController.cs b/src/Umbraco.Web/Mvc/PluginController.cs index 66111875f5..b39403c8bb 100644 --- a/src/Umbraco.Web/Mvc/PluginController.cs +++ b/src/Umbraco.Web/Mvc/PluginController.cs @@ -18,6 +18,8 @@ namespace Umbraco.Web.Mvc /// private static readonly ConcurrentDictionary MetadataStorage = new ConcurrentDictionary(); + private UmbracoHelper _umbracoHelper; + /// /// Default constructor /// @@ -27,7 +29,6 @@ namespace Umbraco.Web.Mvc if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); UmbracoContext = umbracoContext; InstanceId = Guid.NewGuid(); - Umbraco = new UmbracoHelper(umbracoContext); } /// @@ -38,7 +39,10 @@ namespace Umbraco.Web.Mvc /// /// Returns an UmbracoHelper object /// - public UmbracoHelper Umbraco { get; private set; } + public virtual UmbracoHelper Umbraco + { + get { return _umbracoHelper ?? (_umbracoHelper = new UmbracoHelper(UmbracoContext)); } + } /// /// Returns an ILogger @@ -51,7 +55,7 @@ namespace Umbraco.Web.Mvc /// /// Returns a ProfilingLogger /// - public ProfilingLogger ProfilingLogger + public virtual ProfilingLogger ProfilingLogger { get { return UmbracoContext.Application.ProfilingLogger; } } @@ -59,12 +63,12 @@ namespace Umbraco.Web.Mvc /// /// Returns the current UmbracoContext /// - public UmbracoContext UmbracoContext { get; private set; } + public virtual UmbracoContext UmbracoContext { get; private set; } /// /// Returns the current ApplicationContext /// - public ApplicationContext ApplicationContext + public virtual ApplicationContext ApplicationContext { get { return UmbracoContext.Application; } } diff --git a/src/Umbraco.Web/Mvc/RenderMvcController.cs b/src/Umbraco.Web/Mvc/RenderMvcController.cs index da3a5d107f..aeab8448da 100644 --- a/src/Umbraco.Web/Mvc/RenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/RenderMvcController.cs @@ -36,7 +36,7 @@ namespace Umbraco.Web.Mvc /// /// Returns the current UmbracoContext /// - protected new UmbracoContext UmbracoContext + protected virtual new UmbracoContext UmbracoContext { get { return PublishedContentRequest.RoutingContext.UmbracoContext; } } @@ -49,11 +49,10 @@ namespace Umbraco.Web.Mvc get { return PublishedContentRequest.PublishedContent; } } - //TODO: make this protected once we make PublishedContentRequest not internal after we figure out what it should actually contain /// /// Returns the current PublishedContentRequest /// - internal PublishedContentRequest PublishedContentRequest + protected internal virtual PublishedContentRequest PublishedContentRequest { get { diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index 5304821131..1f2bca4144 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -24,7 +24,6 @@ namespace Umbraco.Web.Mvc protected SurfaceController(UmbracoContext umbracoContext) : base(umbracoContext) { - _membershipHelper = new MembershipHelper(umbracoContext); } /// @@ -33,17 +32,16 @@ namespace Umbraco.Web.Mvc protected SurfaceController() : base(UmbracoContext.Current) { - _membershipHelper = new MembershipHelper(UmbracoContext.Current); } - private readonly MembershipHelper _membershipHelper; + private MembershipHelper _membershipHelper; /// /// Returns the MemberHelper instance /// - public MembershipHelper Members + public virtual MembershipHelper Members { - get { return _membershipHelper; } + get { return _membershipHelper ?? (_membershipHelper = new MembershipHelper(UmbracoContext)); } } /// @@ -101,7 +99,7 @@ namespace Umbraco.Web.Mvc /// /// Gets the current page. /// - protected IPublishedContent CurrentPage + protected virtual IPublishedContent CurrentPage { get { diff --git a/src/Umbraco.Web/Mvc/UmbracoController.cs b/src/Umbraco.Web/Mvc/UmbracoController.cs index 413680522d..1ab13cedac 100644 --- a/src/Umbraco.Web/Mvc/UmbracoController.cs +++ b/src/Umbraco.Web/Mvc/UmbracoController.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Mvc /// /// Returns an UmbracoHelper object /// - public UmbracoHelper Umbraco + public virtual UmbracoHelper Umbraco { get { return _umbraco ?? (_umbraco = new UmbracoHelper(UmbracoContext)); } } @@ -45,7 +45,7 @@ namespace Umbraco.Web.Mvc /// /// Returns a ProfilingLogger /// - public ProfilingLogger ProfilingLogger + public virtual ProfilingLogger ProfilingLogger { get { return UmbracoContext.Application.ProfilingLogger; } } @@ -53,12 +53,12 @@ namespace Umbraco.Web.Mvc /// /// Returns the current UmbracoContext /// - public UmbracoContext UmbracoContext { get; private set; } + public virtual UmbracoContext UmbracoContext { get; private set; } /// /// Returns the current ApplicationContext /// - public ApplicationContext ApplicationContext + public virtual ApplicationContext ApplicationContext { get { return UmbracoContext.Application; } } @@ -82,7 +82,7 @@ namespace Umbraco.Web.Mvc /// /// Returns the WebSecurity instance /// - public WebSecurity Security + public virtual WebSecurity Security { get { return UmbracoContext.Security; } } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs new file mode 100644 index 0000000000..f307ff030f --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs @@ -0,0 +1,201 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using umbraco; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Web.Scheduling; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + /// + /// This is the background task runner that persists the xml file to the file system + /// + /// + /// This is used so that all file saving is done on a web aware worker background thread and all logic is performed async so this + /// process will not interfere with any web requests threads. This is also done as to not require any global locks and to ensure that + /// if multiple threads are performing publishing tasks that the file will be persisted in accordance with the final resulting + /// xml structure since the file writes are queued. + /// + internal class XmlCacheFilePersister : ILatchedBackgroundTask + { + private readonly IBackgroundTaskRunner _runner; + private readonly string _xmlFileName; + private readonly ProfilingLogger _logger; + private readonly content _content; + private readonly ManualResetEventSlim _latch = new ManualResetEventSlim(false); + private readonly object _locko = new object(); + private bool _released; + private Timer _timer; + private DateTime _initialTouch; + + private const int WaitMilliseconds = 4000; // save the cache 4s after the last change (ie every 4s min) + private const int MaxWaitMilliseconds = 30000; // save the cache after some time (ie no more than 30s of changes) + + // save the cache when the app goes down + public bool RunsOnShutdown { get { return true; } } + + public XmlCacheFilePersister(IBackgroundTaskRunner runner, content content, string xmlFileName, ProfilingLogger logger, bool touched = false) + { + _runner = runner; + _content = content; + _xmlFileName = xmlFileName; + _logger = logger; + + if (touched == false) return; + + LogHelper.Debug("Create new touched, start."); + + _initialTouch = DateTime.Now; + _timer = new Timer(_ => Release()); + + LogHelper.Debug("Save in {0}ms.", () => WaitMilliseconds); + _timer.Change(WaitMilliseconds, 0); + } + + public XmlCacheFilePersister Touch() + { + lock (_locko) + { + if (_released) + { + LogHelper.Debug("Touched, was released, create new."); + + // released, has run or is running, too late, add & return a new task + var persister = new XmlCacheFilePersister(_runner, _content, _xmlFileName, _logger, true); + _runner.Add(persister); + return persister; + } + + if (_timer == null) + { + LogHelper.Debug("Touched, was idle, start."); + + // not started yet, start + _initialTouch = DateTime.Now; + _timer = new Timer(_ => Release()); + LogHelper.Debug("Save in {0}ms.", () => WaitMilliseconds); + _timer.Change(WaitMilliseconds, 0); + return this; + } + + // set the timer to trigger in WaitMilliseconds unless we've been touched first more + // than MaxWaitMilliseconds ago and then release now + + if (DateTime.Now - _initialTouch < TimeSpan.FromMilliseconds(MaxWaitMilliseconds)) + { + LogHelper.Debug("Touched, was waiting, wait.", () => WaitMilliseconds); + LogHelper.Debug("Save in {0}ms.", () => WaitMilliseconds); + _timer.Change(WaitMilliseconds, 0); + } + else + { + LogHelper.Debug("Save now, release."); + ReleaseLocked(); + } + + return this; // still available + } + } + + private void Release() + { + lock (_locko) + { + ReleaseLocked(); + } + } + + private void ReleaseLocked() + { + LogHelper.Debug("Timer: save now, release."); + if (_timer != null) + _timer.Dispose(); + _timer = null; + _released = true; + _latch.Set(); + } + + public WaitHandle Latch + { + get { return _latch.WaitHandle; } + } + + public bool IsLatched + { + get { return true; } + } + + public async Task RunAsync(CancellationToken token) + { + LogHelper.Debug("Run now."); + var doc = _content.XmlContentInternal; + await PersistXmlToFileAsync(doc); + } + + public bool IsAsync + { + get { return true; } + } + + /// + /// Persist a XmlDocument to the Disk Cache + /// + /// + internal async Task PersistXmlToFileAsync(XmlDocument xmlDoc) + { + if (xmlDoc != null) + { + using (_logger.DebugDuration( + string.Format("Saving content to disk on thread '{0}' (Threadpool? {1})", Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread), + string.Format("Saved content to disk on thread '{0}' (Threadpool? {1})", Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread))) + { + try + { + // Try to create directory for cache path if it doesn't yet exist + var directoryName = Path.GetDirectoryName(_xmlFileName); + // create dir if it is not there, if it's there, this will proceed as normal + Directory.CreateDirectory(directoryName); + + await xmlDoc.SaveAsync(_xmlFileName); + } + catch (Exception ee) + { + // If for whatever reason something goes wrong here, invalidate disk cache + DeleteXmlCache(); + + LogHelper.Error("Error saving content to disk", ee); + } + } + + + } + } + + private void DeleteXmlCache() + { + if (File.Exists(_xmlFileName) == false) return; + + // Reset file attributes, to make sure we can delete file + try + { + File.SetAttributes(_xmlFileName, FileAttributes.Normal); + } + finally + { + File.Delete(_xmlFileName); + } + } + + public void Dispose() + { } + + public void Run() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 33469c0c4b..afeddd1bf1 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Xml.XPath; using Umbraco.Core; @@ -13,82 +14,130 @@ namespace Umbraco.Web /// /// A class used to query for published content, media items /// - public class PublishedContentQuery + public class PublishedContentQuery : ITypedPublishedContentQuery, IDynamicPublishedContentQuery { + private readonly ITypedPublishedContentQuery _typedContentQuery; + private readonly IDynamicPublishedContentQuery _dynamicContentQuery; private readonly ContextualPublishedContentCache _contentCache; private readonly ContextualPublishedMediaCache _mediaCache; + /// + /// Constructor used to return results from the caches + /// + /// + /// public PublishedContentQuery(ContextualPublishedContentCache contentCache, ContextualPublishedMediaCache mediaCache) { + if (contentCache == null) throw new ArgumentNullException("contentCache"); + if (mediaCache == null) throw new ArgumentNullException("mediaCache"); _contentCache = contentCache; _mediaCache = mediaCache; } + /// + /// Constructor used to wrap the ITypedPublishedContentQuery and IDynamicPublishedContentQuery objects passed in + /// + /// + /// + public PublishedContentQuery(ITypedPublishedContentQuery typedContentQuery, IDynamicPublishedContentQuery dynamicContentQuery) + { + if (typedContentQuery == null) throw new ArgumentNullException("typedContentQuery"); + if (dynamicContentQuery == null) throw new ArgumentNullException("dynamicContentQuery"); + _typedContentQuery = typedContentQuery; + _dynamicContentQuery = dynamicContentQuery; + } + #region Content public IPublishedContent TypedContent(int id) { - return TypedDocumentById(id, _contentCache); + return _typedContentQuery == null + ? TypedDocumentById(id, _contentCache) + : _typedContentQuery.TypedContent(id); } public IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars) { - return TypedDocumentByXPath(xpath, vars, _contentCache); + return _typedContentQuery == null + ? TypedDocumentByXPath(xpath, vars, _contentCache) + : _typedContentQuery.TypedContentSingleAtXPath(xpath, vars); } public IEnumerable TypedContent(IEnumerable ids) { - return TypedDocumentsByIds(_contentCache, ids); + return _typedContentQuery == null + ? TypedDocumentsByIds(_contentCache, ids) + : _typedContentQuery.TypedContent(ids); } public IEnumerable TypedContentAtXPath(string xpath, params XPathVariable[] vars) { - return TypedDocumentsByXPath(xpath, vars, _contentCache); + return _typedContentQuery == null + ? TypedDocumentsByXPath(xpath, vars, _contentCache) + : _typedContentQuery.TypedContentAtXPath(xpath, vars); } public IEnumerable TypedContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) { - return TypedDocumentsByXPath(xpath, vars, _contentCache); + return _typedContentQuery == null + ? TypedDocumentsByXPath(xpath, vars, _contentCache) + : _typedContentQuery.TypedContentAtXPath(xpath, vars); } public IEnumerable TypedContentAtRoot() { - return TypedDocumentsAtRoot(_contentCache); + return _typedContentQuery == null + ? TypedDocumentsAtRoot(_contentCache) + : _typedContentQuery.TypedContentAtRoot(); } public dynamic Content(int id) { - return DocumentById(id, _contentCache, DynamicNull.Null); + return _dynamicContentQuery == null + ? DocumentById(id, _contentCache, DynamicNull.Null) + : _dynamicContentQuery.Content(id); } public dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars) { - return DocumentByXPath(xpath, vars, _contentCache, DynamicNull.Null); + return _dynamicContentQuery == null + ? DocumentByXPath(xpath, vars, _contentCache, DynamicNull.Null) + : _dynamicContentQuery.ContentSingleAtXPath(xpath, vars); } public dynamic ContentSingleAtXPath(XPathExpression xpath, params XPathVariable[] vars) { - return DocumentByXPath(xpath, vars, _contentCache, DynamicNull.Null); + return _dynamicContentQuery == null + ? DocumentByXPath(xpath, vars, _contentCache, DynamicNull.Null) + : _dynamicContentQuery.ContentSingleAtXPath(xpath, vars); } public dynamic Content(IEnumerable ids) { - return DocumentByIds(_contentCache, ids.ToArray()); + return _dynamicContentQuery == null + ? DocumentByIds(_contentCache, ids.ToArray()) + : _dynamicContentQuery.Content(ids); } public dynamic ContentAtXPath(string xpath, params XPathVariable[] vars) { - return DocumentsByXPath(xpath, vars, _contentCache); + return _dynamicContentQuery == null + ? DocumentsByXPath(xpath, vars, _contentCache) + : _dynamicContentQuery.ContentAtXPath(xpath, vars); } public dynamic ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) { - return DocumentsByXPath(xpath, vars, _contentCache); + return _dynamicContentQuery == null + ? DocumentsByXPath(xpath, vars, _contentCache) + : _dynamicContentQuery.ContentAtXPath(xpath, vars); } public dynamic ContentAtRoot() { - return DocumentsAtRoot(_contentCache); + return _dynamicContentQuery == null + ? DocumentsAtRoot(_contentCache) + : _dynamicContentQuery.ContentAtRoot(); } #endregion @@ -97,32 +146,44 @@ namespace Umbraco.Web public IPublishedContent TypedMedia(int id) { - return TypedDocumentById(id, _mediaCache); + return _typedContentQuery == null + ? TypedDocumentById(id, _mediaCache) + : _typedContentQuery.TypedMedia(id); } public IEnumerable TypedMedia(IEnumerable ids) { - return TypedDocumentsByIds(_mediaCache, ids); + return _typedContentQuery == null + ? TypedDocumentsByIds(_mediaCache, ids) + : _typedContentQuery.TypedMedia(ids); } public IEnumerable TypedMediaAtRoot() { - return TypedDocumentsAtRoot(_mediaCache); + return _typedContentQuery == null + ? TypedDocumentsAtRoot(_mediaCache) + : _typedContentQuery.TypedMediaAtRoot(); } public dynamic Media(int id) { - return DocumentById(id, _mediaCache, DynamicNull.Null); + return _dynamicContentQuery == null + ? DocumentById(id, _mediaCache, DynamicNull.Null) + : _dynamicContentQuery.Media(id); } public dynamic Media(IEnumerable ids) { - return DocumentByIds(_mediaCache, ids); + return _dynamicContentQuery == null + ? DocumentByIds(_mediaCache, ids) + : _dynamicContentQuery.Media(ids); } public dynamic MediaAtRoot() { - return DocumentsAtRoot(_mediaCache); + return _dynamicContentQuery == null + ? DocumentsAtRoot(_mediaCache) + : _dynamicContentQuery.MediaAtRoot(); } #endregion @@ -240,8 +301,10 @@ namespace Umbraco.Web /// public dynamic Search(string term, bool useWildCards = true, string searchProvider = null) { - return new DynamicPublishedContentList( - TypedSearch(term, useWildCards, searchProvider)); + return _dynamicContentQuery == null + ? new DynamicPublishedContentList( + TypedSearch(term, useWildCards, searchProvider)) + : _dynamicContentQuery.Search(term, useWildCards, searchProvider); } /// @@ -252,8 +315,10 @@ namespace Umbraco.Web /// public dynamic Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) { - return new DynamicPublishedContentList( - TypedSearch(criteria, searchProvider)); + return _dynamicContentQuery == null + ? new DynamicPublishedContentList( + TypedSearch(criteria, searchProvider)) + : _dynamicContentQuery.Search(criteria, searchProvider); } /// @@ -265,6 +330,8 @@ namespace Umbraco.Web /// public IEnumerable TypedSearch(string term, bool useWildCards = true, string searchProvider = null) { + if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(term, useWildCards, searchProvider); + var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider; if (string.IsNullOrEmpty(searchProvider) == false) searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; @@ -281,6 +348,8 @@ namespace Umbraco.Web /// public IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) { + if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(criteria, searchProvider); + var s = Examine.ExamineManager.Instance.DefaultSearchProvider; if (searchProvider != null) s = searchProvider; diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index d2639becf6..78f4449c88 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -1,7 +1,11 @@ using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; +using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using umbraco; @@ -31,26 +35,34 @@ namespace Umbraco.Web.Routing // the content request is just a data holder private readonly PublishedContentRequestEngine _engine; - /// - /// Initializes a new instance of the class with a specific Uri and routing context. - /// - /// The request Uri. - /// A routing context. - public PublishedContentRequest(Uri uri, RoutingContext routingContext) - { - if (uri == null) throw new ArgumentNullException("uri"); - if (routingContext == null) throw new ArgumentNullException("routingContext"); + /// + /// Initializes a new instance of the class with a specific Uri and routing context. + /// + /// The request Uri. + /// A routing context. + /// A callback method to return the roles for the provided login name when required + /// + public PublishedContentRequest(Uri uri, RoutingContext routingContext, IWebRoutingSection routingConfig, Func> getRolesForLogin) + { + if (uri == null) throw new ArgumentNullException("uri"); + if (routingContext == null) throw new ArgumentNullException("routingContext"); - Uri = uri; - RoutingContext = routingContext; + Uri = uri; + RoutingContext = routingContext; + GetRolesForLogin = getRolesForLogin; - _engine = new PublishedContentRequestEngine( - routingContext.UmbracoContext.Application.Services.DomainService, - routingContext.UmbracoContext.Application.Services.LocalizationService, - routingContext.UmbracoContext.Application.ProfilingLogger, + _engine = new PublishedContentRequestEngine( + routingConfig, this); RenderingEngine = RenderingEngine.Unknown; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the constructor specifying all dependencies instead")] + public PublishedContentRequest(Uri uri, RoutingContext routingContext) + : this(uri, routingContext, UmbracoConfig.For.UmbracoSettings().WebRouting, s => Roles.Provider.GetRolesForUser(s)) + { } /// @@ -398,7 +410,9 @@ namespace Umbraco.Web.Routing /// public RoutingContext RoutingContext { get; private set; } - /// + internal Func> GetRolesForLogin { get; private set; } + + /// /// The "umbraco page" object. /// private page _umbracoPage; diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 076429f261..6dfb018019 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Globalization; @@ -6,6 +8,7 @@ using System.IO; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Security; @@ -15,38 +18,33 @@ using umbraco.cms.businesslogic.web; using umbraco.cms.businesslogic.language; using umbraco.cms.businesslogic.member; using Umbraco.Core.Services; +using Umbraco.Web.Security; using RenderingEngine = Umbraco.Core.RenderingEngine; namespace Umbraco.Web.Routing { internal class PublishedContentRequestEngine - { - private readonly IDomainService _domainService; - private readonly ILocalizationService _localizationService; - private readonly ProfilingLogger _profilingLogger; + { private readonly PublishedContentRequest _pcr; - private readonly RoutingContext _routingContext; + private readonly RoutingContext _routingContext; + private readonly IWebRoutingSection _webRoutingSection; /// /// Initializes a new instance of the class with a content request. /// - /// - /// - /// + /// /// The content request. - public PublishedContentRequestEngine(IDomainService domainService, ILocalizationService localizationService, ProfilingLogger profilingLogger, PublishedContentRequest pcr) + public PublishedContentRequestEngine( + IWebRoutingSection webRoutingSection, + PublishedContentRequest pcr) { - if (domainService == null) throw new ArgumentNullException("domainService"); - if (localizationService == null) throw new ArgumentNullException("localizationService"); - if (profilingLogger == null) throw new ArgumentNullException("profilingLogger"); if (pcr == null) throw new ArgumentException("pcr is null."); - - _domainService = domainService; - _localizationService = localizationService; - _profilingLogger = profilingLogger; + if (webRoutingSection == null) throw new ArgumentNullException("webRoutingSection"); + _pcr = pcr; - - _routingContext = pcr.RoutingContext; + _webRoutingSection = webRoutingSection; + + _routingContext = pcr.RoutingContext; if (_routingContext == null) throw new ArgumentException("pcr.RoutingContext is null."); var umbracoContext = _routingContext.UmbracoContext; @@ -56,6 +54,17 @@ namespace Umbraco.Web.Routing //if (umbracoContext.PublishedContentRequest != _pcr) throw new ArgumentException("PublishedContentRequest confusion."); } + protected ProfilingLogger ProfilingLogger + { + get { return _routingContext.UmbracoContext.Application.ProfilingLogger; } + } + + protected ServiceContext Services + { + get { return _routingContext.UmbracoContext.Application.Services; } + + } + #region Public /// @@ -224,16 +233,16 @@ namespace Umbraco.Web.Routing // note - we are not handling schemes nor ports here. - LogHelper.Debug("{0}Uri=\"{1}\"", () => tracePrefix, () => _pcr.Uri); + ProfilingLogger.Logger.Debug("{0}Uri=\"{1}\"", () => tracePrefix, () => _pcr.Uri); // try to find a domain matching the current request - var domainAndUri = DomainHelper.DomainForUri(_domainService.GetAll(false), _pcr.Uri); + var domainAndUri = DomainHelper.DomainForUri(Services.DomainService.GetAll(false), _pcr.Uri); // handle domain if (domainAndUri != null) { // matching an existing domain - LogHelper.Debug("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"", + ProfilingLogger.Logger.Debug("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"", () => tracePrefix, () => domainAndUri.UmbracoDomain.DomainName, () => domainAndUri.UmbracoDomain.RootContent.Id, @@ -253,13 +262,13 @@ namespace Umbraco.Web.Routing else { // not matching any existing domain - LogHelper.Debug("{0}Matches no domain", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Matches no domain", () => tracePrefix); - var defaultLanguage = _localizationService.GetAllLanguages().FirstOrDefault(); + var defaultLanguage = Services.LocalizationService.GetAllLanguages().FirstOrDefault(); _pcr.Culture = defaultLanguage == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultLanguage.IsoCode); } - LogHelper.Debug("{0}Culture=\"{1}\"", () => tracePrefix, () => _pcr.Culture.Name); + ProfilingLogger.Logger.Debug("{0}Culture=\"{1}\"", () => tracePrefix, () => _pcr.Culture.Name); return _pcr.UmbracoDomain != null; } @@ -275,19 +284,19 @@ namespace Umbraco.Web.Routing return; var nodePath = _pcr.PublishedContent.Path; - LogHelper.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath); + ProfilingLogger.Logger.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath); var rootNodeId = _pcr.HasDomain ? _pcr.UmbracoDomain.RootContent.Id : (int?)null; - var domain = DomainHelper.FindWildcardDomainInPath(_domainService.GetAll(true), nodePath, rootNodeId); + var domain = DomainHelper.FindWildcardDomainInPath(Services.DomainService.GetAll(true), nodePath, rootNodeId); if (domain != null) { _pcr.Culture = new CultureInfo(domain.Language.IsoCode); - LogHelper.Debug("{0}Got domain on node {1}, set culture to \"{2}\".", () => tracePrefix, + ProfilingLogger.Logger.Debug("{0}Got domain on node {1}, set culture to \"{2}\".", () => tracePrefix, () => domain.RootContent.Id, () => _pcr.Culture.Name); } else { - LogHelper.Debug("{0}No match.", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}No match.", () => tracePrefix); } } @@ -349,7 +358,7 @@ namespace Umbraco.Web.Routing private void FindPublishedContentAndTemplate() { const string tracePrefix = "FindPublishedContentAndTemplate: "; - LogHelper.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => _pcr.Uri.AbsolutePath); + ProfilingLogger.Logger.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => _pcr.Uri.AbsolutePath); // run the document finders FindPublishedContent(); @@ -388,14 +397,15 @@ namespace Umbraco.Web.Routing // the first successful finder, if any, will set this.PublishedContent, and may also set this.Template // some finders may implement caching - using (_profilingLogger.DebugDuration( + using (ProfilingLogger.DebugDuration( string.Format("{0}Begin finders", tracePrefix), string.Format("{0}End finders, {1}", tracePrefix, (_pcr.HasPublishedContent ? "a document was found" : "no document was found")))) { if (_routingContext.PublishedContentFinders == null) throw new InvalidOperationException("There is no finder collection."); - _routingContext.PublishedContentFinders.ForEach(finder => finder.TryFindContent(_pcr)); + //iterate but return on first one that finds it + var found = _routingContext.PublishedContentFinders.Any(finder => finder.TryFindContent(_pcr)); } // indicate that the published content (if any) we have at the moment is the @@ -419,23 +429,23 @@ namespace Umbraco.Web.Routing const int maxLoop = 8; do { - LogHelper.Debug("{0}{1}", () => tracePrefix, () => (i == 0 ? "Begin" : "Loop")); + ProfilingLogger.Logger.Debug("{0}{1}", () => tracePrefix, () => (i == 0 ? "Begin" : "Loop")); // handle not found if (_pcr.HasPublishedContent == false) { _pcr.Is404 = true; - LogHelper.Debug("{0}No document, try last chance lookup", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}No document, try last chance lookup", () => tracePrefix); // if it fails then give up, there isn't much more that we can do var lastChance = _routingContext.PublishedContentLastChanceFinder; if (lastChance == null || lastChance.TryFindContent(_pcr) == false) { - LogHelper.Debug("{0}Failed to find a document, give up", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Failed to find a document, give up", () => tracePrefix); break; } - LogHelper.Debug("{0}Found a document", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Found a document", () => tracePrefix); } // follow internal redirects as long as it's not running out of control ie infinite loop of some sort @@ -457,11 +467,11 @@ namespace Umbraco.Web.Routing if (i == maxLoop || j == maxLoop) { - LogHelper.Debug("{0}Looks like we're running into an infinite loop, abort", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Looks like we're running into an infinite loop, abort", () => tracePrefix); _pcr.PublishedContent = null; } - LogHelper.Debug("{0}End", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}End", () => tracePrefix); } /// @@ -484,7 +494,7 @@ namespace Umbraco.Web.Routing if (string.IsNullOrWhiteSpace(internalRedirect) == false) { - LogHelper.Debug("{0}Found umbracoInternalRedirectId={1}", () => tracePrefix, () => internalRedirect); + ProfilingLogger.Logger.Debug("{0}Found umbracoInternalRedirectId={1}", () => tracePrefix, () => internalRedirect); int internalRedirectId; if (int.TryParse(internalRedirect, out internalRedirectId) == false) @@ -494,12 +504,12 @@ namespace Umbraco.Web.Routing { // bad redirect - log and display the current page (legacy behavior) //_pcr.Document = null; // no! that would be to force a 404 - LogHelper.Debug("{0}Failed to redirect to id={1}: invalid value", () => tracePrefix, () => internalRedirect); + ProfilingLogger.Logger.Debug("{0}Failed to redirect to id={1}: invalid value", () => tracePrefix, () => internalRedirect); } else if (internalRedirectId == _pcr.PublishedContent.Id) { // redirect to self - LogHelper.Debug("{0}Redirecting to self, ignore", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Redirecting to self, ignore", () => tracePrefix); } else { @@ -510,18 +520,18 @@ namespace Umbraco.Web.Routing if (node != null) { redirect = true; - LogHelper.Debug("{0}Redirecting to id={1}", () => tracePrefix, () => internalRedirectId); + ProfilingLogger.Logger.Debug("{0}Redirecting to id={1}", () => tracePrefix, () => internalRedirectId); } else { - LogHelper.Debug("{0}Failed to redirect to id={1}: no such published document", () => tracePrefix, () => internalRedirectId); + ProfilingLogger.Logger.Debug("{0}Failed to redirect to id={1}: no such published document", () => tracePrefix, () => internalRedirectId); } } } return redirect; } - + /// /// Ensures that access to current node is permitted. /// @@ -535,48 +545,38 @@ namespace Umbraco.Web.Routing var path = _pcr.PublishedContent.Path; - if (Access.IsProtected(_pcr.PublishedContent.Id, path)) + var publicAccessAttempt = Services.PublicAccessService.IsProtected(path); + + if (publicAccessAttempt) { - LogHelper.Debug("{0}Page is protected, check for access", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Page is protected, check for access", () => tracePrefix); - //TODO: We coud speed this up, the only reason we are looking up the members is for it's - // ProviderUserKey (id). We could store this id in the FormsAuth cookie custom data when - // a member logs in. Then we can check if the value exists and just use that, otherwise lookup - // the member like we are currently doing. + var membershipHelper = new MembershipHelper(_routingContext.UmbracoContext); - MembershipUser user = null; - try + if (membershipHelper.IsLoggedIn() == false) { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - user = provider.GetCurrentUser(); - } - catch (ArgumentException) - { - LogHelper.Debug("{0}Membership.GetUser returned ArgumentException", () => tracePrefix); - } + ProfilingLogger.Logger.Debug("{0}Not logged in, redirect to login page", () => tracePrefix); + + var loginPageId = publicAccessAttempt.Result.LoginNodeId; - if (user == null || Member.IsLoggedOn() == false) - { - LogHelper.Debug("{0}Not logged in, redirect to login page", () => tracePrefix); - var loginPageId = Access.GetLoginPage(path); if (loginPageId != _pcr.PublishedContent.Id) _pcr.PublishedContent = _routingContext.UmbracoContext.ContentCache.GetById(loginPageId); } - else if (Access.HasAccces(_pcr.PublishedContent.Id, user.ProviderUserKey) == false) + else if (Services.PublicAccessService.HasAccess(_pcr.PublishedContent.Id, Services.ContentService, _pcr.GetRolesForLogin(membershipHelper.CurrentUserName)) == false) { - LogHelper.Debug("{0}Current member has not access, redirect to error page", () => tracePrefix); - var errorPageId = Access.GetErrorPage(path); + ProfilingLogger.Logger.Debug("{0}Current member has not access, redirect to error page", () => tracePrefix); + var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; if (errorPageId != _pcr.PublishedContent.Id) _pcr.PublishedContent = _routingContext.UmbracoContext.ContentCache.GetById(errorPageId); } else { - LogHelper.Debug("{0}Current member has access", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Current member has access", () => tracePrefix); } } else { - LogHelper.Debug("{0}Page is not protected", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Page is not protected", () => tracePrefix); } } @@ -602,7 +602,7 @@ namespace Umbraco.Web.Routing // does not apply // + optionnally, apply the alternate template on internal redirects var useAltTemplate = _pcr.IsInitialPublishedContent - || (UmbracoConfig.For.UmbracoSettings().WebRouting.InternalRedirectPreservesTemplate && _pcr.IsInternalRedirectPublishedContent); + || (_webRoutingSection.InternalRedirectPreservesTemplate && _pcr.IsInternalRedirectPublishedContent); string altTemplate = useAltTemplate ? _routingContext.UmbracoContext.HttpContext.Request[Constants.Conventions.Url.AltTemplate] : null; @@ -615,7 +615,7 @@ namespace Umbraco.Web.Routing if (_pcr.HasTemplate) { - LogHelper.Debug("{0}Has a template already, and no alternate template.", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Has a template already, and no alternate template.", () => tracePrefix); return; } @@ -626,16 +626,16 @@ namespace Umbraco.Web.Routing if (templateId > 0) { - LogHelper.Debug("{0}Look for template id={1}", () => tracePrefix, () => templateId); + ProfilingLogger.Logger.Debug("{0}Look for template id={1}", () => tracePrefix, () => templateId); var template = ApplicationContext.Current.Services.FileService.GetTemplate(templateId); if (template == null) throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render"); _pcr.TemplateModel = template; - LogHelper.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias); + ProfilingLogger.Logger.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias); } else { - LogHelper.Debug("{0}No specified template.", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}No specified template.", () => tracePrefix); } } else @@ -647,24 +647,24 @@ namespace Umbraco.Web.Routing // ignore if the alias does not match - just trace if (_pcr.HasTemplate) - LogHelper.Debug("{0}Has a template already, but also an alternate template.", () => tracePrefix); - LogHelper.Debug("{0}Look for alternate template alias=\"{1}\"", () => tracePrefix, () => altTemplate); + ProfilingLogger.Logger.Debug("{0}Has a template already, but also an alternate template.", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}Look for alternate template alias=\"{1}\"", () => tracePrefix, () => altTemplate); var template = ApplicationContext.Current.Services.FileService.GetTemplate(altTemplate); if (template != null) { _pcr.TemplateModel = template; - LogHelper.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias); + ProfilingLogger.Logger.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias); } else { - LogHelper.Debug("{0}The template with alias=\"{1}\" does not exist, ignoring.", () => tracePrefix, () => altTemplate); + ProfilingLogger.Logger.Debug("{0}The template with alias=\"{1}\" does not exist, ignoring.", () => tracePrefix, () => altTemplate); } } if (_pcr.HasTemplate == false) { - LogHelper.Debug("{0}No template was found.", () => tracePrefix); + ProfilingLogger.Logger.Debug("{0}No template was found.", () => tracePrefix); // initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true // then reset _pcr.Document to null to force a 404. @@ -677,7 +677,7 @@ namespace Umbraco.Web.Routing } else { - LogHelper.Debug("{0}Running with template id={1} alias=\"{2}\"", () => tracePrefix, () => _pcr.TemplateModel.Id, () => _pcr.TemplateModel.Alias); + ProfilingLogger.Logger.Debug("{0}Running with template id={1} alias=\"{2}\"", () => tracePrefix, () => _pcr.TemplateModel.Id, () => _pcr.TemplateModel.Alias); } } diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index f4b9181bef..714ba64e64 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -21,8 +21,10 @@ namespace Umbraco.Web.Routing /// The Umbraco context. /// /// The list of url providers. - internal UrlProvider(UmbracoContext umbracoContext, IWebRoutingSection routingSettings, IEnumerable urlProviders) + public UrlProvider(UmbracoContext umbracoContext, IWebRoutingSection routingSettings, IEnumerable urlProviders) { + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + _umbracoContext = umbracoContext; _urlProviders = urlProviders; @@ -32,7 +34,23 @@ namespace Umbraco.Web.Routing if (Enum.TryParse(routingSettings.UrlProviderMode, out provider)) { Mode = provider; - } + } + } + + /// + /// Initializes a new instance of the class with an Umbraco context and a list of url providers. + /// + /// The Umbraco context. + /// The list of url providers. + /// + public UrlProvider(UmbracoContext umbracoContext, IEnumerable urlProviders, UrlProviderMode provider = UrlProviderMode.Auto) + { + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + + _umbracoContext = umbracoContext; + _urlProviders = urlProviders; + + Mode = provider; } private readonly UmbracoContext _umbracoContext; diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 501c15b375..91860e03e6 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing var urls = new List(); - if (content.HasPublishedVersion() == false) + if (content.HasPublishedVersion == false) { urls.Add(ui.Text("content", "itemNotPublished", umbracoContext.Security.CurrentUser)); return urls; diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index a0c6720a88..3a5ace8af8 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -9,55 +9,97 @@ using Umbraco.Core.Logging; namespace Umbraco.Web.Scheduling { /// - /// This is used to create a background task runner which will stay alive in the background of and complete - /// any tasks that are queued. It is web aware and will ensure that it is shutdown correctly when the app domain - /// is shutdown. + /// Manages a queue of tasks of type and runs them in the background. /// - /// - internal class BackgroundTaskRunner : IDisposable, IRegisteredObject - where T : IBackgroundTask + /// The type of the managed tasks. + /// The task runner is web-aware and will ensure that it shuts down correctly when the AppDomain + /// shuts down (ie is unloaded). + internal class BackgroundTaskRunner : IBackgroundTaskRunner + where T : class, IBackgroundTask { - private readonly bool _dedicatedThread; - private readonly bool _persistentThread; + private readonly BackgroundTaskRunnerOptions _options; private readonly BlockingCollection _tasks = new BlockingCollection(); - private Task _consumer; + private readonly object _locker = new object(); + private readonly ManualResetEventSlim _completedEvent = new ManualResetEventSlim(false); + + private volatile bool _isRunning; // is running + private volatile bool _isCompleted; // does not accept tasks anymore, may still be running + private Task _runningTask; - private volatile bool _isRunning = false; - private static readonly object Locker = new object(); private CancellationTokenSource _tokenSource; + internal event EventHandler> TaskError; internal event EventHandler> TaskStarting; internal event EventHandler> TaskCompleted; internal event EventHandler> TaskCancelled; - public BackgroundTaskRunner(bool dedicatedThread = false, bool persistentThread = false) + /// + /// Initializes a new instance of the class. + /// + public BackgroundTaskRunner() + : this(new BackgroundTaskRunnerOptions()) + { } + + /// + /// Initializes a new instance of the class with a set of options. + /// + /// The set of options. + public BackgroundTaskRunner(BackgroundTaskRunnerOptions options) { - _dedicatedThread = dedicatedThread; - _persistentThread = persistentThread; + if (options == null) throw new ArgumentNullException("options"); + _options = options; + HostingEnvironment.RegisterObject(this); + + if (options.AutoStart) + StartUp(); } + /// + /// Gets the number of tasks in the queue. + /// public int TaskCount { get { return _tasks.Count; } } + /// + /// Gets a value indicating whether a task is currently running. + /// public bool IsRunning { get { return _isRunning; } } - public TaskStatus TaskStatus + /// + /// Gets a value indicating whether the runner has completed and cannot accept tasks anymore. + /// + public bool IsCompleted { - get { return _consumer.Status; } + get { return _isCompleted; } } + /// + /// Gets the status of the running task. + /// + /// There is no running task. + /// Unless the AutoStart option is true, there will be no running task until + /// a background task is added to the queue. Unless the KeepAlive option is true, there + /// will be no running task when the queue is empty. + public TaskStatus TaskStatus + { + get + { + if (_runningTask == null) + throw new InvalidOperationException("There is no current task."); + return _runningTask.Status; + } + } /// - /// Returns the task awaiter so that consumers of the BackgroundTaskManager can await - /// the threading operation. + /// Gets an awaiter used to await the running task. /// - /// + /// An awaiter for the running task. /// /// This is just the coolest thing ever, check this article out: /// http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx @@ -65,237 +107,292 @@ namespace Umbraco.Web.Scheduling /// So long as we have a method called GetAwaiter() that returns an instance of INotifyCompletion /// we can await anything! :) /// + /// There is no running task. + /// Unless the AutoStart option is true, there will be no running task until + /// a background task is added to the queue. Unless the KeepAlive option is true, there + /// will be no running task when the queue is empty. public TaskAwaiter GetAwaiter() { - return _consumer.GetAwaiter(); + if (_runningTask == null) + throw new InvalidOperationException("There is no current task."); + return _runningTask.GetAwaiter(); } + /// + /// Adds a task to the queue. + /// + /// The task to add. + /// The task runner has completed. public void Add(T task) { - //add any tasks first - LogHelper.Debug>(" Task added {0}", () => task.GetType()); - _tasks.Add(task); + lock (_locker) + { + if (_isCompleted) + throw new InvalidOperationException("The task runner has completed."); - //ensure's everything is started - StartUp(); + // add task + LogHelper.Debug>("Task added {0}", task.GetType); + _tasks.Add(task); + + // start + StartUpLocked(); + } } + /// + /// Tries to add a task to the queue. + /// + /// The task to add. + /// true if the task could be added to the queue; otherwise false. + /// Returns false if the runner is completed. + public bool TryAdd(T task) + { + lock (_locker) + { + if (_isCompleted) return false; + + // add task + LogHelper.Debug>("Task added {0}", task.GetType); + _tasks.Add(task); + + // start + StartUpLocked(); + + return true; + } + } + + /// + /// Starts the tasks runner, if not already running. + /// + /// Is invoked each time a task is added, to ensure it is going to be processed. + /// The task runner has completed. public void StartUp() { - if (!_isRunning) + if (_isRunning) return; + + lock (_locker) { - lock (Locker) - { - //double check - if (!_isRunning) - { - _isRunning = true; - //Create a new token source since this is a new proces - _tokenSource = new CancellationTokenSource(); - StartConsumer(); - LogHelper.Debug>("Starting"); - } - } - } - } + if (_isCompleted) + throw new InvalidOperationException("The task runner has completed."); - public void ShutDown() - { - lock (Locker) - { - _isRunning = false; - - try - { - if (_consumer != null) - { - //cancel all operations - _tokenSource.Cancel(); - - try - { - _consumer.Wait(); - } - catch (AggregateException e) - { - //NOTE: We are logging Debug because we are expecting these errors - - LogHelper.Debug>("AggregateException thrown with the following inner exceptions:"); - // Display information about each exception. - foreach (var v in e.InnerExceptions) - { - var exception = v as TaskCanceledException; - if (exception != null) - { - LogHelper.Debug>(" .Net TaskCanceledException: .Net Task ID {0}", () => exception.Task.Id); - } - else - { - LogHelper.Debug>(" Exception: {0}", () => v.GetType().Name); - } - } - } - } - - if (_tasks.Count > 0) - { - LogHelper.Debug>("Processing remaining tasks before shutdown: {0}", () => _tasks.Count); - - //now we need to ensure the remaining queue is processed if there's any remaining, - // this will all be processed on the current/main thread. - T remainingTask; - while (_tasks.TryTake(out remainingTask)) - { - ConsumeTaskInternal(remainingTask); - } - } - - LogHelper.Debug>("Shutdown"); - - //disposing these is really optional since they'll be disposed immediately since they are no longer running - //but we'll put this here anyways. - if (_consumer != null && (_consumer.IsCompleted || _consumer.IsCanceled)) - { - _consumer.Dispose(); - } - } - catch (Exception ex) - { - LogHelper.Error>("Error occurred shutting down task runner", ex); - } - finally - { - HostingEnvironment.UnregisterObject(this); - } + StartUpLocked(); } } /// - /// Starts the consumer task + /// Starts the tasks runner, if not already running. /// - private void StartConsumer() + /// Must be invoked within lock(_locker) and with _isCompleted being false. + private void StartUpLocked() { - var token = _tokenSource.Token; + // double check + if (_isRunning) return; + _isRunning = true; - _consumer = Task.Factory.StartNew(() => - StartThread(token), + // create a new token source since this is a new process + _tokenSource = new CancellationTokenSource(); + _runningTask = PumpIBackgroundTasks(Task.Factory, _tokenSource.Token); + LogHelper.Debug>("Starting"); + } + + /// + /// Shuts the taks runner down. + /// + /// True for force the runner to stop. + /// True to wait until the runner has stopped. + /// If is false, no more tasks can be queued but all queued tasks + /// will run. If it is true, then only the current one (if any) will end and no other task will run. + public void Shutdown(bool force, bool wait) + { + lock (_locker) + { + _isCompleted = true; // do not accept new tasks + if (_isRunning == false) return; // done already + } + + // try to be nice + // assuming multiple threads can do these without problems + _completedEvent.Set(); + _tasks.CompleteAdding(); + + if (force) + { + // we must bring everything down, now + Thread.Sleep(100); // give time to CompleAdding() + lock (_locker) + { + // was CompleteAdding() enough? + if (_isRunning == false) return; + } + // try to cancel running async tasks (cannot do much about sync tasks) + // break delayed tasks delay + // truncate running queues + _tokenSource.Cancel(false); // false is the default + } + + // tasks in the queue will be executed... + if (wait == false) return; + _runningTask.Wait(); // wait for whatever is running to end... + + } + + /// + /// Runs background tasks for as long as there are background tasks in the queue, with an asynchronous operation. + /// + /// The supporting . + /// A cancellation token. + /// The asynchronous operation. + private Task PumpIBackgroundTasks(TaskFactory factory, CancellationToken token) + { + var taskSource = new TaskCompletionSource(factory.CreationOptions); + var enumerator = _options.KeepAlive ? _tasks.GetConsumingEnumerable(token).GetEnumerator() : null; + + // ReSharper disable once MethodSupportsCancellation // always run + var taskSourceContinuing = taskSource.Task.ContinueWith(t => + { + // because the pump does not lock, there's a race condition, + // the pump may stop and then we still have tasks to process, + // and then we must restart the pump - lock to avoid race cond + lock (_locker) + { + if (token.IsCancellationRequested || _tasks.Count == 0) + { + _isRunning = false; // done + if (_options.PreserveRunningTask == false) + _runningTask = null; + return; + } + } + + // if _runningTask is taskSource.Task then we must keep continuing it, + // not starting a new taskSource, else _runningTask would complete and + // something may be waiting on it + //PumpIBackgroundTasks(factory, token); // restart + // ReSharper disable once MethodSupportsCancellation // always run + t.ContinueWithTask(_ => PumpIBackgroundTasks(factory, token)); // restart + }); + + Action pump = null; + pump = task => + { + // RunIBackgroundTaskAsync does NOT throw exceptions, just raises event + // so if we have an exception here, really, wtf? - must read the exception + // anyways so it does not bubble up and kill everything + if (task != null && task.IsFaulted) + { + var exception = task.Exception; + LogHelper.Error>("Task runner exception.", exception); + } + + // is it ok to run? + if (TaskSourceCanceled(taskSource, token)) return; + + // try to get a task + // the blocking MoveNext will end if token is cancelled or collection is completed + T bgTask; + var hasBgTask = _options.KeepAlive + ? (bgTask = enumerator.MoveNext() ? enumerator.Current : null) != null // blocking + : _tasks.TryTake(out bgTask); // non-blocking + + // no task, signal the runner we're done + if (hasBgTask == false) + { + TaskSourceCompleted(taskSource, token); + return; + } + + // wait for latched task, supporting cancellation + var dbgTask = bgTask as ILatchedBackgroundTask; + if (dbgTask != null && dbgTask.IsLatched) + { + WaitHandle.WaitAny(new[] { dbgTask.Latch, token.WaitHandle, _completedEvent.WaitHandle }); + if (TaskSourceCanceled(taskSource, token)) return; + // else run now, either because latch ok or runner is completed + // still latched & not running on shutdown = stop here + if (dbgTask.IsLatched && dbgTask.RunsOnShutdown == false) + { + TaskSourceCompleted(taskSource, token); + return; + } + } + + // run the task as first task, or a continuation + task = task == null + ? RunIBackgroundTaskAsync(bgTask, token) + // ReSharper disable once MethodSupportsCancellation // always run + : task.ContinueWithTask(_ => RunIBackgroundTaskAsync(bgTask, token)); + + // and pump + // ReSharper disable once MethodSupportsCancellation // always run + task.ContinueWith(t => pump(t)); + }; + + // start it all + factory.StartNew(() => pump(null), token, - _dedicatedThread ? TaskCreationOptions.LongRunning : TaskCreationOptions.None, + _options.LongRunning ? TaskCreationOptions.LongRunning : TaskCreationOptions.None, TaskScheduler.Default); - //if this is not a persistent thread, wait till it's done and shut ourselves down - // thus ending the thread or giving back to the thread pool. If another task is added - // another thread will spawn or be taken from the pool to process. - if (!_persistentThread) - { - _consumer.ContinueWith(task => ShutDown()); - } - + return taskSourceContinuing; } - /// - /// Invokes a new worker thread to consume tasks - /// - /// - private void StartThread(CancellationToken token) - { - // Was cancellation already requested? - if (token.IsCancellationRequested) - { - LogHelper.Info>("Thread {0} was cancelled before it got started.", () => Thread.CurrentThread.ManagedThreadId); - token.ThrowIfCancellationRequested(); - } - - TakeAndConsumeTask(token); - } - - /// - /// Trys to get a task from the queue, if there isn't one it will wait a second and try again - /// - /// - private void TakeAndConsumeTask(CancellationToken token) + private bool TaskSourceCanceled(TaskCompletionSource taskSource, CancellationToken token) { if (token.IsCancellationRequested) { - LogHelper.Info>("Thread {0} was cancelled.", () => Thread.CurrentThread.ManagedThreadId); - token.ThrowIfCancellationRequested(); + taskSource.SetCanceled(); + return true; } + return false; + } - //If this is true, the thread will stay alive and just wait until there is anything in the queue - // and process it. When there is nothing in the queue, the thread will just block until there is - // something to process. - //When this is false, the thread will process what is currently in the queue and once that is - // done, the thread will end and we will shutdown the process - - if (_persistentThread) - { - //This will iterate over the collection, if there is nothing to take - // the thread will block until there is something available. - //We need to pass our cancellation token so that the thread will - // cancel when we shutdown - foreach (var t in _tasks.GetConsumingEnumerable(token)) - { - ConsumeTaskCancellable(t, token); - } - - //recurse and keep going - TakeAndConsumeTask(token); - } + private void TaskSourceCompleted(TaskCompletionSource taskSource, CancellationToken token) + { + if (token.IsCancellationRequested) + taskSource.SetCanceled(); else - { - T repositoryTask; - while (_tasks.TryTake(out repositoryTask)) - { - ConsumeTaskCancellable(repositoryTask, token); - } - - //the task will end here - } + taskSource.SetResult(null); } - internal void ConsumeTaskCancellable(T task, CancellationToken token) - { - if (token.IsCancellationRequested) - { - OnTaskCancelled(new TaskEventArgs(task)); - - //NOTE: Since the task hasn't started this is pretty pointless so leaving it out. - LogHelper.Info>("Task {0}) was cancelled.", - () => task.GetType()); - - token.ThrowIfCancellationRequested(); - } - - ConsumeTaskInternal(task); - } - - private void ConsumeTaskInternal(T task) + /// + /// Runs a background task asynchronously. + /// + /// The background task. + /// A cancellation token. + /// The asynchronous operation. + internal async Task RunIBackgroundTaskAsync(T bgTask, CancellationToken token) { try { - OnTaskStarting(new TaskEventArgs(task)); + OnTaskStarting(new TaskEventArgs(bgTask)); try { - using (task) + using (bgTask) // ensure it's disposed { - task.Run(); + if (bgTask.IsAsync) + await bgTask.RunAsync(token); + else + bgTask.Run(); } } catch (Exception e) { - OnTaskError(new TaskEventArgs(task, e)); + OnTaskError(new TaskEventArgs(bgTask, e)); throw; } - OnTaskCompleted(new TaskEventArgs(task)); + OnTaskCompleted(new TaskEventArgs(bgTask)); } catch (Exception ex) { - LogHelper.Error>("An error occurred consuming task", ex); + LogHelper.Error>("Task has failed.", ex); } } + #region Events + protected virtual void OnTaskError(TaskEventArgs e) { var handler = TaskError; @@ -318,10 +415,15 @@ namespace Umbraco.Web.Scheduling { var handler = TaskCancelled; if (handler != null) handler(this, e); + + //dispose it + e.Task.Dispose(); } + #endregion + + #region IDisposable - #region Disposal private readonly object _disposalLocker = new object(); public bool IsDisposed { get; private set; } @@ -338,8 +440,9 @@ namespace Umbraco.Web.Scheduling protected virtual void Dispose(bool disposing) { - if (this.IsDisposed || !disposing) + if (this.IsDisposed || disposing == false) return; + lock (_disposalLocker) { if (IsDisposed) @@ -351,31 +454,60 @@ namespace Umbraco.Web.Scheduling protected virtual void DisposeResources() { - ShutDown(); + // just make sure we eventually go down + Shutdown(true, false); } + #endregion + /// + /// Requests a registered object to unregister. + /// + /// true to indicate the registered object should unregister from the hosting + /// environment before returning; otherwise, false. + /// + /// "When the application manager needs to stop a registered object, it will call the Stop method." + /// The application manager will call the Stop method to ask a registered object to unregister. During + /// processing of the Stop method, the registered object must call the HostingEnvironment.UnregisterObject method. + /// public void Stop(bool immediate) { if (immediate == false) { - LogHelper.Debug>("Application is shutting down, waiting for tasks to complete"); - Dispose(); + // The Stop method is first called with the immediate parameter set to false. The object can either complete + // processing, call the UnregisterObject method, and then return or it can return immediately and complete + // processing asynchronously before calling the UnregisterObject method. + + LogHelper.Debug>("Shutting down, waiting for tasks to complete."); + Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait + + lock (_locker) + { + if (_runningTask != null) + _runningTask.ContinueWith(_ => + { + HostingEnvironment.UnregisterObject(this); + LogHelper.Info>("Down, tasks completed."); + }); + else + { + HostingEnvironment.UnregisterObject(this); + LogHelper.Info>("Down, tasks completed."); + } + } } else { - //NOTE: this will thread block the current operation if the manager - // is still shutting down because the Shutdown operation is also locked - // by this same lock instance. This would only matter if Stop is called by ASP.Net - // on two different threads though, otherwise the current thread will just block normally - // until the app is shutdown - lock (Locker) - { - LogHelper.Info>("Application is shutting down immediately"); - } + // If the registered object does not complete processing before the application manager's time-out + // period expires, the Stop method is called again with the immediate parameter set to true. When the + // immediate parameter is true, the registered object must call the UnregisterObject method before returning; + // otherwise, its registration will be removed by the application manager. + + LogHelper.Info>("Shutting down immediately."); + Shutdown(true, true); // cancel all tasks, wait for the current one to end + HostingEnvironment.UnregisterObject(this); + LogHelper.Info>("Down."); } - } - } } diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs new file mode 100644 index 0000000000..4688ff37d6 --- /dev/null +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs @@ -0,0 +1,44 @@ +namespace Umbraco.Web.Scheduling +{ + /// + /// Provides options to the class. + /// + internal class BackgroundTaskRunnerOptions + { + //TODO: Could add options for using a stack vs queue if required + + /// + /// Initializes a new instance of the class. + /// + public BackgroundTaskRunnerOptions() + { + LongRunning = false; + KeepAlive = false; + AutoStart = false; + } + + /// + /// Gets or sets a value indicating whether the running task should be a long-running, + /// coarse grained operation. + /// + public bool LongRunning { get; set; } + + /// + /// Gets or sets a value indicating whether the running task should block and wait + /// on the queue, or end, when the queue is empty. + /// + public bool KeepAlive { get; set; } + + /// + /// Gets or sets a value indicating whether the running task should start immediately + /// or only once a task has been added to the queue. + /// + public bool AutoStart { get; set; } + + /// + /// Gets or setes a value indicating whether the running task should be preserved + /// once completed, or reset to null. For unit tests. + /// + public bool PreserveRunningTask { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/DelayedRecurringTaskBase.cs b/src/Umbraco.Web/Scheduling/DelayedRecurringTaskBase.cs new file mode 100644 index 0000000000..f7cec0079b --- /dev/null +++ b/src/Umbraco.Web/Scheduling/DelayedRecurringTaskBase.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Web.Scheduling +{ + /// + /// Provides a base class for recurring background tasks. + /// + /// The type of the managed tasks. + internal abstract class DelayedRecurringTaskBase : RecurringTaskBase, ILatchedBackgroundTask + where T : class, IBackgroundTask + { + private readonly ManualResetEventSlim _latch; + private Timer _timer; + + protected DelayedRecurringTaskBase(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds) + : base(runner, periodMilliseconds) + { + if (delayMilliseconds > 0) + { + _latch = new ManualResetEventSlim(false); + _timer = new Timer(_ => + { + _timer.Dispose(); + _timer = null; + _latch.Set(); + }); + _timer.Change(delayMilliseconds, 0); + } + } + + protected DelayedRecurringTaskBase(DelayedRecurringTaskBase source) + : base(source) + { + // no latch on recurring instances + _latch = null; + } + + public override void Run() + { + if (_latch != null) + _latch.Dispose(); + base.Run(); + } + + public override async Task RunAsync(CancellationToken token) + { + if (_latch != null) + _latch.Dispose(); + await base.RunAsync(token); + } + + public WaitHandle Latch + { + get + { + if (_latch == null) + throw new InvalidOperationException("The task is not latched."); + return _latch.WaitHandle; + } + } + + public bool IsLatched + { + get { return _latch != null; } + } + + public virtual bool RunsOnShutdown + { + get { return true; } + } + } +} diff --git a/src/Umbraco.Web/Scheduling/IBackgroundTask.cs b/src/Umbraco.Web/Scheduling/IBackgroundTask.cs index 343f076b2a..4e646c0623 100644 --- a/src/Umbraco.Web/Scheduling/IBackgroundTask.cs +++ b/src/Umbraco.Web/Scheduling/IBackgroundTask.cs @@ -1,9 +1,30 @@ using System; +using System.Threading; +using System.Threading.Tasks; namespace Umbraco.Web.Scheduling { + /// + /// Represents a background task. + /// internal interface IBackgroundTask : IDisposable { + /// + /// Runs the background task. + /// void Run(); + + /// + /// Runs the task asynchronously. + /// + /// A cancellation token. + /// A instance representing the execution of the background task. + /// The background task cannot run asynchronously. + Task RunAsync(CancellationToken token); + + /// + /// Indicates whether the background task can run asynchronously. + /// + bool IsAsync { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/IBackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/IBackgroundTaskRunner.cs new file mode 100644 index 0000000000..c4e2dab35d --- /dev/null +++ b/src/Umbraco.Web/Scheduling/IBackgroundTaskRunner.cs @@ -0,0 +1,20 @@ +using System; +using System.Web.Hosting; + +namespace Umbraco.Web.Scheduling +{ + /// + /// Defines a service managing a queue of tasks of type and running them in the background. + /// + /// The type of the managed tasks. + /// The interface is not complete and exists only to have the contravariance on T. + internal interface IBackgroundTaskRunner : IDisposable, IRegisteredObject + where T : class, IBackgroundTask + { + bool IsCompleted { get; } + void Add(T task); + bool TryAdd(T task); + + // fixme - complete the interface? + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/ILatchedBackgroundTask.cs b/src/Umbraco.Web/Scheduling/ILatchedBackgroundTask.cs new file mode 100644 index 0000000000..0ad4d42bdf --- /dev/null +++ b/src/Umbraco.Web/Scheduling/ILatchedBackgroundTask.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading; + +namespace Umbraco.Web.Scheduling +{ + /// + /// Represents a latched background task. + /// + /// Latched background tasks can suspend their execution until + /// a condition is met. However if the tasks runner has to terminate, + /// latched background tasks can be executed immediately, depending on + /// the value returned by RunsOnShutdown. + internal interface ILatchedBackgroundTask : IBackgroundTask + { + /// + /// Gets a wait handle on the task condition. + /// + /// The task is not latched. + WaitHandle Latch { get; } + + /// + /// Gets a value indicating whether the task is latched. + /// + bool IsLatched { get; } + + /// + /// Gets a value indicating whether the task can be executed immediately if the task runner has to terminate. + /// + bool RunsOnShutdown { get; } + } +} diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index 1c7a8f3537..c4a5ce2c00 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using System.Web; using System.Web.Caching; using umbraco.BusinessLogic; @@ -8,17 +9,31 @@ using Umbraco.Core.Logging; namespace Umbraco.Web.Scheduling { - internal class LogScrubber : DisposableObject, IBackgroundTask + internal class LogScrubber : DelayedRecurringTaskBase { private readonly ApplicationContext _appContext; private readonly IUmbracoSettingsSection _settings; - public LogScrubber(ApplicationContext appContext, IUmbracoSettingsSection settings) + public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + ApplicationContext appContext, IUmbracoSettingsSection settings) + : base(runner, delayMilliseconds, periodMilliseconds) { _appContext = appContext; _settings = settings; } + public LogScrubber(LogScrubber source) + : base(source) + { + _appContext = source._appContext; + _settings = source._settings; + } + + protected override LogScrubber GetRecurring() + { + return new LogScrubber(this); + } + private int GetLogScrubbingMaximumAge(IUmbracoSettingsSection settings) { int maximumAge = 24 * 60 * 60; @@ -35,19 +50,42 @@ namespace Umbraco.Web.Scheduling } - /// - /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. - /// - protected override void DisposeResources() - { + public static int GetLogScrubbingInterval(IUmbracoSettingsSection settings) + { + int interval = 24 * 60 * 60; //24 hours + try + { + if (settings.Logging.CleaningMiliseconds > -1) + interval = settings.Logging.CleaningMiliseconds; + } + catch (Exception e) + { + LogHelper.Error("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e); + } + return interval; } - public void Run() + public override void PerformRun() { using (DisposableTimer.DebugDuration(() => "Log scrubbing executing", () => "Log scrubbing complete")) { Log.CleanLogs(GetLogScrubbingMaximumAge(_settings)); } } + + public override Task PerformRunAsync() + { + throw new NotImplementedException(); + } + + public override bool IsAsync + { + get { return false; } + } + + public override bool RunsOnShutdown + { + get { return false; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs new file mode 100644 index 0000000000..d710a70e03 --- /dev/null +++ b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs @@ -0,0 +1,115 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Web.Scheduling +{ + /// + /// Provides a base class for recurring background tasks. + /// + /// The type of the managed tasks. + internal abstract class RecurringTaskBase : IBackgroundTask + where T : class, IBackgroundTask + { + private readonly IBackgroundTaskRunner _runner; + private readonly int _periodMilliseconds; + private Timer _timer; + private T _recurrent; + + /// + /// Initializes a new instance of the class with a tasks runner and a period. + /// + /// The task runner. + /// The period. + /// The task will repeat itself periodically. Use this constructor to create a new task. + protected RecurringTaskBase(IBackgroundTaskRunner runner, int periodMilliseconds) + { + _runner = runner; + _periodMilliseconds = periodMilliseconds; + } + + /// + /// Initializes a new instance of the class with a source task. + /// + /// The source task. + /// Use this constructor to create a new task from a source task in GetRecurring. + protected RecurringTaskBase(RecurringTaskBase source) + { + _runner = source._runner; + _timer = source._timer; + _periodMilliseconds = source._periodMilliseconds; + } + + /// + /// Implements IBackgroundTask.Run(). + /// + /// Classes inheriting from RecurringTaskBase must implement PerformRun. + public virtual void Run() + { + PerformRun(); + Repeat(); + } + + /// + /// Implements IBackgroundTask.RunAsync(). + /// + /// Classes inheriting from RecurringTaskBase must implement PerformRun. + public virtual async Task RunAsync(CancellationToken token) + { + await PerformRunAsync(); + Repeat(); + } + + private void Repeat() + { + // again? + if (_runner.IsCompleted) return; // fail fast + + if (_periodMilliseconds == 0) return; + + _recurrent = GetRecurring(); + if (_recurrent == null) + { + _timer.Dispose(); + _timer = null; + return; // done + } + + // note + // must use the single-parameter constructor on Timer to avoid it from being GC'd + // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer + + _timer = _timer ?? new Timer(_ => _runner.TryAdd(_recurrent)); + _timer.Change(_periodMilliseconds, 0); + } + + /// + /// Indicates whether the background task can run asynchronously. + /// + public abstract bool IsAsync { get; } + + /// + /// Runs the background task. + /// + public abstract void PerformRun(); + + /// + /// Runs the task asynchronously. + /// + /// A instance representing the execution of the background task. + public abstract Task PerformRunAsync(); + + /// + /// Gets a new occurence of the recurring task. + /// + /// A new task instance to be queued, or null to terminate the recurring task. + /// The new task instance must be created via the RecurringTaskBase(RecurringTaskBase{T} source) constructor, + /// where source is the current task, eg: return new MyTask(this); + protected abstract T GetRecurring(); + + /// + /// Dispose the task. + /// + public virtual void Dispose() + { } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 8a64098764..9db21fba8a 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.Net; using System.Text; +using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -11,30 +12,41 @@ using Umbraco.Web.Mvc; namespace Umbraco.Web.Scheduling { - internal class ScheduledPublishing : DisposableObject, IBackgroundTask + internal class ScheduledPublishing : DelayedRecurringTaskBase { private readonly ApplicationContext _appContext; private readonly IUmbracoSettingsSection _settings; - private static bool _isPublishingRunning = false; + private static bool _isPublishingRunning; - public ScheduledPublishing(ApplicationContext appContext, IUmbracoSettingsSection settings) + public ScheduledPublishing(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + ApplicationContext appContext, IUmbracoSettingsSection settings) + : base(runner, delayMilliseconds, periodMilliseconds) { _appContext = appContext; _settings = settings; } - - /// - /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. - /// - protected override void DisposeResources() + private ScheduledPublishing(ScheduledPublishing source) + : base(source) { + _appContext = source._appContext; + _settings = source._settings; } - public void Run() + protected override ScheduledPublishing GetRecurring() + { + return new ScheduledPublishing(this); + } + + public override void PerformRun() { if (_appContext == null) return; + if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave) + { + LogHelper.Debug("Does not run on slave servers."); + return; + } using (DisposableTimer.DebugDuration(() => "Scheduled publishing executing", () => "Scheduled publishing complete")) { @@ -75,5 +87,20 @@ namespace Umbraco.Web.Scheduling } } } + + public override Task PerformRunAsync() + { + throw new NotImplementedException(); + } + + public override bool IsAsync + { + get { return false; } + } + + public override bool RunsOnShutdown + { + get { return false; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs index 94c035631f..cba3cb4fc8 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Linq; using System.Net; +using System.Threading.Tasks; using System.Xml; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; @@ -16,19 +17,33 @@ namespace Umbraco.Web.Scheduling // would need to be a publicly available task (URL) which isn't really very good :( // We should really be using the AdminTokenAuthorizeAttribute for this stuff - internal class ScheduledTasks : DisposableObject, IBackgroundTask + internal class ScheduledTasks : DelayedRecurringTaskBase { private readonly ApplicationContext _appContext; private readonly IUmbracoSettingsSection _settings; private static readonly Hashtable ScheduledTaskTimes = new Hashtable(); private static bool _isPublishingRunning = false; - public ScheduledTasks(ApplicationContext appContext, IUmbracoSettingsSection settings) + public ScheduledTasks(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + ApplicationContext appContext, IUmbracoSettingsSection settings) + : base(runner, delayMilliseconds, periodMilliseconds) { _appContext = appContext; _settings = settings; } + public ScheduledTasks(ScheduledTasks source) + : base(source) + { + _appContext = source._appContext; + _settings = source._settings; + } + + protected override ScheduledTasks GetRecurring() + { + return new ScheduledTasks(this); + } + private void ProcessTasks() { var scheduledTasks = _settings.ScheduledTasks.Tasks; @@ -77,15 +92,14 @@ namespace Umbraco.Web.Scheduling return false; } - /// - /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. - /// - protected override void DisposeResources() + public override void PerformRun() { - } + if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave) + { + LogHelper.Debug("Does not run on slave servers."); + return; + } - public void Run() - { using (DisposableTimer.DebugDuration(() => "Scheduled tasks executing", () => "Scheduled tasks complete")) { if (_isPublishingRunning) return; @@ -106,5 +120,20 @@ namespace Umbraco.Web.Scheduling } } } + + public override Task PerformRunAsync() + { + throw new NotImplementedException(); + } + + public override bool IsAsync + { + get { return false; } + } + + public override bool RunsOnShutdown + { + get { return false; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs index 5ab0760100..8a25e356f6 100644 --- a/src/Umbraco.Web/Scheduling/Scheduler.cs +++ b/src/Umbraco.Web/Scheduling/Scheduler.cs @@ -19,12 +19,10 @@ namespace Umbraco.Web.Scheduling internal sealed class Scheduler : ApplicationEventHandler { private static Timer _pingTimer; - private static Timer _schedulingTimer; - private static BackgroundTaskRunner _publishingRunner; - private static BackgroundTaskRunner _tasksRunner; - private static BackgroundTaskRunner _scrubberRunner; - private static Timer _logScrubberTimer; - private static volatile bool _started = false; + private static BackgroundTaskRunner _publishingRunner; + private static BackgroundTaskRunner _tasksRunner; + private static BackgroundTaskRunner _scrubberRunner; + private static volatile bool _started; private static readonly object Locker = new object(); /// @@ -54,97 +52,37 @@ namespace Umbraco.Web.Scheduling _started = true; LogHelper.Debug(() => "Initializing the scheduler"); - // time to setup the tasks + // backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly + _publishingRunner = new BackgroundTaskRunner(); + _tasksRunner = new BackgroundTaskRunner(); + _scrubberRunner = new BackgroundTaskRunner(); - //We have 3 background runners that are web aware, if the app domain dies, these tasks will wind down correctly - _publishingRunner = new BackgroundTaskRunner(); - _tasksRunner = new BackgroundTaskRunner(); - _scrubberRunner = new BackgroundTaskRunner(); + var settings = UmbracoConfig.For.UmbracoSettings(); - //NOTE: It is important to note that we need to use the ctor for a timer without the 'state' object specified, this is in order - // to ensure that the timer itself is not GC'd since internally .net will pass itself in as the state object and that will keep it alive. - // There's references to this here: http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer - // we also make these timers static to ensure further GC safety. + // note + // must use the single-parameter constructor on Timer to avoid it from being GC'd + // also make the timer static to ensure further GC safety + // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer - // ping/keepalive - NOTE: we don't use a background runner for this because it does not need to be web aware, if the app domain dies, no problem + // ping/keepalive - no need for a background runner - does not need to be web aware, ok if the app domain dies _pingTimer = new Timer(state => KeepAlive.Start(applicationContext, UmbracoConfig.For.UmbracoSettings())); _pingTimer.Change(60000, 300000); // scheduled publishing/unpublishing - _schedulingTimer = new Timer(state => PerformScheduling(applicationContext, UmbracoConfig.For.UmbracoSettings())); - _schedulingTimer.Change(60000, 60000); + // install on all, will only run on non-slaves servers + // both are delayed recurring tasks + _publishingRunner.Add(new ScheduledPublishing(_publishingRunner, 60000, 60000, applicationContext, settings)); + _tasksRunner.Add(new ScheduledTasks(_tasksRunner, 60000, 60000, applicationContext, settings)); - //log scrubbing - _logScrubberTimer = new Timer(state => PerformLogScrub(applicationContext, UmbracoConfig.For.UmbracoSettings())); - _logScrubberTimer.Change(60000, GetLogScrubbingInterval(UmbracoConfig.For.UmbracoSettings())); + // log scrubbing + // install & run on all servers + // LogScrubber is a delayed recurring task + _scrubberRunner.Add(new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings), applicationContext, settings)); } } } }; }; } - - - private int GetLogScrubbingInterval(IUmbracoSettingsSection settings) - { - int interval = 24 * 60 * 60; //24 hours - try - { - if (settings.Logging.CleaningMiliseconds > -1) - interval = settings.Logging.CleaningMiliseconds; - } - catch (Exception e) - { - LogHelper.Error("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e); - } - return interval; - } - - private static void PerformLogScrub(ApplicationContext appContext, IUmbracoSettingsSection settings) - { - _scrubberRunner.Add(new LogScrubber(appContext, settings)); - } - - /// - /// This performs all of the scheduling on the one timer - /// - /// - /// - /// - /// No processing will be done if this server is a slave - /// - private static void PerformScheduling(ApplicationContext appContext, IUmbracoSettingsSection settings) - { - using (DisposableTimer.DebugDuration(() => "Scheduling interval executing", () => "Scheduling interval complete")) - { - //get the current server status to see if this server should execute the scheduled publishing - var serverStatus = ServerEnvironmentHelper.GetStatus(settings); - - switch (serverStatus) - { - case CurrentServerEnvironmentStatus.Single: - case CurrentServerEnvironmentStatus.Master: - case CurrentServerEnvironmentStatus.Unknown: - //if it's a single server install, a master or it cannot be determined - // then we will process the scheduling - - //do the scheduled publishing - _publishingRunner.Add(new ScheduledPublishing(appContext, settings)); - - //do the scheduled tasks - _tasksRunner.Add(new ScheduledTasks(appContext, settings)); - - break; - case CurrentServerEnvironmentStatus.Slave: - //do not process - - LogHelper.Debug( - () => string.Format("Current server ({0}) detected as a slave, no scheduled processes will execute on this server", NetworkHelper.MachineName)); - - break; - } - } - } - } } diff --git a/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs b/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs new file mode 100644 index 0000000000..a57ea904b0 --- /dev/null +++ b/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Web.Scheduling +{ + internal static class TaskAndFactoryExtensions + { + #region Task Extensions + + static void SetCompletionSource(TaskCompletionSource completionSource, Task task) + { + if (task.IsFaulted) + completionSource.SetException(task.Exception.InnerException); + else + completionSource.SetResult(default(TResult)); + } + + static void SetCompletionSource(TaskCompletionSource completionSource, Task task) + { + if (task.IsFaulted) + completionSource.SetException(task.Exception.InnerException); + else + completionSource.SetResult(task.Result); + } + + public static Task ContinueWithTask(this Task task, Func continuation) + { + var completionSource = new TaskCompletionSource(); + task.ContinueWith(atask => continuation(atask).ContinueWith(atask2 => SetCompletionSource(completionSource, atask2))); + return completionSource.Task; + } + + public static Task ContinueWithTask(this Task task, Func continuation, CancellationToken token) + { + var completionSource = new TaskCompletionSource(); + task.ContinueWith(atask => continuation(atask).ContinueWith(atask2 => SetCompletionSource(completionSource, atask2), token), token); + return completionSource.Task; + } + + #endregion + + #region TaskFactory Extensions + + public static Task Completed(this TaskFactory factory) + { + var taskSource = new TaskCompletionSource(); + taskSource.SetResult(null); + return taskSource.Task; + } + + public static Task Sync(this TaskFactory factory, Action action) + { + var taskSource = new TaskCompletionSource(); + action(); + taskSource.SetResult(null); + return taskSource.Task; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index f066476948..3284674338 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -23,23 +23,43 @@ namespace Umbraco.Web.Security /// public class MembershipHelper { + private readonly MembershipProvider _membershipProvider; + private readonly RoleProvider _roleProvider; private readonly ApplicationContext _applicationContext; private readonly HttpContextBase _httpContext; #region Constructors public MembershipHelper(ApplicationContext applicationContext, HttpContextBase httpContext) + : this(applicationContext, httpContext, MPE.GetMembersMembershipProvider(), Roles.Provider) + { + } + + public MembershipHelper(ApplicationContext applicationContext, HttpContextBase httpContext, MembershipProvider membershipProvider, RoleProvider roleProvider) { if (applicationContext == null) throw new ArgumentNullException("applicationContext"); if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (membershipProvider == null) throw new ArgumentNullException("membershipProvider"); + if (roleProvider == null) throw new ArgumentNullException("roleProvider"); _applicationContext = applicationContext; _httpContext = httpContext; + _membershipProvider = membershipProvider; + _roleProvider = roleProvider; } public MembershipHelper(UmbracoContext umbracoContext) + : this(umbracoContext, MPE.GetMembersMembershipProvider(), Roles.Provider) + { + } + + public MembershipHelper(UmbracoContext umbracoContext, MembershipProvider membershipProvider, RoleProvider roleProvider) { if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (membershipProvider == null) throw new ArgumentNullException("membershipProvider"); + if (roleProvider == null) throw new ArgumentNullException("roleProvider"); _httpContext = umbracoContext.HttpContext; _applicationContext = umbracoContext.Application; + _membershipProvider = membershipProvider; + _roleProvider = roleProvider; } #endregion @@ -49,7 +69,7 @@ namespace Umbraco.Web.Security /// public bool IsUmbracoMembershipProviderActive() { - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; return provider.IsUmbracoMembershipProvider(); } @@ -60,7 +80,7 @@ namespace Umbraco.Web.Security /// /// The updated MembershipUser object /// - public Attempt UpdateMemberProfile(ProfileModel model) + public virtual Attempt UpdateMemberProfile(ProfileModel model) { if (IsLoggedIn() == false) { @@ -68,7 +88,7 @@ namespace Umbraco.Web.Security } //get the current membership user - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; var membershipUser = provider.GetCurrentUser(); //NOTE: This should never happen since they are logged in if (membershipUser == null) throw new InvalidOperationException("Could not find member with username " + _httpContext.User.Identity.Name); @@ -130,12 +150,12 @@ namespace Umbraco.Web.Security /// true to log the member in upon successful registration /// /// - public MembershipUser RegisterMember(RegisterModel model, out MembershipCreateStatus status, bool logMemberIn = true) + public virtual MembershipUser RegisterMember(RegisterModel model, out MembershipCreateStatus status, bool logMemberIn = true) { model.Username = (model.UsernameIsEmail || model.Username == null) ? model.Email : model.Username; MembershipUser membershipUser; - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; //update their real name if (provider.IsUmbracoMembershipProvider()) { @@ -190,9 +210,9 @@ namespace Umbraco.Web.Security /// /// /// - public bool Login(string username, string password) + public virtual bool Login(string username, string password) { - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; //Validate credentials if (provider.ValidateUser(username, password) == false) { @@ -214,19 +234,19 @@ namespace Umbraco.Web.Security /// /// Logs out the current member /// - public void Logout() + public virtual void Logout() { FormsAuthentication.SignOut(); } #region Querying for front-end - public IPublishedContent GetByProviderKey(object key) + public virtual IPublishedContent GetByProviderKey(object key) { return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( GetCacheKey("GetByProviderKey", key), () => { - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; if (provider.IsUmbracoMembershipProvider() == false) { throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); @@ -237,12 +257,12 @@ namespace Umbraco.Web.Security }); } - public IPublishedContent GetById(int memberId) + public virtual IPublishedContent GetById(int memberId) { return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( GetCacheKey("GetById", memberId), () => { - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; if (provider.IsUmbracoMembershipProvider() == false) { throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); @@ -253,12 +273,12 @@ namespace Umbraco.Web.Security }); } - public IPublishedContent GetByUsername(string username) + public virtual IPublishedContent GetByUsername(string username) { return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( GetCacheKey("GetByUsername", username), () => { - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; if (provider.IsUmbracoMembershipProvider() == false) { throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); @@ -269,12 +289,12 @@ namespace Umbraco.Web.Security }); } - public IPublishedContent GetByEmail(string email) + public virtual IPublishedContent GetByEmail(string email) { return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( GetCacheKey("GetByEmail", email), () => { - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; if (provider.IsUmbracoMembershipProvider() == false) { throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); @@ -289,7 +309,7 @@ namespace Umbraco.Web.Security /// Returns the currently logged in member as IPublishedContent /// /// - public IPublishedContent GetCurrentMember() + public virtual IPublishedContent GetCurrentMember() { if (IsLoggedIn() == false) { @@ -321,14 +341,14 @@ namespace Umbraco.Web.Security /// profile properties /// /// - public ProfileModel GetCurrentMemberProfileModel() + public virtual ProfileModel GetCurrentMemberProfileModel() { if (IsLoggedIn() == false) { return null; } - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; if (provider.IsUmbracoMembershipProvider()) { @@ -380,9 +400,9 @@ namespace Umbraco.Web.Security /// /// /// - public RegisterModel CreateRegistrationModel(string memberTypeAlias = null) + public virtual RegisterModel CreateRegistrationModel(string memberTypeAlias = null) { - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; if (provider.IsUmbracoMembershipProvider()) { memberTypeAlias = memberTypeAlias ?? Constants.Conventions.MemberTypes.DefaultAlias; @@ -466,7 +486,7 @@ namespace Umbraco.Web.Security /// Returns the login status model of the currently logged in member, if no member is logged in it returns null; /// /// - public LoginStatusModel GetCurrentLoginStatus() + public virtual LoginStatusModel GetCurrentLoginStatus() { var model = LoginStatusModel.CreateModel(); @@ -476,7 +496,7 @@ namespace Umbraco.Web.Security return model; } - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; if (provider.IsUmbracoMembershipProvider()) { @@ -524,6 +544,14 @@ namespace Umbraco.Web.Security return _httpContext.User != null && _httpContext.User.Identity.IsAuthenticated; } + /// + /// Returns the currently logged in username + /// + public string CurrentUserName + { + get { return _httpContext.User.Identity.Name; } + } + /// /// Returns true or false if the currently logged in member is authorized based on the parameters provided /// @@ -532,7 +560,7 @@ namespace Umbraco.Web.Security /// /// /// - public bool IsMemberAuthorized( + public virtual bool IsMemberAuthorized( bool allowAll = false, IEnumerable allowTypes = null, IEnumerable allowGroups = null, @@ -558,7 +586,7 @@ namespace Umbraco.Web.Security } else { - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; string username; if (provider.IsUmbracoMembershipProvider()) @@ -591,7 +619,7 @@ namespace Umbraco.Web.Security if (allowAction && allowGroupsList.Any(allowGroup => allowGroup != string.Empty)) { // Allow only if member is assigned to a group in the list - var groups = Roles.GetRolesForUser(username); + var groups = _roleProvider.GetRolesForUser(username); allowAction = allowGroupsList.Select(s => s.ToLowerInvariant()).Intersect(groups.Select(myGroup => myGroup.ToLowerInvariant())).Any(); } @@ -608,7 +636,7 @@ namespace Umbraco.Web.Security /// /// /// - public Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, string membershipProviderName) + public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, string membershipProviderName) { var provider = Membership.Providers[membershipProviderName]; if (provider == null) @@ -625,7 +653,7 @@ namespace Umbraco.Web.Security /// /// /// - public Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) + public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { // YES! It is completely insane how many options you have to take into account based on the membership provider. yikes! @@ -818,7 +846,7 @@ namespace Umbraco.Web.Security return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( GetCacheKey("GetCurrentPersistedMember"), () => { - var provider = MPE.GetMembersMembershipProvider(); + var provider = _membershipProvider; if (provider.IsUmbracoMembershipProvider() == false) { diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 371f99f2c7..d16d22b946 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -26,10 +26,6 @@ namespace Umbraco.Web.Security { _httpContext = httpContext; _applicationContext = applicationContext; - //This ensures the dispose method is called when the request terminates, though - // we also ensure this happens in the Umbraco module because the UmbracoContext is added to the - // http context items. - _httpContext.DisposeOnPipelineCompleted(this); } /// @@ -61,7 +57,7 @@ namespace Umbraco.Web.Security /// Gets the current user. /// /// The current user. - public IUser CurrentUser + public virtual IUser CurrentUser { get { @@ -85,7 +81,7 @@ namespace Umbraco.Web.Security /// /// The user Id /// returns the number of seconds until their session times out - public double PerformLogin(int userId) + public virtual double PerformLogin(int userId) { var user = _applicationContext.Services.UserService.GetUserById(userId); return PerformLogin(user).GetRemainingAuthSeconds(); @@ -96,7 +92,7 @@ namespace Umbraco.Web.Security /// /// /// returns the number of seconds until their session times out - public FormsAuthenticationTicket PerformLogin(IUser user) + public virtual FormsAuthenticationTicket PerformLogin(IUser user) { var ticket = _httpContext.CreateUmbracoAuthTicket(new UserData(Guid.NewGuid().ToString("N")) { @@ -119,7 +115,7 @@ namespace Umbraco.Web.Security /// /// Clears the current login for the currently logged in user /// - public void ClearCurrentLogin() + public virtual void ClearCurrentLogin() { _httpContext.UmbracoLogout(); } @@ -127,7 +123,7 @@ namespace Umbraco.Web.Security /// /// Renews the user's login ticket /// - public void RenewLoginTimeout() + public virtual void RenewLoginTimeout() { _httpContext.RenewUmbracoAuthTicket(); } @@ -138,7 +134,7 @@ namespace Umbraco.Web.Security /// /// /// - public bool ValidateBackOfficeCredentials(string username, string password) + public virtual bool ValidateBackOfficeCredentials(string username, string password) { var membershipProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); return membershipProvider != null && membershipProvider.ValidateUser(username, password); @@ -150,7 +146,7 @@ namespace Umbraco.Web.Security /// /// /// - public MembershipUser GetBackOfficeMembershipUser(string username, bool setOnline) + public virtual MembershipUser GetBackOfficeMembershipUser(string username, bool setOnline) { var membershipProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); return membershipProvider != null ? membershipProvider.GetUser(username, setOnline) : null; @@ -275,7 +271,7 @@ namespace Umbraco.Web.Security /// Gets the currnet user's id. /// /// - public int GetUserId() + public virtual int GetUserId() { var identity = _httpContext.GetCurrentIdentity(false); if (identity == null) @@ -287,7 +283,7 @@ namespace Umbraco.Web.Security /// Returns the current user's unique session id - used to mitigate csrf attacks or any other reason to validate a request /// /// - public string GetSessionId() + public virtual string GetSessionId() { var identity = _httpContext.GetCurrentIdentity(false); if (identity == null) @@ -310,7 +306,7 @@ namespace Umbraco.Web.Security /// Validates the currently logged in user and ensures they are not timed out /// /// - public bool ValidateCurrentUser() + public virtual bool ValidateCurrentUser() { var result = ValidateCurrentUser(false); return result == ValidateRequestAttempt.Success; diff --git a/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs b/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs index 86b3066413..d543567ac3 100644 --- a/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs +++ b/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.Strategies { /// - /// Used to ensure that the access.xml file is kept up to date properly + /// Used to ensure that the public access data file is kept up to date properly /// public sealed class PublicAccessEventHandler : ApplicationEventHandler { @@ -31,7 +31,7 @@ namespace Umbraco.Web.Strategies && grp.AdditionalData["previousName"].ToString().IsNullOrWhiteSpace() == false && grp.AdditionalData["previousName"].ToString() != grp.Name) { - Access.RenameMemberShipRole(grp.AdditionalData["previousName"].ToString(), grp.Name); + ApplicationContext.Current.Services.PublicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"].ToString(), grp.Name); } } } diff --git a/src/Umbraco.Web/TagQuery.cs b/src/Umbraco.Web/TagQuery.cs index 58dcdc3947..ded4dea66e 100644 --- a/src/Umbraco.Web/TagQuery.cs +++ b/src/Umbraco.Web/TagQuery.cs @@ -11,10 +11,10 @@ namespace Umbraco.Web /// /// A class that exposes methods used to query tag data in views /// - public class TagQuery + public class TagQuery : ITagQuery { private readonly ITagService _tagService; - private readonly PublishedContentQuery _contentQuery; + private readonly ITypedPublishedContentQuery _typedContentQuery; [Obsolete("Use the alternate constructor specifying the contentQuery instead")] public TagQuery(ITagService tagService) @@ -22,12 +22,26 @@ namespace Umbraco.Web { } + [Obsolete("Use the alternate constructor specifying the ITypedPublishedContentQuery instead")] public TagQuery(ITagService tagService, PublishedContentQuery contentQuery) { if (tagService == null) throw new ArgumentNullException("tagService"); if (contentQuery == null) throw new ArgumentNullException("contentQuery"); _tagService = tagService; - _contentQuery = contentQuery; + _typedContentQuery = contentQuery; + } + + /// + /// Constructor + /// + /// + /// + public TagQuery(ITagService tagService, ITypedPublishedContentQuery typedContentQuery) + { + if (tagService == null) throw new ArgumentNullException("tagService"); + if (typedContentQuery == null) throw new ArgumentNullException("typedContentQuery"); + _tagService = tagService; + _typedContentQuery = typedContentQuery; } /// @@ -40,7 +54,7 @@ namespace Umbraco.Web { var ids = _tagService.GetTaggedContentByTag(tag, tagGroup) .Select(x => x.EntityId); - return _contentQuery.TypedContent(ids) + return _typedContentQuery.TypedContent(ids) .Where(x => x != null); } @@ -53,7 +67,7 @@ namespace Umbraco.Web { var ids = _tagService.GetTaggedContentByTagGroup(tagGroup) .Select(x => x.EntityId); - return _contentQuery.TypedContent(ids) + return _typedContentQuery.TypedContent(ids) .Where(x => x != null); } @@ -67,7 +81,7 @@ namespace Umbraco.Web { var ids = _tagService.GetTaggedMediaByTag(tag, tagGroup) .Select(x => x.EntityId); - return _contentQuery.TypedMedia(ids) + return _typedContentQuery.TypedMedia(ids) .Where(x => x != null); } @@ -80,7 +94,7 @@ namespace Umbraco.Web { var ids = _tagService.GetTaggedMediaByTagGroup(tagGroup) .Select(x => x.EntityId); - return _contentQuery.TypedMedia(ids) + return _typedContentQuery.TypedMedia(ids) .Where(x => x != null); } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 9ed37b29d6..50267f1d05 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -106,7 +106,7 @@ namespace Umbraco.Web.Trees if (entity.HasPendingChanges) node.SetHasUnpublishedVersionStyle(); - if (Access.IsProtected(e.Id, e.Path)) + if (Services.PublicAccessService.IsProtected(e.Path)) node.SetProtectedStyle(); return node; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 899afc71d6..4fd4be4854 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -275,7 +275,12 @@ + + + + + @@ -506,6 +511,12 @@ + + + + + + @@ -549,6 +560,8 @@ ASPXCodeBehind + + True True diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs new file mode 100644 index 0000000000..9e789ac0c5 --- /dev/null +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -0,0 +1,320 @@ +using System; +using System.Collections; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Xml.Linq; +using System.Xml.XPath; +using HtmlAgilityPack; +using Umbraco.Core; +using Umbraco.Core.Dictionary; +using Umbraco.Core.Dynamics; +using Umbraco.Core.Models; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Core.Xml; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; +using Umbraco.Web.Templates; +using umbraco; +using System.Collections.Generic; +using umbraco.cms.businesslogic.web; +using umbraco.presentation.templateControls; +using Umbraco.Core.Cache; + +namespace Umbraco.Web +{ + + /// + /// Methods used to render umbraco components as HTML in templates + /// + /// + /// Used by UmbracoHelper + /// + internal class UmbracoComponentRenderer : IUmbracoComponentRenderer + { + private readonly UmbracoContext _umbracoContext; + + public UmbracoComponentRenderer(UmbracoContext umbracoContext) + { + _umbracoContext = umbracoContext; + } + + /// + /// Renders the template for the specified pageId and an optional altTemplateId + /// + /// + /// If not specified, will use the template assigned to the node + /// + public IHtmlString RenderTemplate(int pageId, int? altTemplateId = null) + { + var templateRenderer = new TemplateRenderer(_umbracoContext, pageId, altTemplateId); + using (var sw = new StringWriter()) + { + try + { + templateRenderer.Render(sw); + } + catch (Exception ex) + { + sw.Write("", pageId, ex); + } + return new HtmlString(sw.ToString()); + } + } + + /// + /// Renders the macro with the specified alias. + /// + /// The alias. + /// + public IHtmlString RenderMacro(string alias) + { + return RenderMacro(alias, new { }); + } + + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The alias. + /// The parameters. + /// + public IHtmlString RenderMacro(string alias, object parameters) + { + return RenderMacro(alias, parameters.ToDictionary()); + } + + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The alias. + /// The parameters. + /// + public IHtmlString RenderMacro(string alias, IDictionary parameters) + { + + if (_umbracoContext.PublishedContentRequest == null) + { + throw new InvalidOperationException("Cannot render a macro when there is no current PublishedContentRequest."); + } + + return RenderMacro(alias, parameters, _umbracoContext.PublishedContentRequest.UmbracoPage); + } + + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The alias. + /// The parameters. + /// The legacy umbraco page object that is required for some macros + /// + internal IHtmlString RenderMacro(string alias, IDictionary parameters, page umbracoPage) + { + if (alias == null) throw new ArgumentNullException("alias"); + if (umbracoPage == null) throw new ArgumentNullException("umbracoPage"); + + var m = macro.GetMacro(alias); + if (m == null) + { + throw new KeyNotFoundException("Could not find macro with alias " + alias); + } + + return RenderMacro(m, parameters, umbracoPage); + } + + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The macro. + /// The parameters. + /// The legacy umbraco page object that is required for some macros + /// + internal IHtmlString RenderMacro(macro m, IDictionary parameters, page umbracoPage) + { + if (umbracoPage == null) throw new ArgumentNullException("umbracoPage"); + if (m == null) throw new ArgumentNullException("m"); + + if (_umbracoContext.PageId == null) + { + throw new InvalidOperationException("Cannot render a macro when UmbracoContext.PageId is null."); + } + + var macroProps = new Hashtable(); + foreach (var i in parameters) + { + //TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method of macro.cs + // looks for a lower case match. WTF. the whole macro concept needs to be rewritten. + + + //NOTE: the value could have html encoded values, so we need to deal with that + macroProps.Add(i.Key.ToLowerInvariant(), (i.Value is string) ? HttpUtility.HtmlDecode(i.Value.ToString()) : i.Value); + } + var macroControl = m.renderMacro(macroProps, + umbracoPage.Elements, + _umbracoContext.PageId.Value); + + string html; + if (macroControl is LiteralControl) + { + // no need to execute, we already have text + html = (macroControl as LiteralControl).Text; + } + else + { + var containerPage = new FormlessPage(); + containerPage.Controls.Add(macroControl); + + using (var output = new StringWriter()) + { + // .Execute() does a PushTraceContext/PopTraceContext and writes trace output straight into 'output' + // and I do not see how we could wire the trace context to the current context... so it creates dirty + // trace output right in the middle of the page. + // + // The only thing we can do is fully disable trace output while .Execute() runs and restore afterwards + // which means trace output is lost if the macro is a control (.ascx or user control) that is invoked + // from within Razor -- which makes sense anyway because the control can _not_ run correctly from + // within Razor since it will never be inserted into the page pipeline (which may even not exist at all + // if we're running MVC). + // + // I'm sure there's more things that will get lost with this context changing but I guess we'll figure + // those out as we go along. One thing we lose is the content type response output. + // http://issues.umbraco.org/issue/U4-1599 if it is setup during the macro execution. So + // here we'll save the content type response and reset it after execute is called. + + var contentType = _umbracoContext.HttpContext.Response.ContentType; + var traceIsEnabled = containerPage.Trace.IsEnabled; + containerPage.Trace.IsEnabled = false; + _umbracoContext.HttpContext.Server.Execute(containerPage, output, true); + containerPage.Trace.IsEnabled = traceIsEnabled; + //reset the content type + _umbracoContext.HttpContext.Response.ContentType = contentType; + + //Now, we need to ensure that local links are parsed + html = TemplateUtilities.ParseInternalLinks(output.ToString()); + } + } + + return new HtmlString(html); + } + + /// + /// Renders an field to the template + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + //// + /// + public IHtmlString Field(IPublishedContent currentPage, string fieldAlias, + string altFieldAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", + bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, + RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, + RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, + bool formatAsDate = false, + bool formatAsDateWithTime = false, + string formatAsDateWithTimeSeparator = "") + + //TODO: commented out until as it is not implemented by umbraco:item yet + //,string formatString = "") + { + Mandate.ParameterNotNull(currentPage, "currentPage"); + Mandate.ParameterNotNullOrEmpty(fieldAlias, "fieldAlias"); + + //TODO: This is real nasty and we should re-write the 'item' and 'ItemRenderer' class but si fine for now + + var attributes = new Dictionary + { + {"field", fieldAlias}, + {"recursive", recursive.ToString().ToLowerInvariant()}, + {"useifempty", altFieldAlias}, + {"textifempty", altText}, + {"stripparagraph", removeParagraphTags.ToString().ToLowerInvariant()}, + { + "case", casing == RenderFieldCaseType.Lower ? "lower" + : casing == RenderFieldCaseType.Upper ? "upper" + : casing == RenderFieldCaseType.Title ? "title" + : string.Empty + }, + {"inserttextbefore", insertBefore}, + {"inserttextafter", insertAfter}, + {"convertlinebreaks", convertLineBreaks.ToString().ToLowerInvariant()}, + {"formatasdate", formatAsDate.ToString().ToLowerInvariant()}, + {"formatasdatewithtime", formatAsDateWithTime.ToString().ToLowerInvariant()}, + {"formatasdatewithtimeseparator", formatAsDateWithTimeSeparator} + }; + switch (encoding) + { + case RenderFieldEncodingType.Url: + attributes.Add("urlencode", "true"); + break; + case RenderFieldEncodingType.Html: + attributes.Add("htmlencode", "true"); + break; + case RenderFieldEncodingType.Unchanged: + default: + break; + } + + //need to convert our dictionary over to this weird dictionary type + var attributesForItem = new AttributeCollectionAdapter( + new AttributeCollection( + new StateBag())); + foreach (var i in attributes) + { + attributesForItem.Add(i.Key, i.Value); + } + + + + var item = new Item(currentPage) + { + Field = fieldAlias, + TextIfEmpty = altText, + LegacyAttributes = attributesForItem + }; + + //here we are going to check if we are in the context of an Umbraco routed page, if we are we + //will leave the NodeId empty since the underlying ItemRenderer will work ever so slightly faster + //since it already knows about the current page. Otherwise, we'll assign the id based on our + //currently assigned node. The PublishedContentRequest will be null if: + // * we are rendering a partial view or child action + // * we are rendering a view from a custom route + if ((_umbracoContext.PublishedContentRequest == null + || _umbracoContext.PublishedContentRequest.PublishedContent.Id != currentPage.Id) + && currentPage.Id > 0) // in case we're rendering a detached content (id == 0) + { + item.NodeId = currentPage.Id.ToString(CultureInfo.InvariantCulture); + } + + + var containerPage = new FormlessPage(); + containerPage.Controls.Add(item); + + using (var output = new StringWriter()) + using (var htmlWriter = new HtmlTextWriter(output)) + { + ItemRenderer.Instance.Init(item); + ItemRenderer.Instance.Load(item); + ItemRenderer.Instance.Render(item, htmlWriter); + + //because we are rendering the output through the legacy Item (webforms) stuff, the {localLinks} will already be replaced. + return new HtmlString(output.ToString()); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index efbe290f15..e3f86b8eb0 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -84,139 +84,18 @@ namespace Umbraco.Web bool replaceContext, bool? preview) { - return EnsureContext(httpContext, applicationContext, webSecurity, UmbracoConfig.For.UmbracoSettings(), replaceContext, preview); + return EnsureContext(httpContext, applicationContext, webSecurity, UmbracoConfig.For.UmbracoSettings(), UrlProviderResolver.Current.Providers, replaceContext, preview); } #endregion /// - /// This is a helper method which is called to ensure that the singleton context is created and the nice url and routing - /// context is created and assigned. - /// - /// - /// - /// - /// - /// - /// The Singleton context object - /// - /// - /// This is created in order to standardize the creation of the singleton. Normally it is created during a request - /// in the UmbracoModule, however this module does not execute during application startup so we need to ensure it - /// during the startup process as well. - /// See: http://issues.umbraco.org/issue/U4-1890, http://issues.umbraco.org/issue/U4-1717 - /// - public static UmbracoContext EnsureContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, - WebSecurity webSecurity, - IUmbracoSettingsSection umbracoSettings) - { - return EnsureContext(httpContext, applicationContext, webSecurity, umbracoSettings, false); - } - - - - /// - /// This is a helper method which is called to ensure that the singleton context is created and the nice url and routing - /// context is created and assigned. - /// - /// - /// - /// - /// - /// The Singleton context object - /// - /// - /// This is created in order to standardize the creation of the singleton. Normally it is created during a request - /// in the UmbracoModule, however this module does not execute during application startup so we need to ensure it - /// during the startup process as well. - /// See: http://issues.umbraco.org/issue/U4-1890, http://issues.umbraco.org/issue/U4-1717 - /// - public static UmbracoContext EnsureContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, - IUmbracoSettingsSection umbracoSettings) - { - return EnsureContext(httpContext, applicationContext, new WebSecurity(httpContext, applicationContext), umbracoSettings, false); - } - - - - /// - /// This is a helper method which is called to ensure that the singleton context is created and the nice url and routing - /// context is created and assigned. - /// - /// - /// - /// - /// - /// if set to true will replace the current singleton with a new one, this is generally only ever used because - /// during application startup the base url domain will not be available so after app startup we'll replace the current - /// context with a new one in which we can access the httpcontext.Request object. - /// - /// - /// The Singleton context object - /// - /// - /// This is created in order to standardize the creation of the singleton. Normally it is created during a request - /// in the UmbracoModule, however this module does not execute during application startup so we need to ensure it - /// during the startup process as well. - /// See: http://issues.umbraco.org/issue/U4-1890, http://issues.umbraco.org/issue/U4-1717 - /// - public static UmbracoContext EnsureContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, - IUmbracoSettingsSection umbracoSettings, - bool replaceContext) - { - return EnsureContext(httpContext, applicationContext, new WebSecurity(httpContext, applicationContext), umbracoSettings, replaceContext); - } - - - - - /// - /// This is a helper method which is called to ensure that the singleton context is created and the nice url and routing - /// context is created and assigned. - /// - /// - /// - /// - /// - /// - /// if set to true will replace the current singleton with a new one, this is generally only ever used because - /// during application startup the base url domain will not be available so after app startup we'll replace the current - /// context with a new one in which we can access the httpcontext.Request object. - /// - /// - /// The Singleton context object - /// - /// - /// This is created in order to standardize the creation of the singleton. Normally it is created during a request - /// in the UmbracoModule, however this module does not execute during application startup so we need to ensure it - /// during the startup process as well. - /// See: http://issues.umbraco.org/issue/U4-1890, http://issues.umbraco.org/issue/U4-1717 - /// - public static UmbracoContext EnsureContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, - WebSecurity webSecurity, - IUmbracoSettingsSection umbracoSettings, - bool replaceContext) - { - return EnsureContext(httpContext, applicationContext, new WebSecurity(httpContext, applicationContext), umbracoSettings, replaceContext, null); - } - - - - /// - /// This is a helper method which is called to ensure that the singleton context is created and the nice url and routing - /// context is created and assigned. + /// This is a helper method which is called to ensure that the singleton context is created /// /// /// /// /// + /// /// /// if set to true will replace the current singleton with a new one, this is generally only ever used because /// during application startup the base url domain will not be available so after app startup we'll replace the current @@ -237,9 +116,16 @@ namespace Umbraco.Web ApplicationContext applicationContext, WebSecurity webSecurity, IUmbracoSettingsSection umbracoSettings, + IEnumerable urlProviders, bool replaceContext, - bool? preview) + bool? preview = null) { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + if (webSecurity == null) throw new ArgumentNullException("webSecurity"); + if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); + if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + if (UmbracoContext.Current != null) { if (replaceContext == false) @@ -257,15 +143,18 @@ namespace Umbraco.Web // create the RoutingContext, and assign var routingContext = new RoutingContext( umbracoContext, + + //TODO: Until the new cache is done we can't really expose these to override/mock new Lazy>(() => ContentFinderResolver.Current.Finders), new Lazy(() => ContentLastChanceFinderResolver.Current.Finder), + // create the nice urls provider // there's one per request because there are some behavior parameters that can be changed new Lazy( () => new UrlProvider( umbracoContext, umbracoSettings.WebRouting, - UrlProviderResolver.Current.Providers), + urlProviders), false)); //assign the routing context back diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 4ff2704e1a..0429cf83ae 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1,63 +1,71 @@ using System; -using System.Collections; -using System.IO; -using System.Linq; -using System.Text; +using System.ComponentModel; using System.Web; using System.Web.Security; -using System.Web.UI; using System.Xml.Linq; using System.Xml.XPath; -using HtmlAgilityPack; using Umbraco.Core; using Umbraco.Core.Dictionary; using Umbraco.Core.Dynamics; using Umbraco.Core.Models; using Umbraco.Core.Security; +using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web.Routing; using Umbraco.Web.Security; -using Umbraco.Web.Templates; -using umbraco; using System.Collections.Generic; -using umbraco.cms.businesslogic.web; -using umbraco.presentation.templateControls; using Umbraco.Core.Cache; namespace Umbraco.Web { - /// /// A helper class that provides many useful methods and functionality for using Umbraco in templates /// - public class UmbracoHelper - { + public class UmbracoHelper : IUmbracoComponentRenderer + { private readonly UmbracoContext _umbracoContext; private readonly IPublishedContent _currentPage; - private PublishedContentQuery _query; - private readonly MembershipHelper _membershipHelper; - private TagQuery _tag; + private readonly ITypedPublishedContentQuery _typedQuery; + private readonly IDynamicPublishedContentQuery _dynamicQuery; + + private readonly HtmlStringUtilities _stringUtilities = new HtmlStringUtilities(); + + private IUmbracoComponentRenderer _componentRenderer; + private PublishedContentQuery _query; + private MembershipHelper _membershipHelper; + private ITagQuery _tag; + private IDataTypeService _dataTypeService; + private UrlProvider _urlProvider; + private ICultureDictionary _cultureDictionary; /// /// Lazy instantiates the tag context /// - public TagQuery TagQuery + public ITagQuery TagQuery { - get { return _tag ?? (_tag = new TagQuery(UmbracoContext.Application.Services.TagService, ContentQuery)); } + get { return _tag ?? (_tag = new TagQuery(UmbracoContext.Application.Services.TagService, _typedQuery)); } } /// - /// Lazy instantiates the query context + /// Lazy instantiates the query context if not specified in the constructor /// - public PublishedContentQuery ContentQuery - { - get { return _query ?? (_query = new PublishedContentQuery(UmbracoContext.ContentCache, UmbracoContext.MediaCache)); } - } + public PublishedContentQuery ContentQuery + { + get + { + //If the content query doesn't exist it will either be created with the ITypedPublishedContentQuery, IDynamicPublishedContentQuery + // used to construct this instance or with the content caches of the UmbracoContext + return _query ?? + (_query = _typedQuery != null + ? new PublishedContentQuery(_typedQuery, _dynamicQuery) + : new PublishedContentQuery(UmbracoContext.ContentCache, UmbracoContext.MediaCache)); + } + } /// /// Helper method to ensure an umbraco context is set when it is needed /// - private UmbracoContext UmbracoContext + public UmbracoContext UmbracoContext { get { @@ -70,58 +78,142 @@ namespace Umbraco.Web } /// - /// Empty constructor to create an umbraco helper for access to methods that don't have dependencies or used for testing + /// Lazy instantiates the membership helper if not specified in the constructor /// - public UmbracoHelper() - { + public MembershipHelper MembershipHelper + { + get { return _membershipHelper ?? (_membershipHelper = new MembershipHelper(UmbracoContext)); } } + /// + /// Lazy instantiates the UrlProvider if not specified in the constructor + /// + public UrlProvider UrlProvider + { + get { return _urlProvider ?? (_urlProvider = UmbracoContext.UrlProvider); } + } + + /// + /// Lazy instantiates the IDataTypeService if not specified in the constructor + /// + public IDataTypeService DataTypeService + { + get { return _dataTypeService ?? (_dataTypeService = UmbracoContext.Application.Services.DataTypeService); } + } + + /// + /// Lazy instantiates the IUmbracoComponentRenderer if not specified in the constructor + /// + public IUmbracoComponentRenderer UmbracoComponentRenderer + { + get { return _componentRenderer ?? (_componentRenderer = new UmbracoComponentRenderer(UmbracoContext)); } + } + + #region Constructors + /// + /// Empty constructor to create an umbraco helper for access to methods that don't have dependencies + /// + public UmbracoHelper() + { + } + + /// + /// Constructor accepting all dependencies + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// This constructor can be used to create a testable UmbracoHelper + /// + public UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content, + ITypedPublishedContentQuery typedQuery, + IDynamicPublishedContentQuery dynamicQuery, + ITagQuery tagQuery, + IDataTypeService dataTypeService, + UrlProvider urlProvider, + ICultureDictionary cultureDictionary, + IUmbracoComponentRenderer componentRenderer, + MembershipHelper membershipHelper) + { + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (content == null) throw new ArgumentNullException("content"); + if (typedQuery == null) throw new ArgumentNullException("typedQuery"); + if (dynamicQuery == null) throw new ArgumentNullException("dynamicQuery"); + if (tagQuery == null) throw new ArgumentNullException("tagQuery"); + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + if (urlProvider == null) throw new ArgumentNullException("urlProvider"); + if (cultureDictionary == null) throw new ArgumentNullException("cultureDictionary"); + if (componentRenderer == null) throw new ArgumentNullException("componentRenderer"); + if (membershipHelper == null) throw new ArgumentNullException("membershipHelper"); + + _umbracoContext = umbracoContext; + _tag = tagQuery; + _dataTypeService = dataTypeService; + _urlProvider = urlProvider; + _cultureDictionary = cultureDictionary; + _componentRenderer = componentRenderer; + _membershipHelper = membershipHelper; + _currentPage = content; + _typedQuery = typedQuery; + _dynamicQuery = dynamicQuery; + } + + [Obsolete("Use the constructor specifying all dependencies")] + [EditorBrowsable(EditorBrowsableState.Never)] public UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content, PublishedContentQuery query) : this(umbracoContext) { if (content == null) throw new ArgumentNullException("content"); if (query == null) throw new ArgumentNullException("query"); - _membershipHelper = new MembershipHelper(_umbracoContext); _currentPage = content; _query = query; } - - /// - /// Custom constructor setting the current page to the parameter passed in - /// - /// - /// - public UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content) - : this(umbracoContext) - { - if (content == null) throw new ArgumentNullException("content"); - _currentPage = content; - _membershipHelper = new MembershipHelper(_umbracoContext); - } - /// - /// Standard constructor setting the current page to the page that has been routed to - /// - /// - public UmbracoHelper(UmbracoContext umbracoContext) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (umbracoContext.RoutingContext == null) throw new NullReferenceException("The RoutingContext on the UmbracoContext cannot be null"); - _umbracoContext = umbracoContext; - _membershipHelper = new MembershipHelper(_umbracoContext); - if (_umbracoContext.IsFrontEndUmbracoRequest) - { - _currentPage = _umbracoContext.PublishedContentRequest.PublishedContent; - } - } + /// + /// Custom constructor setting the current page to the parameter passed in + /// + /// + /// + public UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content) + : this(umbracoContext) + { + if (content == null) throw new ArgumentNullException("content"); + _currentPage = content; + } + /// + /// Standard constructor setting the current page to the page that has been routed to + /// + /// + public UmbracoHelper(UmbracoContext umbracoContext) + { + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (umbracoContext.RoutingContext == null) throw new NullReferenceException("The RoutingContext on the UmbracoContext cannot be null"); + + _umbracoContext = umbracoContext; + if (_umbracoContext.IsFrontEndUmbracoRequest) + { + _currentPage = _umbracoContext.PublishedContentRequest.PublishedContent; + } + } + + [Obsolete("Use the constructor specifying all dependencies")] + [EditorBrowsable(EditorBrowsableState.Never)] public UmbracoHelper(UmbracoContext umbracoContext, PublishedContentQuery query) : this(umbracoContext) { if (query == null) throw new ArgumentNullException("query"); _query = query; - _membershipHelper = new MembershipHelper(_umbracoContext); - } + } + #endregion /// /// Returns the current IPublishedContent item assigned to the UmbracoHelper @@ -152,21 +244,9 @@ namespace Umbraco.Web /// If not specified, will use the template assigned to the node /// public IHtmlString RenderTemplate(int pageId, int? altTemplateId = null) - { - var templateRenderer = new TemplateRenderer(UmbracoContext, pageId, altTemplateId); - using (var sw = new StringWriter()) - { - try - { - templateRenderer.Render(sw); - } - catch(Exception ex) - { - sw.Write("", pageId, ex); - } - return new HtmlString(sw.ToString()); - } - } + { + return _componentRenderer.RenderTemplate(pageId, altTemplateId); + } #region RenderMacro @@ -177,7 +257,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias) { - return RenderMacro(alias, new { }); + return _componentRenderer.RenderMacro(alias, new { }); } /// @@ -188,7 +268,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias, object parameters) { - return RenderMacro(alias, parameters.ToDictionary()); + return _componentRenderer.RenderMacro(alias, parameters.ToDictionary()); } /// @@ -199,111 +279,9 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias, IDictionary parameters) { - - if (UmbracoContext.PublishedContentRequest == null) - { - throw new InvalidOperationException("Cannot render a macro when there is no current PublishedContentRequest."); - } - - return RenderMacro(alias, parameters, UmbracoContext.PublishedContentRequest.UmbracoPage); + return _componentRenderer.RenderMacro(alias, parameters); } - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// The alias. - /// The parameters. - /// The legacy umbraco page object that is required for some macros - /// - internal IHtmlString RenderMacro(string alias, IDictionary parameters, page umbracoPage) - { - if (alias == null) throw new ArgumentNullException("alias"); - if (umbracoPage == null) throw new ArgumentNullException("umbracoPage"); - - var m = macro.GetMacro(alias); - if (m == null) - { - throw new KeyNotFoundException("Could not find macro with alias " + alias); - } - - return RenderMacro(m, parameters, umbracoPage); - } - - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// The macro. - /// The parameters. - /// The legacy umbraco page object that is required for some macros - /// - internal IHtmlString RenderMacro(macro m, IDictionary parameters, page umbracoPage) - { - if (umbracoPage == null) throw new ArgumentNullException("umbracoPage"); - if (m == null) throw new ArgumentNullException("m"); - - if (UmbracoContext.PageId == null) - { - throw new InvalidOperationException("Cannot render a macro when UmbracoContext.PageId is null."); - } - - var macroProps = new Hashtable(); - foreach (var i in parameters) - { - //TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method of macro.cs - // looks for a lower case match. WTF. the whole macro concept needs to be rewritten. - - - //NOTE: the value could have html encoded values, so we need to deal with that - macroProps.Add(i.Key.ToLowerInvariant(), (i.Value is string) ? HttpUtility.HtmlDecode(i.Value.ToString()) : i.Value); - } - var macroControl = m.RenderMacro(macroProps, - umbracoPage.Elements, - UmbracoContext.PageId.Value); - - string html; - if (macroControl is LiteralControl) - { - // no need to execute, we already have text - html = (macroControl as LiteralControl).Text; - } - else - { - var containerPage = new FormlessPage(); - containerPage.Controls.Add(macroControl); - - using (var output = new StringWriter()) - { - // .Execute() does a PushTraceContext/PopTraceContext and writes trace output straight into 'output' - // and I do not see how we could wire the trace context to the current context... so it creates dirty - // trace output right in the middle of the page. - // - // The only thing we can do is fully disable trace output while .Execute() runs and restore afterwards - // which means trace output is lost if the macro is a control (.ascx or user control) that is invoked - // from within Razor -- which makes sense anyway because the control can _not_ run correctly from - // within Razor since it will never be inserted into the page pipeline (which may even not exist at all - // if we're running MVC). - // - // I'm sure there's more things that will get lost with this context changing but I guess we'll figure - // those out as we go along. One thing we lose is the content type response output. - // http://issues.umbraco.org/issue/U4-1599 if it is setup during the macro execution. So - // here we'll save the content type response and reset it after execute is called. - - var contentType = UmbracoContext.HttpContext.Response.ContentType; - var traceIsEnabled = containerPage.Trace.IsEnabled; - containerPage.Trace.IsEnabled = false; - UmbracoContext.HttpContext.Server.Execute(containerPage, output, true); - containerPage.Trace.IsEnabled = traceIsEnabled; - //reset the content type - UmbracoContext.HttpContext.Response.ContentType = contentType; - - //Now, we need to ensure that local links are parsed - html = TemplateUtilities.ParseInternalLinks(output.ToString()); - } - } - - return new HtmlString(html); - } - #endregion #region Field @@ -334,13 +312,10 @@ namespace Umbraco.Web bool formatAsDate = false, bool formatAsDateWithTime = false, string formatAsDateWithTimeSeparator = "") - - //TODO: commented out until as it is not implemented by umbraco:item yet - //,string formatString = "") { - return Field(AssignedContentItem, fieldAlias, altFieldAlias, + return _componentRenderer.Field(AssignedContentItem, fieldAlias, altFieldAlias, altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, - casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); // formatString); + casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); } /// @@ -370,101 +345,16 @@ namespace Umbraco.Web bool formatAsDate = false, bool formatAsDateWithTime = false, string formatAsDateWithTimeSeparator = "") - - //TODO: commented out until as it is not implemented by umbraco:item yet - //,string formatString = "") { - Mandate.ParameterNotNull(currentPage, "currentPage"); - Mandate.ParameterNotNullOrEmpty(fieldAlias, "fieldAlias"); - - //TODO: This is real nasty and we should re-write the 'item' and 'ItemRenderer' class but si fine for now - - var attributes = new Dictionary - { - {"field", fieldAlias}, - {"recursive", recursive.ToString().ToLowerInvariant()}, - {"useifempty", altFieldAlias}, - {"textifempty", altText}, - {"stripparagraph", removeParagraphTags.ToString().ToLowerInvariant()}, - { - "case", casing == RenderFieldCaseType.Lower ? "lower" - : casing == RenderFieldCaseType.Upper ? "upper" - : casing == RenderFieldCaseType.Title ? "title" - : string.Empty - }, - {"inserttextbefore", insertBefore}, - {"inserttextafter", insertAfter}, - {"convertlinebreaks", convertLineBreaks.ToString().ToLowerInvariant()}, - {"formatasdate", formatAsDate.ToString().ToLowerInvariant()}, - {"formatasdatewithtime", formatAsDateWithTime.ToString().ToLowerInvariant()}, - {"formatasdatewithtimeseparator", formatAsDateWithTimeSeparator} - }; - switch (encoding) - { - case RenderFieldEncodingType.Url: - attributes.Add("urlencode", "true"); - break; - case RenderFieldEncodingType.Html: - attributes.Add("htmlencode", "true"); - break; - case RenderFieldEncodingType.Unchanged: - default: - break; - } - - //need to convert our dictionary over to this weird dictionary type - var attributesForItem = new AttributeCollectionAdapter( - new AttributeCollection( - new StateBag())); - foreach (var i in attributes) - { - attributesForItem.Add(i.Key, i.Value); - } - - - - var item = new Item(currentPage) - { - Field = fieldAlias, - TextIfEmpty = altText, - LegacyAttributes = attributesForItem - }; - - //here we are going to check if we are in the context of an Umbraco routed page, if we are we - //will leave the NodeId empty since the underlying ItemRenderer will work ever so slightly faster - //since it already knows about the current page. Otherwise, we'll assign the id based on our - //currently assigned node. The PublishedContentRequest will be null if: - // * we are rendering a partial view or child action - // * we are rendering a view from a custom route - if ((UmbracoContext.PublishedContentRequest == null - || UmbracoContext.PublishedContentRequest.PublishedContent.Id != currentPage.Id) - && currentPage.Id > 0) // in case we're rendering a detached content (id == 0) - { - item.NodeId = currentPage.Id.ToString(); - } - - - var containerPage = new FormlessPage(); - containerPage.Controls.Add(item); - - using (var output = new StringWriter()) - using (var htmlWriter = new HtmlTextWriter(output)) - { - ItemRenderer.Instance.Init(item); - ItemRenderer.Instance.Load(item); - ItemRenderer.Instance.Render(item, htmlWriter); - - //because we are rendering the output through the legacy Item (webforms) stuff, the {localLinks} will already be replaced. - return new HtmlString(output.ToString()); - } + return _componentRenderer.Field(currentPage, fieldAlias, altFieldAlias, + altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, + casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); } #endregion #region Dictionary - private ICultureDictionary _cultureDictionary; - /// /// Returns the dictionary value for the key specified /// @@ -484,32 +374,44 @@ namespace Umbraco.Web #region Membership - /// - /// Check if a document object is protected by the "Protect Pages" functionality in umbraco - /// - /// The identifier of the document object to check - /// The full path of the document object to check - /// True if the document object is protected - public bool IsProtected(int documentId, string path) + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the IsProtected method that only specifies path")] + public bool IsProtected(int documentId, string path) { - return Access.IsProtected(documentId, path); + return IsProtected(path.EnsureEndsWith("," + documentId)); } - /// - /// Check if the current user has access to a document - /// - /// The identifier of the document object to check - /// The full path of the document object to check - /// True if the current user has access or if the current document isn't protected - public bool MemberHasAccess(int nodeId, string path) - { - if (IsProtected(nodeId, path)) - { - return _membershipHelper.IsLoggedIn() - && Access.HasAccess(nodeId, path, GetCurrentMember()); - } - return true; - } + /// + /// Check if a document object is protected by the "Protect Pages" functionality in umbraco + /// + /// The full path of the document object to check + /// True if the document object is protected + public bool IsProtected(string path) + { + return UmbracoContext.Application.Services.PublicAccessService.IsProtected(path); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the MemberHasAccess method that only specifies path")] + public bool MemberHasAccess(int nodeId, string path) + { + return MemberHasAccess(path.EnsureEndsWith("," + nodeId)); + } + + /// + /// Check if the current user has access to a document + /// + /// The full path of the document object to check + /// True if the current user has access or if the current document isn't protected + public bool MemberHasAccess(string path) + { + if (IsProtected(path)) + { + return MembershipHelper.IsLoggedIn() + && UmbracoContext.Application.Services.PublicAccessService.HasAccess(path, GetCurrentMember(), Roles.Provider); + } + return true; + } /// /// Gets (or adds) the current member from the current request cache @@ -530,7 +432,7 @@ namespace Umbraco.Web /// True is the current user is logged in public bool MemberIsLoggedOn() { - return _membershipHelper.IsLoggedIn(); + return MembershipHelper.IsLoggedIn(); } #endregion @@ -556,7 +458,7 @@ namespace Umbraco.Web /// The url for the content. public string Url(int contentId) { - return UmbracoContext.Current.UrlProvider.GetUrl(contentId); + return UrlProvider.GetUrl(contentId); } /// @@ -567,7 +469,7 @@ namespace Umbraco.Web /// The url for the content. public string Url(int contentId, UrlProviderMode mode) { - return UmbracoContext.Current.UrlProvider.GetUrl(contentId, mode); + return UrlProvider.GetUrl(contentId, mode); } /// @@ -587,7 +489,7 @@ namespace Umbraco.Web /// The absolute url for the content. public string UrlAbsolute(int contentId) { - return UmbracoContext.Current.UrlProvider.GetUrl(contentId, true); + return UrlProvider.GetUrl(contentId, true); } #endregion @@ -597,39 +499,39 @@ namespace Umbraco.Web public IPublishedContent TypedMember(object id) { var asInt = id.TryConvertTo(); - return asInt ? _membershipHelper.GetById(asInt.Result) : _membershipHelper.GetByProviderKey(id); + return asInt ? MembershipHelper.GetById(asInt.Result) : MembershipHelper.GetByProviderKey(id); } public IPublishedContent TypedMember(int id) { - return _membershipHelper.GetById(id); + return MembershipHelper.GetById(id); } public IPublishedContent TypedMember(string id) { var asInt = id.TryConvertTo(); - return asInt ? _membershipHelper.GetById(asInt.Result) : _membershipHelper.GetByProviderKey(id); + return asInt ? MembershipHelper.GetById(asInt.Result) : MembershipHelper.GetByProviderKey(id); } public dynamic Member(object id) { var asInt = id.TryConvertTo(); return asInt - ? _membershipHelper.GetById(asInt.Result).AsDynamic() - : _membershipHelper.GetByProviderKey(id).AsDynamic(); + ? MembershipHelper.GetById(asInt.Result).AsDynamic() + : MembershipHelper.GetByProviderKey(id).AsDynamic(); } public dynamic Member(int id) { - return _membershipHelper.GetById(id).AsDynamic(); + return MembershipHelper.GetById(id).AsDynamic(); } public dynamic Member(string id) { var asInt = id.TryConvertTo(); return asInt - ? _membershipHelper.GetById(asInt.Result).AsDynamic() - : _membershipHelper.GetByProviderKey(id).AsDynamic(); + ? MembershipHelper.GetById(asInt.Result).AsDynamic() + : MembershipHelper.GetByProviderKey(id).AsDynamic(); } #endregion @@ -1006,10 +908,7 @@ namespace Umbraco.Web /// The text with text line breaks replaced with html linebreaks (
)
public string ReplaceLineBreaksForHtml(string text) { - if (bool.Parse(Umbraco.Core.Configuration.GlobalSettings.EditXhtmlMode)) - return text.Replace("\n", "
\n"); - else - return text.Replace("\n", "
\n"); + return _stringUtilities.ReplaceLineBreaksForHtml(text); } /// @@ -1032,79 +931,22 @@ namespace Umbraco.Web } public HtmlString StripHtml(string html, params string[] tags) { - return StripHtmlTags(html, tags); + return _stringUtilities.StripHtmlTags(html, tags); } - - private HtmlString StripHtmlTags(string html, params string[] tags) - { - var doc = new HtmlDocument(); - doc.LoadHtml("

" + html + "

"); - var targets = new List(); - - var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*"); - if (nodes != null) - { - foreach (var node in nodes) - { - //is element - if (node.NodeType != HtmlNodeType.Element) continue; - var filterAllTags = (tags == null || !tags.Any()); - if (filterAllTags || tags.Any(tag => string.Equals(tag, node.Name, StringComparison.CurrentCultureIgnoreCase))) - { - targets.Add(node); - } - } - foreach (var target in targets) - { - HtmlNode content = doc.CreateTextNode(target.InnerText); - target.ParentNode.ReplaceChild(content, target); - } - } - else - { - return new HtmlString(html); - } - return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml); - } - + public string Coalesce(params object[] args) { - return Coalesce(args); - } - - internal string Coalesce(params object[] args) - { - foreach (var sArg in args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg))) - { - return sArg; - } - return string.Empty; + return _stringUtilities.Coalesce(args); } public string Concatenate(params object[] args) { - return Concatenate(args); - } - - internal string Concatenate(params object[] args) - { - var result = new StringBuilder(); - foreach (var sArg in args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg))) - { - result.Append(sArg); - } - return result.ToString(); + return _stringUtilities.Concatenate(args); } public string Join(string seperator, params object[] args) { - return Join(seperator, args); - } - - internal string Join(string seperator, params object[] args) - { - var results = args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg)).ToList(); - return string.Join(seperator, results); + return _stringUtilities.Join(seperator, args); } public IHtmlString Truncate(IHtmlString html, int length) @@ -1141,153 +983,7 @@ namespace Umbraco.Web } public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) { - using (var outputms = new MemoryStream()) - { - using (var outputtw = new StreamWriter(outputms)) - { - using (var ms = new MemoryStream()) - { - using (var tw = new StreamWriter(ms)) - { - tw.Write(html); - tw.Flush(); - ms.Position = 0; - var tagStack = new Stack(); - - using (TextReader tr = new StreamReader(ms)) - { - bool isInsideElement = false, - lengthReached = false, - insideTagSpaceEncountered = false, - isTagClose = false; - - int ic = 0, - currentLength = 0, - currentTextLength = 0; - - string currentTag = string.Empty, - tagContents = string.Empty; - - while ((ic = tr.Read()) != -1) - { - bool write = true; - - switch ((char)ic) - { - case '<': - if (!lengthReached) - { - isInsideElement = true; - } - - insideTagSpaceEncountered = false; - currentTag = string.Empty; - tagContents = string.Empty; - isTagClose = false; - if (tr.Peek() == (int)'/') - { - isTagClose = true; - } - break; - - case '>': - isInsideElement = false; - - if (isTagClose && tagStack.Count > 0) - { - string thisTag = tagStack.Pop(); - outputtw.Write(""); - } - if (!isTagClose && currentTag.Length > 0) - { - if (!lengthReached) - { - tagStack.Push(currentTag); - outputtw.Write("<" + currentTag); - if (!string.IsNullOrEmpty(tagContents)) - { - if (tagContents.EndsWith("/")) - { - // No end tag e.g.
. - tagStack.Pop(); - } - - outputtw.Write(tagContents); - write = true; - insideTagSpaceEncountered = false; - } - outputtw.Write(">"); - } - } - // Continue to next iteration of the text reader. - continue; - - default: - if (isInsideElement) - { - if (ic == (int)' ') - { - if (!insideTagSpaceEncountered) - { - insideTagSpaceEncountered = true; - } - } - - if (!insideTagSpaceEncountered) - { - currentTag += (char)ic; - } - } - break; - } - - if (isInsideElement || insideTagSpaceEncountered) - { - write = false; - if (insideTagSpaceEncountered) - { - tagContents += (char)ic; - } - } - - if (!isInsideElement || treatTagsAsContent) - { - currentTextLength++; - } - - if (currentTextLength <= length || (lengthReached && isInsideElement)) - { - if (write) - { - var charToWrite = (char)ic; - outputtw.Write(charToWrite); - currentLength++; - } - } - - if (!lengthReached && currentTextLength >= length) - { - // Reached truncate limit. - if (addElipsis) - { - outputtw.Write("…"); - } - lengthReached = true; - } - - } - - } - } - } - outputtw.Flush(); - outputms.Position = 0; - using (TextReader outputtr = new StreamReader(outputms)) - { - return new HtmlString(outputtr.ReadToEnd().Replace(" ", " ").Trim()); - } - } - } + return _stringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); } @@ -1310,8 +1006,7 @@ namespace Umbraco.Web public string GetPreValueAsString(int id) { - var ds = _umbracoContext.Application.Services.DataTypeService; - return ds.GetPreValueAsString(id); + return DataTypeService.GetPreValueAsString(id); } #endregion diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 8e8e706de9..aaebbe976c 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Security.Principal; using System.Threading; +using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using System.Web.Routing; @@ -521,24 +522,8 @@ namespace Umbraco.Web urlRouting.PostResolveRequestCache(context); } - /// - /// Checks if the xml cache file needs to be updated/persisted - /// - /// - /// - /// TODO: This needs an overhaul, see the error report created here: - /// https://docs.google.com/document/d/1neGE3q3grB4lVJfgID1keWY2v9JYqf-pw75sxUUJiyo/edit - /// - static void PersistXmlCache(HttpContextBase httpContext) - { - if (content.Instance.IsXmlQueuedForPersistenceToFile) - { - content.Instance.RemoveXmlFilePersistenceQueue(); - content.Instance.PersistXmlToFile(); - } - } - - /// + + /// /// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request /// /// @@ -605,13 +590,6 @@ namespace Umbraco.Web ProcessRequest(new HttpContextWrapper(httpContext)); }; - // used to check if the xml cache file needs to be updated/persisted - app.PostRequestHandlerExecute += (sender, e) => - { - var httpContext = ((HttpApplication)sender).Context; - PersistXmlCache(new HttpContextWrapper(httpContext)); - }; - app.EndRequest += (sender, args) => { var httpContext = ((HttpApplication)sender).Context; diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 8228f3da5e..8496d15fec 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Threading; +using System.Threading.Tasks; using System.Web; using System.Xml; using Umbraco.Core; @@ -15,8 +16,11 @@ using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.web; using Umbraco.Core.Models; +using Umbraco.Core.Profiling; using umbraco.DataLayer; using Umbraco.Web; +using Umbraco.Web.PublishedCache.XmlPublishedCache; +using Umbraco.Web.Scheduling; using File = System.IO.File; namespace umbraco @@ -26,6 +30,18 @@ namespace umbraco /// public class content { + private static readonly BackgroundTaskRunner FilePersister + = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { LongRunning = true }); + + private XmlCacheFilePersister _persisterTask; + + private content() + { + _persisterTask = new XmlCacheFilePersister(FilePersister, this, UmbracoXmlDiskCacheFileName, + new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler)); + FilePersister.Add(_persisterTask); + } + #region Declarations // Sync access to disk file @@ -64,7 +80,6 @@ namespace umbraco #endregion - #region Singleton private static readonly Lazy LazyInstance = new Lazy(() => new content()); @@ -123,7 +138,7 @@ namespace umbraco /// /// Before returning we always check to ensure that the xml is loaded /// - protected virtual XmlDocument XmlContentInternal + protected internal virtual XmlDocument XmlContentInternal { get { @@ -309,8 +324,7 @@ namespace umbraco // and clear the queue in case is this a web request, we don't want it reprocessing. if (UmbracoConfig.For.UmbracoSettings().Content.XmlCacheEnabled && UmbracoConfig.For.UmbracoSettings().Content.ContinouslyUpdateXmlDiskCache) { - RemoveXmlFilePersistenceQueue(); - PersistXmlToFile(xmlDoc); + QueueXmlForPersistence(); } } } @@ -888,54 +902,6 @@ namespace umbraco #region Protected & Private methods - internal const string PersistenceFlagContextKey = "vnc38ykjnkjdnk2jt98ygkxjng"; - - /// - /// Removes the flag that queues the file for persistence - /// - internal void RemoveXmlFilePersistenceQueue() - { - if (UmbracoContext.Current != null && UmbracoContext.Current.HttpContext != null) - { - UmbracoContext.Current.HttpContext.Application.Lock(); - UmbracoContext.Current.HttpContext.Application[PersistenceFlagContextKey] = null; - UmbracoContext.Current.HttpContext.Application.UnLock(); - } - } - - internal bool IsXmlQueuedForPersistenceToFile - { - get - { - if (UmbracoContext.Current != null && UmbracoContext.Current.HttpContext != null) - { - bool val = UmbracoContext.Current.HttpContext.Application[PersistenceFlagContextKey] != null; - if (val) - { - DateTime persistenceTime = DateTime.MinValue; - try - { - persistenceTime = (DateTime)UmbracoContext.Current.HttpContext.Application[PersistenceFlagContextKey]; - if (persistenceTime > GetCacheFileUpdateTime()) - { - return true; - } - else - { - RemoveXmlFilePersistenceQueue(); - } - } - catch (Exception ex) - { - // Nothing to catch here - we'll just persist - LogHelper.Error("An error occurred checking if xml file is queued for persistence", ex); - } - } - } - return false; - } - } - /// /// Invalidates the disk content cache file. Effectively just deletes it. /// @@ -1183,51 +1149,26 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } } + [Obsolete("This method should not be used, xml file persistence is done in a queue using a BackgroundTaskRunner")] public void PersistXmlToFile() - { - PersistXmlToFile(_xmlContent); - } - - /// - /// Persist a XmlDocument to the Disk Cache - /// - /// - internal void PersistXmlToFile(XmlDocument xmlDoc) { lock (ReaderWriterSyncLock) { - if (xmlDoc != null) - { - LogHelper.Debug("Saving content to disk on thread '{0}' (Threadpool? {1})", - () => Thread.CurrentThread.Name, - () => Thread.CurrentThread.IsThreadPoolThread); - + if (_xmlContent != null) + { try { - Stopwatch stopWatch = Stopwatch.StartNew(); + // create directory for cache path if it doesn't yet exist + var directoryName = Path.GetDirectoryName(UmbracoXmlDiskCacheFileName); + Directory.CreateDirectory(directoryName); - DeleteXmlCache(); - - // Try to create directory for cache path if it doesn't yet exist - string directoryName = Path.GetDirectoryName(UmbracoXmlDiskCacheFileName); - if (!File.Exists(UmbracoXmlDiskCacheFileName) && !Directory.Exists(directoryName)) - { - // We're already in a try-catch and saving will fail if this does, so don't need another - Directory.CreateDirectory(directoryName); - } - - xmlDoc.Save(UmbracoXmlDiskCacheFileName); - - LogHelper.Debug("Saved content on thread '{0}' in {1} (Threadpool? {2})", - () => Thread.CurrentThread.Name, - () => stopWatch.Elapsed, - () => Thread.CurrentThread.IsThreadPoolThread); + _xmlContent.Save(UmbracoXmlDiskCacheFileName); } catch (Exception ee) { // If for whatever reason something goes wrong here, invalidate disk cache DeleteXmlCache(); - + LogHelper.Error(string.Format( "Error saving content on thread '{0}' due to '{1}' (Threadpool? {2})", Thread.CurrentThread.Name, ee.Message, Thread.CurrentThread.IsThreadPoolThread), ee); @@ -1237,48 +1178,17 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } /// - /// Marks a flag in the HttpContext so that, upon page execution completion, the Xml cache will - /// get persisted to disk. Ensure this method is only called from a thread executing a page request - /// since UmbracoModule is the only monitor of this flag and is responsible - /// for enacting the persistence at the PostRequestHandlerExecute stage of the page lifecycle. + /// Adds a task to the xml cache file persister /// private void QueueXmlForPersistence() { - //if this is called outside a web request we cannot queue it it will run in the current thread. - - if (UmbracoContext.Current != null && UmbracoContext.Current.HttpContext != null) - { - UmbracoContext.Current.HttpContext.Application.Lock(); - try - { - if (UmbracoContext.Current.HttpContext.Application[PersistenceFlagContextKey] != null) - { - UmbracoContext.Current.HttpContext.Application.Add(PersistenceFlagContextKey, null); - } - UmbracoContext.Current.HttpContext.Application[PersistenceFlagContextKey] = DateTime.UtcNow; - } - finally - { - UmbracoContext.Current.HttpContext.Application.UnLock(); - } - } - else - { - // Save copy of content - if (UmbracoConfig.For.UmbracoSettings().Content.CloneXmlContent) - { - XmlDocument xmlContentCopy = CloneXmlDoc(_xmlContent); - PersistXmlToFile(xmlContentCopy); - } - else - { - PersistXmlToFile(); - } - } + _persisterTask = _persisterTask.Touch(); } internal DateTime GetCacheFileUpdateTime() { + //TODO: Should there be a try/catch here in case the file is being written to while this is trying to be executed? + if (File.Exists(UmbracoXmlDiskCacheFileName)) { return new FileInfo(UmbracoXmlDiskCacheFileName).LastWriteTimeUtc; diff --git a/src/Umbraco.Web/umbraco.presentation/template.cs b/src/Umbraco.Web/umbraco.presentation/template.cs index aec16e96c5..eeee729ec8 100644 --- a/src/Umbraco.Web/umbraco.presentation/template.cs +++ b/src/Umbraco.Web/umbraco.presentation/template.cs @@ -24,7 +24,7 @@ namespace umbraco /// /// Holds methods for parsing and building umbraco templates /// - /// + [Obsolete("Do not use this class, use Umbraco.Core.Service.IFileService to work with templates")] public class template { #region private variables diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs index b89091c017..ab88df2b4a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs @@ -172,10 +172,10 @@ namespace umbraco.presentation.umbraco.dialogs protected void protect_Click(object sender, CommandEventArgs e) { - if (string.IsNullOrEmpty(errorPagePicker.Value)) + if (string.IsNullOrEmpty(errorPagePicker.Value) || errorPagePicker.Value == "-1") cv_errorPage.IsValid = false; - if (string.IsNullOrEmpty(loginPagePicker.Value)) + if (string.IsNullOrEmpty(loginPagePicker.Value) || loginPagePicker.Value == "-1") cv_loginPage.IsValid = false; //reset diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs index e05f4b6907..6944ad9198 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs @@ -101,7 +101,7 @@ namespace umbraco.presentation.preview //Inject preview xml parentId = document.Level == 1 ? -1 : document.Parent.Id; var previewXml = document.ToPreviewXml(XmlContent); - if (document.Content.Published == false + if (document.ContentEntity.Published == false && ApplicationContext.Current.Services.ContentService.HasPublishedVersion(document.Id)) previewXml.Attributes.Append(XmlContent.CreateAttribute("isDraft")); content.AppendDocumentXml(document.Id, document.Level, parentId, previewXml, XmlContent); diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs index ccea38dfc8..8f995a125e 100644 --- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs @@ -83,31 +83,14 @@ namespace UmbracoExamine.DataServices } /// - /// Unfortunately, we need to implement our own IsProtected method since - /// the Umbraco core code requires an HttpContext for this method and when we're running - /// async, there is no context - /// - /// - /// - - private static XmlNode GetPage(int documentId) - { - var xDoc = Access.GetXmlDocumentCopy(); - var x = xDoc.SelectSingleNode("/access/page [@id=" + documentId.ToString() + "]"); - return x; - } - - /// - /// Unfortunately, we need to implement our own IsProtected method since - /// the Umbraco core code requires an HttpContext for this method and when we're running - /// async, there is no context + /// Check if the node is protected /// /// /// /// public bool IsProtected(int nodeId, string path) { - return path.Split(',').Any(id => GetPage(int.Parse(id)) != null); + return _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId)); } /// diff --git a/src/umbraco.cms/businesslogic/web/Access.cs b/src/umbraco.cms/businesslogic/web/Access.cs index 26ef66483e..8271fa581f 100644 --- a/src/umbraco.cms/businesslogic/web/Access.cs +++ b/src/umbraco.cms/businesslogic/web/Access.cs @@ -1,91 +1,62 @@ using System; -using System.Data; +using System.Collections.Generic; using System.Globalization; -using System.Threading; -using System.Web; +using System.Linq; using System.Xml; using System.Xml.Linq; -using System.Xml.XPath; -using System.Collections; -using System.IO; - using System.Web.Security; using Umbraco.Core; -using Umbraco.Core.IO; +using Umbraco.Core.Models; using Umbraco.Core.Security; +using Umbraco.Core.Services; namespace umbraco.cms.businesslogic.web { /// /// Summary description for Access. /// + [Obsolete("Use Umbraco.Core.Service.IPublicAccessService instead")] public class Access { - static private readonly Hashtable CheckedPages = new Hashtable(); - - static private volatile XmlDocument _accessXmlContent; - static private string _accessXmlFilePath; - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - private static readonly object LoadLocker = new object(); [Obsolete("Do not access this property directly, it is not thread safe, use GetXmlDocumentCopy instead")] public static XmlDocument AccessXml { - get { return GetXmlDocument(); } + get { return GetXmlDocumentCopy(); } } - //private method to initialize and return the in-memory xmldocument - private static XmlDocument GetXmlDocument() - { - if (_accessXmlContent == null) - { - lock (LoadLocker) - { - if (_accessXmlContent == null) - { - if (_accessXmlFilePath == null) - { - //if we pop it here it'll make for better stack traces ;) - _accessXmlFilePath = IOHelper.MapPath(SystemFiles.AccessXml); - } - - _accessXmlContent = new XmlDocument(); - - if (File.Exists(_accessXmlFilePath) == false) - { - var file = new FileInfo(_accessXmlFilePath); - if (Directory.Exists(file.DirectoryName) == false) - { - Directory.CreateDirectory(file.Directory.FullName); //ensure the folder exists! - } - var f = File.Open(_accessXmlFilePath, FileMode.Create); - var sw = new StreamWriter(f); - sw.WriteLine(""); - sw.Close(); - f.Close(); - } - _accessXmlContent.Load(_accessXmlFilePath); - } - } - } - return _accessXmlContent; - } - - //used by all other methods in this class to read and write the document which - // is thread safe and only clones once per request so it's still fast. + //NOTE: This is here purely for backwards compat + [Obsolete("This should never be used, the data is stored in the database now")] public static XmlDocument GetXmlDocumentCopy() { - if (HttpContext.Current == null) + var allAccessEntries = ApplicationContext.Current.Services.PublicAccessService.GetAll().ToArray(); + + var xml = XDocument.Parse(""); + foreach (var entry in allAccessEntries) { - return (XmlDocument)GetXmlDocument().Clone(); + var pageXml = new XElement("page", + new XAttribute("id", entry.ProtectedNodeId), + new XAttribute("loginPage", entry.LoginNodeId), + new XAttribute("noRightsPage", entry.NoAccessNodeId)); + + foreach (var rule in entry.Rules) + { + if (rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) + { + //if there is a member id claim then it is 'simple' (this is how legacy worked) + pageXml.Add(new XAttribute("simple", "True")); + pageXml.Add(new XAttribute("memberId", rule.RuleValue)); + } + else if (rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) + { + pageXml.Add(new XElement("group", new XAttribute("id", rule.RuleValue))); + } + } + + xml.Root.Add(pageXml); } - if (HttpContext.Current.Items.Contains(typeof (Access)) == false) - { - HttpContext.Current.Items.Add(typeof (Access), GetXmlDocument().Clone()); - } - - return (XmlDocument)HttpContext.Current.Items[typeof(Access)]; + return xml.ToXmlDocument(); } #region Manipulation methods @@ -93,212 +64,167 @@ namespace umbraco.cms.businesslogic.web public static void AddMembershipRoleToDocument(int documentId, string role) { //event + var doc = new Document(documentId); var e = new AddMemberShipRoleToDocumentEventArgs(); - new Access().FireBeforeAddMemberShipRoleToDocument(new Document(documentId), role, e); + new Access().FireBeforeAddMemberShipRoleToDocument(doc, role, e); if (e.Cancel) return; - using (new WriteLock(Locker)) + + var entry = ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberRoleRuleType, + role); + + if (entry == null) { - var x = (XmlElement)GetPage(documentId); - - if (x == null) - throw new Exception("Document is not protected!"); - - if (x.SelectSingleNode("group [@id = '" + role + "']") == null) - { - var groupXml = (XmlElement)x.OwnerDocument.CreateNode(XmlNodeType.Element, "group", ""); - groupXml.SetAttribute("id", role); - x.AppendChild(groupXml); - Save(x.OwnerDocument); - } + throw new Exception("Document is not protected!"); } - new Access().FireAfterAddMemberShipRoleToDocument(new Document(documentId), role, e); + Save(); + + new Access().FireAfterAddMemberShipRoleToDocument(doc, role, e); } - /// - /// Used to refresh cache among servers in an LB scenario - /// - /// - internal static void UpdateInMemoryDocument(XmlDocument newDoc) - { - //NOTE: This would be better to use our normal ReaderWriter lock but because we are emitting an - // event inside of the WriteLock and code can then listen to the event and call this method we end - // up in a dead-lock. This specifically happens in the PublicAccessCacheRefresher. - //So instead we use the load locker which is what is used for the static XmlDocument instance, we'll - // lock that, set the doc to null which will cause any reader threads to block for the AccessXml instance - // then save the doc and re-load it, then all blocked threads can carry on. - lock (LoadLocker) - { - _accessXmlContent = null; - //do a real clone - _accessXmlContent = new XmlDocument(); - _accessXmlContent.LoadXml(newDoc.OuterXml); - ClearCheckPages(); - } - } [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] public static void AddMemberGroupToDocument(int DocumentId, int MemberGroupId) { - var x = (XmlElement)GetPage(DocumentId); + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (x == null) - throw new Exception("Document is not protected!"); + if (content == null) + throw new Exception("No content found with document id " + DocumentId); - using (new WriteLock(Locker)) - { - if (x.SelectSingleNode("group [@id = '" + MemberGroupId + "']") == null) - { - var groupXml = (XmlElement)x.OwnerDocument.CreateNode(XmlNodeType.Element, "group", ""); - groupXml.SetAttribute("id", MemberGroupId.ToString(CultureInfo.InvariantCulture)); - x.AppendChild(groupXml); - Save(x.OwnerDocument); - } - } + var entry = ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( + content, + Constants.Conventions.PublicAccess.MemberGroupIdRuleType, + MemberGroupId.ToString(CultureInfo.InvariantCulture)); + + Save(); } [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] public static void AddMemberToDocument(int DocumentId, int MemberId) { - var x = (XmlElement)GetPage(DocumentId); + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (x == null) - throw new Exception("Document is not protected!"); + if (content == null) + throw new Exception("No content found with document id " + DocumentId); - using (new WriteLock(Locker)) - { - if (x.Attributes.GetNamedItem("memberId") != null) - { - x.Attributes.GetNamedItem("memberId").Value = MemberId.ToString(CultureInfo.InvariantCulture); - } - else - { - x.SetAttribute("memberId", MemberId.ToString(CultureInfo.InvariantCulture)); - } - Save(x.OwnerDocument); - } + ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( + content, + Constants.Conventions.PublicAccess.MemberIdRuleType, + MemberId.ToString(CultureInfo.InvariantCulture)); + + Save(); } public static void AddMembershipUserToDocument(int documentId, string membershipUserName) { //event + var doc = new Document(documentId); var e = new AddMembershipUserToDocumentEventArgs(); - new Access().FireBeforeAddMembershipUserToDocument(new Document(documentId), membershipUserName, e); + new Access().FireBeforeAddMembershipUserToDocument(doc, membershipUserName, e); if (e.Cancel) return; - using (new WriteLock(Locker)) + var entry = ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberUsernameRuleType, + membershipUserName); + + if (entry == null) { - var x = (XmlElement)GetPage(documentId); - - if (x == null) - throw new Exception("Document is not protected!"); - - if (x.Attributes.GetNamedItem("memberId") != null) - x.Attributes.GetNamedItem("memberId").Value = membershipUserName; - else - x.SetAttribute("memberId", membershipUserName); - Save(x.OwnerDocument); + throw new Exception("Document is not protected!"); } - new Access().FireAfterAddMembershipUserToDocument(new Document(documentId), membershipUserName, e); + Save(); + + new Access().FireAfterAddMembershipUserToDocument(doc, membershipUserName, e); } [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] public static void RemoveMemberGroupFromDocument(int DocumentId, int MemberGroupId) { - using (new WriteLock(Locker)) + var doc = new Document(DocumentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberGroupIdRuleType, + MemberGroupId.ToString(CultureInfo.InvariantCulture)); + + if (entry == null) { - var x = (XmlElement)GetPage(DocumentId); - - if (x == null) - throw new Exception("Document is not protected!"); - - var xGroup = x.SelectSingleNode("group [@id = '" + MemberGroupId + "']"); - if (xGroup == null) return; - - x.RemoveChild(xGroup); - Save(x.OwnerDocument); + throw new Exception("Document is not protected!"); } + Save(); } public static void RemoveMembershipRoleFromDocument(int documentId, string role) { + var doc = new Document(documentId); var e = new RemoveMemberShipRoleFromDocumentEventArgs(); - new Access().FireBeforeRemoveMemberShipRoleFromDocument(new Document(documentId), role, e); + new Access().FireBeforeRemoveMemberShipRoleFromDocument(doc, role, e); if (e.Cancel) return; - using (new WriteLock(Locker)) - { - var x = (XmlElement)GetPage(documentId); - if (x == null) - throw new Exception("Document is not protected!"); - var xGroup = x.SelectSingleNode("group [@id = '" + role + "']"); + ApplicationContext.Current.Services.PublicAccessService.RemoveRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberRoleRuleType, + role); - if (xGroup != null) - { - x.RemoveChild(xGroup); - Save(x.OwnerDocument); - } - } + Save(); - new Access().FireAfterRemoveMemberShipRoleFromDocument(new Document(documentId), role, e); + new Access().FireAfterRemoveMemberShipRoleFromDocument(doc, role, e); } public static bool RenameMemberShipRole(string oldRolename, string newRolename) { - var hasChange = false; - if (oldRolename == newRolename) return false; - - using (new WriteLock(Locker)) - { - var xDoc = GetXmlDocumentCopy(); - oldRolename = oldRolename.Replace("'", "'"); - foreach (XmlNode x in xDoc.SelectNodes("//group [@id = '" + oldRolename + "']")) - { - x.Attributes["id"].Value = newRolename; - hasChange = true; - } - if (hasChange) - Save(xDoc); - } + var hasChange = ApplicationContext.Current.Services.PublicAccessService.RenameMemberGroupRoleRules(oldRolename, newRolename); + + if (hasChange) + Save(); return hasChange; } public static void ProtectPage(bool Simple, int DocumentId, int LoginDocumentId, int ErrorDocumentId) { + var doc = new Document(DocumentId); var e = new AddProtectionEventArgs(); - new Access().FireBeforeAddProtection(new Document(DocumentId), e); + new Access().FireBeforeAddProtection(doc, e); if (e.Cancel) return; - using (new WriteLock(Locker)) + var loginContent = ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId); + if (loginContent == null) throw new NullReferenceException("No content item found with id " + LoginDocumentId); + var noAccessContent = ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId); + if (noAccessContent == null) throw new NullReferenceException("No content item found with id " + ErrorDocumentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity); + if (entry != null) { - var x = (XmlElement)GetPage(DocumentId); - - if (x == null) + if (Simple) { - var xDoc = GetXmlDocumentCopy(); - - x = (XmlElement)xDoc.CreateNode(XmlNodeType.Element, "page", ""); - x.OwnerDocument.DocumentElement.AppendChild(x); + // if using simple mode, make sure that all existing groups are removed + entry.ClearRules(); } - // if using simple mode, make sure that all existing groups are removed - else if (Simple) - { - x.RemoveAll(); - } - x.SetAttribute("id", DocumentId.ToString()); - x.SetAttribute("loginPage", LoginDocumentId.ToString()); - x.SetAttribute("noRightsPage", ErrorDocumentId.ToString()); - x.SetAttribute("simple", Simple.ToString()); - Save(x.OwnerDocument); - ClearCheckPages(); + + //ensure the correct ids are applied + entry.LoginNodeId = loginContent.Id; + entry.NoAccessNodeId = noAccessContent.Id; } + else + { + entry = new PublicAccessEntry(doc.ContentEntity, + ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId), + ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId), + new List()); + } + + ApplicationContext.Current.Services.PublicAccessService.Save(entry); + + Save(); new Access().FireAfterAddProtection(new Document(DocumentId), e); } @@ -306,22 +232,21 @@ namespace umbraco.cms.businesslogic.web public static void RemoveProtection(int DocumentId) { //event + var doc = new Document(DocumentId); var e = new RemoveProtectionEventArgs(); - new Access().FireBeforeRemoveProtection(new Document(DocumentId), e); + new Access().FireBeforeRemoveProtection(doc, e); if (e.Cancel) return; - using (new WriteLock(Locker)) + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity); + if (entry != null) { - var x = (XmlElement)GetPage(DocumentId); - if (x == null) return; - - x.ParentNode.RemoveChild(x); - Save(x.OwnerDocument); - ClearCheckPages(); + ApplicationContext.Current.Services.PublicAccessService.Delete(entry); } - new Access().FireAfterRemoveProtection(new Document(DocumentId), e); + Save(); + + new Access().FireAfterRemoveProtection(doc, e); } #endregion @@ -330,168 +255,109 @@ namespace umbraco.cms.businesslogic.web [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] public static bool IsProtectedByGroup(int DocumentId, int GroupId) { - bool isProtected = false; - var d = new Document(DocumentId); - using (new ReadLock(Locker)) - { - if (IsProtectedInternal(DocumentId, d.Path)) - { - var currentNode = GetPage(GetProtectedPage(d.Path)); - if (currentNode.SelectSingleNode("./group [@id=" + GroupId + "]") != null) - { - isProtected = true; - } - } - } + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(d.ContentEntity); + if (entry == null) return false; + + return entry.Rules + .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType + && x.RuleValue == GroupId.ToString(CultureInfo.InvariantCulture)); - return isProtected; } public static bool IsProtectedByMembershipRole(int documentId, string role) { - bool isProtected = false; + var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); - var d = new CMSNode(documentId); + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return false; - using (new ReadLock(Locker)) - { - if (IsProtectedInternal(documentId, d.Path)) - { - var currentNode = GetPage(GetProtectedPage(d.Path)); - if (currentNode.SelectSingleNode("./group [@id='" + role + "']") != null) - { - isProtected = true; - } - } - } + return entry.Rules + .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType + && x.RuleValue == role); - return isProtected; } public static string[] GetAccessingMembershipRoles(int documentId, string path) { - var roles = new ArrayList(); + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(path.EnsureEndsWith("," + documentId)); + if (entry == null) return new string[] { }; - using (new ReadLock(Locker)) - { - if (IsProtectedInternal(documentId, path) == false) - return null; + var memberGroupRoleRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType); + return memberGroupRoleRules.Select(x => x.RuleValue).ToArray(); - var currentNode = GetPage(GetProtectedPage(path)); - foreach (XmlNode n in currentNode.SelectNodes("./group")) - { - roles.Add(n.Attributes.GetNamedItem("id").Value); - } - } - - return (string[])roles.ToArray(typeof(string)); } [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] public static member.MemberGroup[] GetAccessingGroups(int DocumentId) { - var d = new Document(DocumentId); + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return null; - using (new ReadLock(Locker)) - { - if (IsProtectedInternal(DocumentId, d.Path) == false) - return null; + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return null; - var currentNode = GetPage(GetProtectedPage(d.Path)); - var mg = new member.MemberGroup[currentNode.SelectNodes("./group").Count]; - int count = 0; - foreach (XmlNode n in currentNode.SelectNodes("./group")) - { - mg[count] = new member.MemberGroup(int.Parse(n.Attributes.GetNamedItem("id").Value)); - count++; - } - return mg; - } + var memberGroupIdRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType); + + return memberGroupIdRules.Select(x => new member.MemberGroup(int.Parse(x.RuleValue))).ToArray(); } [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] public static member.Member GetAccessingMember(int DocumentId) { - var d = new Document(DocumentId); + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return null; - using (new ReadLock(Locker)) + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return null; + + //legacy would throw an exception here if it was not 'simple' and simple means based on a member id in this case + if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberIdRuleType)) { - if (IsProtectedInternal(DocumentId, d.Path) == false) - return null; - - if (GetProtectionTypeInternal(DocumentId) != ProtectionType.Simple) - throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); - - var currentNode = GetPage(GetProtectedPage(d.Path)); - if (currentNode.Attributes.GetNamedItem("memberId") != null) - return new member.Member(int.Parse( - currentNode.Attributes.GetNamedItem("memberId").Value)); + throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); } + + var memberIdRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType); + return new member.Member(int.Parse(memberIdRule.RuleValue)); - throw new Exception("Document doesn't contain a memberId. This might be caused if document is protected using umbraco RC1 or older."); } public static MembershipUser GetAccessingMembershipUser(int documentId) { - var d = new CMSNode(documentId); + var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); + if (content == null) return null; - using (new ReadLock(Locker)) - { - if (IsProtectedInternal(documentId, d.Path) == false) - return null; + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return null; - if (GetProtectionTypeInternal(documentId) != ProtectionType.Simple) - throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); - - var currentNode = GetPage(GetProtectedPage(d.Path)); - if (currentNode.Attributes.GetNamedItem("memberId") != null) - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - - return provider.GetUser(currentNode.Attributes.GetNamedItem("memberId").Value, false); - } + //legacy would throw an exception here if it was not 'simple' and simple means based on a username + if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberUsernameRuleType)) + { + throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); } - throw new Exception("Document doesn't contain a memberId. This might be caused if document is protected using umbraco RC1 or older."); + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + var usernameRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType); + return provider.GetUser(usernameRule.RuleValue, false); + } [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] public static bool HasAccess(int DocumentId, member.Member Member) { - bool hasAccess = false; + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return true; - var d = new Document(DocumentId); + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return true; - using (new ReadLock(Locker)) - { - if (IsProtectedInternal(DocumentId, d.Path) == false) - { - hasAccess = true; - } - else - { - var currentNode = GetPage(GetProtectedPage(d.Path)); - if (Member != null) - { - var ide = Member.Groups.GetEnumerator(); - while (ide.MoveNext()) - { - var mg = (member.MemberGroup)ide.Value; - if (currentNode.SelectSingleNode("./group [@id=" + mg.Id.ToString() + "]") != null) - { - hasAccess = true; - break; - } - } - } - } - } + var memberGroupIds = Member.Groups.Values.Cast().Select(x => x.Id.ToString(CultureInfo.InvariantCulture)).ToArray(); + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType + && memberGroupIds.Contains(x.RuleValue)); - return hasAccess; } [Obsolete("This method has been replaced because of a spelling mistake. Use the HasAccess method instead.", false)] @@ -503,204 +369,76 @@ namespace umbraco.cms.businesslogic.web public static bool HasAccess(int documentId, object memberId) { - bool hasAccess = false; - var node = new CMSNode(documentId); - - using (new ReadLock(Locker)) - { - if (IsProtectedInternal(documentId, node.Path) == false) - return true; - - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - - var member = provider.GetUser(memberId, false); - var currentNode = GetPage(GetProtectedPage(node.Path)); - - if (member != null) - { - foreach (string role in Roles.GetRolesForUser(member.UserName)) - { - if (currentNode.SelectSingleNode("./group [@id='" + role + "']") != null) - { - hasAccess = true; - break; - } - } - } - } - - return hasAccess; + return ApplicationContext.Current.Services.PublicAccessService.HasAccess( + documentId, + memberId, + ApplicationContext.Current.Services.ContentService, + MembershipProviderExtensions.GetMembersMembershipProvider(), + //TODO: This should really be targeting a specific provider by name!! + Roles.Provider); } public static bool HasAccess(int documentId, string path, MembershipUser member) { - bool hasAccess = false; - - using (new ReadLock(Locker)) - { - if (IsProtectedInternal(documentId, path) == false) - { - hasAccess = true; - } - else - { - XmlNode currentNode = GetPage(GetProtectedPage(path)); - if (member != null) - { - string[] roles = Roles.GetRolesForUser(member.UserName); - foreach (string role in roles) - { - if (currentNode.SelectSingleNode("./group [@id='" + role + "']") != null) - { - hasAccess = true; - break; - } - } - } - } - } - - return hasAccess; + return ApplicationContext.Current.Services.PublicAccessService.HasAccess( + path, + member, + //TODO: This should really be targeting a specific provider by name!! + Roles.Provider); } public static ProtectionType GetProtectionType(int DocumentId) { - using (new ReadLock(Locker)) - { - XmlNode x = GetPage(DocumentId); - try - { - return bool.Parse(x.Attributes.GetNamedItem("simple").Value) - ? ProtectionType.Simple - : ProtectionType.Advanced; - } - catch - { - return ProtectionType.NotProtected; - } - } + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return ProtectionType.NotProtected; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return ProtectionType.NotProtected; + + //legacy states that if it is protected by a member id then it is 'simple' + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType) + ? ProtectionType.Simple + : ProtectionType.Advanced; } public static bool IsProtected(int DocumentId, string Path) { - using (new ReadLock(Locker)) - { - return IsProtectedInternal(DocumentId, Path); - } + return ApplicationContext.Current.Services.PublicAccessService.IsProtected(Path.EnsureEndsWith("," + DocumentId)); } public static int GetErrorPage(string Path) { - using (new ReadLock(Locker)) - { - return int.Parse(GetPage(GetProtectedPage(Path)).Attributes.GetNamedItem("noRightsPage").Value); - } + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); + if (entry == null) return -1; + var entity = ApplicationContext.Current.Services.EntityService.Get(entry.NoAccessNodeId, UmbracoObjectTypes.Document, false); + return entity.Id; + } public static int GetLoginPage(string Path) { - using (new ReadLock(Locker)) - { - return int.Parse(GetPage(GetProtectedPage(Path)).Attributes.GetNamedItem("loginPage").Value); - } + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); + if (entry == null) return -1; + var entity = ApplicationContext.Current.Services.EntityService.Get(entry.LoginNodeId, UmbracoObjectTypes.Document, false); + return entity.Id; + } #endregion - private static ProtectionType GetProtectionTypeInternal(int DocumentId) + + //NOTE: This is purely here for backwards compat for events + private static void Save() { - //NOTE: No locks here! the locking is done in callers to this method - - XmlNode x = GetPage(DocumentId); - try - { - return bool.Parse(x.Attributes.GetNamedItem("simple").Value) - ? ProtectionType.Simple - : ProtectionType.Advanced; - } - catch - { - return ProtectionType.NotProtected; - } - } - - private static bool IsProtectedInternal(int DocumentId, string Path) - { - //NOTE: No locks here! the locking is done in callers to this method - - bool isProtected = false; - - if (CheckedPages.ContainsKey(DocumentId) == false) - { - foreach (string id in Path.Split(',')) - { - if (GetPage(int.Parse(id)) != null) - { - isProtected = true; - break; - } - } - - if (CheckedPages.ContainsKey(DocumentId) == false) - { - CheckedPages.Add(DocumentId, isProtected); - } - } - else - { - isProtected = (bool)CheckedPages[DocumentId]; - } - - return isProtected; - } - - private static void Save(XmlDocument newDoc) - { - //NOTE: No locks here! the locking is done in callers to this method - var e = new SaveEventArgs(); new Access().FireBeforeSave(e); if (e.Cancel) return; - using (var f = File.Open(_accessXmlFilePath, FileMode.Create)) - { - newDoc.Save(f); - f.Close(); - //set the underlying in-mem object to null so it gets re-read - _accessXmlContent = null; - } - new Access().FireAfterSave(e); } - private static int GetProtectedPage(string Path) - { - //NOTE: No locks here! the locking is done in callers to this method - - int protectedPage = 0; - - foreach (string id in Path.Split(',')) - if (GetPage(int.Parse(id)) != null) - protectedPage = int.Parse(id); - - return protectedPage; - } - - private static XmlNode GetPage(int documentId) - { - //NOTE: No locks here! the locking is done in callers to this method - var xDoc = GetXmlDocumentCopy(); - - var x = xDoc.SelectSingleNode("/access/page [@id=" + documentId + "]"); - return x; - } - - private static void ClearCheckPages() - { - CheckedPages.Clear(); - } //Event delegates public delegate void SaveEventHandler(Access sender, SaveEventArgs e); diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index abf5f9baa3..ea98caa818 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -82,27 +82,27 @@ namespace umbraco.cms.businesslogic.web this._optimizedMode = optimizedMode; if (optimizedMode) { - Content = ApplicationContext.Current.Services.ContentService.GetById(id); + ContentEntity = ApplicationContext.Current.Services.ContentService.GetById(id); bool hasChildren = ApplicationContext.Current.Services.ContentService.HasChildren(id); - int templateId = Content.Template == null ? 0 : Content.Template.Id; + int templateId = ContentEntity.Template == null ? 0 : ContentEntity.Template.Id; - SetupDocumentForTree(Content.Key, Content.Level, Content.ParentId, Content.CreatorId, - Content.WriterId, - Content.Published, Content.Path, Content.Name, Content.CreateDate, - Content.UpdateDate, Content.UpdateDate, Content.ContentType.Icon, hasChildren, - Content.ContentType.Alias, Content.ContentType.Thumbnail, - Content.ContentType.Description, null, Content.ContentType.Id, - templateId, Content.ContentType.IsContainer); + SetupDocumentForTree(ContentEntity.Key, ContentEntity.Level, ContentEntity.ParentId, ContentEntity.CreatorId, + ContentEntity.WriterId, + ContentEntity.Published, ContentEntity.Path, ContentEntity.Name, ContentEntity.CreateDate, + ContentEntity.UpdateDate, ContentEntity.UpdateDate, ContentEntity.ContentType.Icon, hasChildren, + ContentEntity.ContentType.Alias, ContentEntity.ContentType.Thumbnail, + ContentEntity.ContentType.Description, null, ContentEntity.ContentType.Id, + templateId, ContentEntity.ContentType.IsContainer); - var tmpReleaseDate = Content.ReleaseDate.HasValue ? Content.ReleaseDate.Value : new DateTime(); - var tmpExpireDate = Content.ExpireDate.HasValue ? Content.ExpireDate.Value : new DateTime(); - var creator = new User(Content.CreatorId, true); - var writer = new User(Content.WriterId, true); + var tmpReleaseDate = ContentEntity.ReleaseDate.HasValue ? ContentEntity.ReleaseDate.Value : new DateTime(); + var tmpExpireDate = ContentEntity.ExpireDate.HasValue ? ContentEntity.ExpireDate.Value : new DateTime(); + var creator = new User(ContentEntity.CreatorId, true); + var writer = new User(ContentEntity.WriterId, true); - InitializeContent(Content.ContentType.Id, Content.Version, Content.UpdateDate, - Content.ContentType.Icon); - InitializeDocument(creator, writer, Content.Name, templateId, tmpReleaseDate, tmpExpireDate, - Content.UpdateDate, Content.Published); + InitializeContent(ContentEntity.ContentType.Id, ContentEntity.Version, ContentEntity.UpdateDate, + ContentEntity.ContentType.Icon); + InitializeDocument(creator, writer, ContentEntity.Name, templateId, tmpReleaseDate, tmpExpireDate, + ContentEntity.UpdateDate, ContentEntity.Published); } } @@ -155,7 +155,7 @@ namespace umbraco.cms.businesslogic.web private User _writer; private int? _writerId; private readonly bool _optimizedMode; - protected internal IContent Content; + protected internal IContent ContentEntity; /// /// This is used to cache the child documents of Document when the children property @@ -349,17 +349,17 @@ namespace umbraco.cms.businesslogic.web { get { - return Content == null ? base.sortOrder : Content.SortOrder; + return ContentEntity == null ? base.sortOrder : ContentEntity.SortOrder; } set { - if (Content == null) + if (ContentEntity == null) { base.sortOrder = value; } else { - Content.SortOrder = value; + ContentEntity.SortOrder = value; } } } @@ -368,17 +368,17 @@ namespace umbraco.cms.businesslogic.web { get { - return Content == null ? base.Level : Content.Level; + return ContentEntity == null ? base.Level : ContentEntity.Level; } set { - if (Content == null) + if (ContentEntity == null) { base.Level = value; } else { - Content.Level = value; + ContentEntity.Level = value; } } } @@ -387,7 +387,7 @@ namespace umbraco.cms.businesslogic.web { get { - return Content == null ? base.ParentId : Content.ParentId; + return ContentEntity == null ? base.ParentId : ContentEntity.ParentId; } } @@ -395,17 +395,17 @@ namespace umbraco.cms.businesslogic.web { get { - return Content == null ? base.Path : Content.Path; + return ContentEntity == null ? base.Path : ContentEntity.Path; } set { - if (Content == null) + if (ContentEntity == null) { base.Path = value; } else { - Content.Path = value; + ContentEntity.Path = value; } } } @@ -488,11 +488,11 @@ namespace umbraco.cms.businesslogic.web _published = value; if (_published) { - Content.ChangePublishedState(PublishedState.Published); + ContentEntity.ChangePublishedState(PublishedState.Published); } else { - Content.ChangePublishedState(PublishedState.Unpublished); + ContentEntity.ChangePublishedState(PublishedState.Unpublished); } } @@ -506,7 +506,7 @@ namespace umbraco.cms.businesslogic.web { get { - return ApplicationContext.Current.Services.ContentService.IsPublishable(Content); + return ApplicationContext.Current.Services.ContentService.IsPublishable(ContentEntity); } } @@ -515,12 +515,12 @@ namespace umbraco.cms.businesslogic.web { get { - return Content.Name; + return ContentEntity.Name; } set { value = value.Trim(); - Content.Name = value; + ContentEntity.Name = value; } } @@ -534,7 +534,7 @@ namespace umbraco.cms.businesslogic.web set { _updated = value; - Content.UpdateDate = value; + ContentEntity.UpdateDate = value; /*SqlHelper.ExecuteNonQuery("update cmsDocument set updateDate = @value where versionId = @versionId", SqlHelper.CreateParameter("@value", value), SqlHelper.CreateParameter("@versionId", Version));*/ @@ -551,7 +551,7 @@ namespace umbraco.cms.businesslogic.web set { _release = value; - Content.ReleaseDate = value; + ContentEntity.ReleaseDate = value; } } @@ -565,7 +565,7 @@ namespace umbraco.cms.businesslogic.web set { _expire = value; - Content.ExpireDate = value; + ContentEntity.ExpireDate = value; } } @@ -588,12 +588,12 @@ namespace umbraco.cms.businesslogic.web _template = value; if (value == 0) { - Content.Template = null; + ContentEntity.Template = null; } else { var template = ApplicationContext.Current.Services.FileService.GetTemplate(value); - Content.Template = template; + ContentEntity.Template = template; } } } @@ -628,7 +628,7 @@ namespace umbraco.cms.businesslogic.web FireBeforeSendToPublish(e); if (e.Cancel == false) { - var sent = ApplicationContext.Current.Services.ContentService.SendToPublication(Content, u.Id); + var sent = ApplicationContext.Current.Services.ContentService.SendToPublication(ContentEntity, u.Id); if (sent) { FireAfterSendToPublish(e); @@ -668,7 +668,7 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { - var result = ApplicationContext.Current.Services.ContentService.PublishWithStatus(Content, u.Id); + var result = ApplicationContext.Current.Services.ContentService.PublishWithStatus(ContentEntity, u.Id); _published = result.Success; FireAfterPublish(e); @@ -684,7 +684,7 @@ namespace umbraco.cms.businesslogic.web [Obsolete("Obsolete, Use Umbraco.Core.Services.ContentService.PublishWithChildren()", false)] public bool PublishWithChildrenWithResult(User u) { - var result = ApplicationContext.Current.Services.ContentService.PublishWithChildrenWithStatus(Content, u.Id, true); + var result = ApplicationContext.Current.Services.ContentService.PublishWithChildrenWithStatus(ContentEntity, u.Id, true); //This used to just return false only when the parent content failed, otherwise would always return true so we'll // do the same thing for the moment return result.Single(x => x.Result.ContentItem.Id == Id).Success; @@ -704,7 +704,7 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { - Content = ApplicationContext.Current.Services.ContentService.Rollback(Id, VersionId, u.Id); + ContentEntity = ApplicationContext.Current.Services.ContentService.Rollback(Id, VersionId, u.Id); FireAfterRollBack(e); } @@ -725,7 +725,7 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { IEnumerable> publishedResults = ApplicationContext.Current.Services.ContentService - .PublishWithChildrenWithStatus(Content, u.Id); + .PublishWithChildrenWithStatus(ContentEntity, u.Id); FireAfterPublish(e); } @@ -742,7 +742,7 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { publishedResults = ApplicationContext.Current.Services.ContentService - .PublishWithChildrenWithStatus(Content, userId, includeUnpublished); + .PublishWithChildrenWithStatus(ContentEntity, userId, includeUnpublished); FireAfterPublish(e); } @@ -761,7 +761,7 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { - _published = ApplicationContext.Current.Services.ContentService.UnPublish(Content); + _published = ApplicationContext.Current.Services.ContentService.UnPublish(ContentEntity); FireAfterUnPublish(e); } @@ -773,7 +773,7 @@ namespace umbraco.cms.businesslogic.web [Obsolete("Obsolete, Use Umbraco.Core.Services.ContentService.HasPublishedVersion()", false)] public bool HasPublishedVersion() { - return Content.HasPublishedVersion(); + return ContentEntity.HasPublishedVersion; } /// @@ -785,7 +785,7 @@ namespace umbraco.cms.businesslogic.web [Obsolete("Obsolete, Instead of calling this just check if the latest version of the content is published", false)] public bool HasPendingChanges() { - return Content.Published == false && ((Umbraco.Core.Models.Content)Content).PublishedState != PublishedState.Unpublished; + return ContentEntity.Published == false && ((Umbraco.Core.Models.Content)ContentEntity).PublishedState != PublishedState.Unpublished; } /// @@ -864,7 +864,7 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { // Make the new document - var content = ApplicationContext.Current.Services.ContentService.Copy(Content, CopyTo, RelateToOrignal, u.Id); + var content = ApplicationContext.Current.Services.ContentService.Copy(ContentEntity, CopyTo, RelateToOrignal, u.Id); newDoc = new Document(content); // Then save to preserve any changes made by action handlers @@ -910,9 +910,9 @@ namespace umbraco.cms.businesslogic.web [Obsolete("Obsolete, Use Umbraco.Core.Services.ContentService.GetDescendants()", false)] public override IEnumerable GetDescendants() { - var descendants = Content == null + var descendants = ContentEntity == null ? ApplicationContext.Current.Services.ContentService.GetDescendants(Id) - : ApplicationContext.Current.Services.ContentService.GetDescendants(Content); + : ApplicationContext.Current.Services.ContentService.GetDescendants(ContentEntity); return descendants.Select(x => new Document(x.Id, true)); } @@ -965,32 +965,32 @@ namespace umbraco.cms.businesslogic.web private void SetupNode(IContent content) { - Content = content; + ContentEntity = content; //Also need to set the ContentBase item to this one so all the propery values load from it - ContentBase = Content; + ContentBase = ContentEntity; //Setting private properties from IContentBase replacing CMSNode.setupNode() / CMSNode.PopulateCMSNodeFromReader() - base.PopulateCMSNodeFromUmbracoEntity(Content, _objectType); + base.PopulateCMSNodeFromUmbracoEntity(ContentEntity, _objectType); //If the version is empty we update with the latest version from the current IContent. if (Version == Guid.Empty) - Version = Content.Version; + Version = ContentEntity.Version; //Setting private properties from IContent replacing Document.setupNode() - _creator = User.GetUser(Content.CreatorId); - _writer = User.GetUser(Content.WriterId); - _updated = Content.UpdateDate; + _creator = User.GetUser(ContentEntity.CreatorId); + _writer = User.GetUser(ContentEntity.WriterId); + _updated = ContentEntity.UpdateDate; - if (Content.Template != null) - _template = Content.Template.Id; + if (ContentEntity.Template != null) + _template = ContentEntity.Template.Id; - if (Content.ExpireDate.HasValue) - _expire = Content.ExpireDate.Value; + if (ContentEntity.ExpireDate.HasValue) + _expire = ContentEntity.ExpireDate.Value; - if (Content.ReleaseDate.HasValue) - _release = Content.ReleaseDate.Value; + if (ContentEntity.ReleaseDate.HasValue) + _release = ContentEntity.ReleaseDate.Value; - _published = Content.Published; + _published = ContentEntity.Published; } [Obsolete("Obsolete", false)] @@ -1115,14 +1115,14 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { - if (Content != null) + if (ContentEntity != null) { - ApplicationContext.Current.Services.ContentService.Delete(Content); + ApplicationContext.Current.Services.ContentService.Delete(ContentEntity); } else { - Content = ApplicationContext.Current.Services.ContentService.GetById(Id); - ApplicationContext.Current.Services.ContentService.Delete(Content); + ContentEntity = ApplicationContext.Current.Services.ContentService.GetById(Id); + ApplicationContext.Current.Services.ContentService.Delete(ContentEntity); } //Keeping the base.delete() as it looks to be clear 'private/internal cache' @@ -1146,14 +1146,14 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { UnPublish(); - if (Content != null) + if (ContentEntity != null) { - ApplicationContext.Current.Services.ContentService.MoveToRecycleBin(Content); + ApplicationContext.Current.Services.ContentService.MoveToRecycleBin(ContentEntity); } else { - Content = ApplicationContext.Current.Services.ContentService.GetById(Id); - ApplicationContext.Current.Services.ContentService.MoveToRecycleBin(Content); + ContentEntity = ApplicationContext.Current.Services.ContentService.GetById(Id); + ApplicationContext.Current.Services.ContentService.MoveToRecycleBin(ContentEntity); } FireAfterMoveToTrash(e); } diff --git a/src/umbraco.cms/businesslogic/workflow/Notification.cs b/src/umbraco.cms/businesslogic/workflow/Notification.cs index 098dfe49a7..76bf86a130 100644 --- a/src/umbraco.cms/businesslogic/workflow/Notification.cs +++ b/src/umbraco.cms/businesslogic/workflow/Notification.cs @@ -77,7 +77,7 @@ namespace umbraco.cms.businesslogic.workflow var pUser = ApplicationContext.Current.Services.UserService.GetUserById(performingUser.Id); nService.SendNotifications( - pUser, documentObject.Content, action.Letter.ToString(CultureInfo.InvariantCulture), ui.Text(action.Alias), + pUser, documentObject.ContentEntity, action.Letter.ToString(CultureInfo.InvariantCulture), ui.Text(action.Alias), new HttpContextWrapper(HttpContext.Current), (user, strings) => ui.Text("notifications", "mailSubject", strings, mailingUser), (user, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail