From efab275c99ccccffb844b8c1b3865330dc44d371 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 27 Jan 2015 19:47:30 +1100 Subject: [PATCH 01/38] Fixes: U4-6188 umbracoNode.uniqueId needs to be changed to not allow nulls, U4-6187 umbracoNode.uniqueID needs a Unique index - currently it is a non-unique index, Gets the public access repository built and started testing --- src/Umbraco.Core/Models/EntityBase/Entity.cs | 2 +- src/Umbraco.Core/Models/PublicAccessEntry.cs | 177 ++++++++++++ src/Umbraco.Core/Models/PublicAccessRule.cs | 81 ++++++ src/Umbraco.Core/Models/Rdbms/AccessDto.cs | 36 ++- .../Models/Rdbms/AccessRuleDto.cs | 17 +- src/Umbraco.Core/Models/Rdbms/NodeDto.cs | 6 +- .../Persistence/Factories/ContentFactory.cs | 5 +- .../Factories/ContentTypeFactory.cs | 5 +- .../Factories/DataTypeDefinitionFactory.cs | 5 +- .../Persistence/Factories/MediaFactory.cs | 5 +- .../Persistence/Factories/MediaTypeFactory.cs | 5 +- .../Persistence/Factories/MemberFactory.cs | 5 +- .../Factories/MemberGroupFactory.cs | 2 +- .../Factories/PublicAccessEntryFactory.cs | 55 ++++ .../Persistence/Factories/TemplateFactory.cs | 2 +- .../Factories/UmbracoEntityFactory.cs | 2 +- .../AddPublicAccessTables.cs | 34 ++- .../UpdateUniqueIdToHaveCorrectIndexType.cs | 22 ++ .../Relators/AccessRulesRelator.cs | 43 +++ .../Repositories/ContentRepository.cs | 18 ++ .../Interfaces/IPublicAccessRepository.cs | 10 + .../Interfaces/ITaskRepository.cs | 5 - .../Interfaces/ITaskTypeRepository.cs | 9 + .../Repositories/PetaPocoRepositoryBase.cs | 8 +- .../Repositories/PublicAccessRepository.cs | 153 +++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 8 + .../Repositories/ContentRepositoryTest.cs | 8 +- .../PublicAccessRepositoryTest.cs | 259 ++++++++++++++++++ .../Repositories/TaskRepositoryTest.cs | 4 + src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../config/ClientDependency.config | 2 +- 31 files changed, 927 insertions(+), 67 deletions(-) create mode 100644 src/Umbraco.Core/Models/PublicAccessEntry.cs create mode 100644 src/Umbraco.Core/Models/PublicAccessRule.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/PublicAccessEntryFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs create mode 100644 src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IPublicAccessRepository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/ITaskTypeRepository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs create mode 100644 src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index 315e2697c0..1551f29f86 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -59,7 +59,7 @@ namespace Umbraco.Core.Models.EntityBase /// The key is currectly used to store the Unique Id from the /// umbracoNode table, which many of the entities are based on. [DataMember] - public Guid Key + public virtual Guid Key { get { diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs new file mode 100644 index 0000000000..948f33bc03 --- /dev/null +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -0,0 +1,177 @@ +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 Guid _protectedNodeId; + private Guid _noAccessNodeId; + private Guid _loginNodeId; + private readonly List _removedRules = new List(); + + public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent noAccessNode, IEnumerable ruleCollection) + { + LoginNodeId = loginNode.Key; + NoAccessNodeId = noAccessNode.Key; + _protectedNodeId = protectedNode.Key; + + _ruleCollection = new ObservableCollection(ruleCollection); + _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; + } + + public PublicAccessEntry(Guid id, Guid protectedNodeId, Guid loginNodeId, Guid noAccessNodeId, IEnumerable ruleCollection) + { + Key = id; + + 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 claim, string claimType) + { + var rule = new PublicAccessRule + { + AccessEntryId = Key, + Claim = claim, + ClaimType = claimType + }; + _ruleCollection.Add(rule); + return rule; + } + + public void RemoveRule(PublicAccessRule rule) + { + _ruleCollection.Remove(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 sealed override Guid Key + { + get { return base.Key; } + set + { + base.Key = value; + HasIdentity = true; + } + } + + [DataMember] + public Guid LoginNodeId + { + get { return _loginNodeId; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _loginNodeId = value; + return _loginNodeId; + }, _loginNodeId, LoginNodeIdSelector); + } + } + + [DataMember] + public Guid NoAccessNodeId + { + get { return _noAccessNodeId; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _noAccessNodeId = value; + return _noAccessNodeId; + }, _noAccessNodeId, NoAccessNodeIdSelector); + } + } + + [DataMember] + public Guid 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..aefa681794 --- /dev/null +++ b/src/Umbraco.Core/Models/PublicAccessRule.cs @@ -0,0 +1,81 @@ +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 _claim; + private string _claimType; + + public PublicAccessRule(Guid id, Guid accessEntryId) + { + AccessEntryId = accessEntryId; + Key = id; + Id = Key.GetHashCode(); + } + + public PublicAccessRule() + { + } + + private static readonly PropertyInfo ClaimSelector = ExpressionHelper.GetPropertyInfo(x => x.Claim); + private static readonly PropertyInfo ClaimTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ClaimType); + + public sealed override Guid Key + { + get { return base.Key; } + set + { + base.Key = value; + HasIdentity = true; + } + } + + 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 Claim + { + get { return _claim; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _claim = value; + return _claim; + }, _claim, ClaimSelector); + } + } + + public string ClaimType + { + get { return _claimType; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _claimType = value; + return _claimType; + }, _claimType, ClaimTypeSelector); + } + } + + + } +} \ 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..2c587946a1 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/AccessDto.cs @@ -1,27 +1,43 @@ -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")] - public int NodeId { get; set; } + [ForeignKey(typeof(NodeDto), Name = "FK_umbracoAccess_umbracoNode_uniqueID", Column = "uniqueID")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoAccess_nodeId")] + public Guid NodeId { get; set; } [Column("loginNodeId")] - [ForeignKey(typeof(NodeDto), Name = "FK_umbracoAccess_umbracoNode_id1")] - public int LoginNodeId { get; set; } + [ForeignKey(typeof(NodeDto), Name = "FK_umbracoAccess_umbracoNode_uniqueID1", Column = "uniqueID")] + public Guid LoginNodeId { get; set; } [Column("noAccessNodeId")] - [ForeignKey(typeof(NodeDto), Name = "FK_umbracoAccess_umbracoNode_id2")] - public int AccessDeniedNodeId { get; set; } + [ForeignKey(typeof(NodeDto), Name = "FK_umbracoAccess_umbracoNode_uniqueID2", Column = "uniqueID")] + public Guid AccessDeniedNodeId { 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..4a8961c9dd 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs @@ -1,25 +1,34 @@ +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("claimType")] public string ClaimType { 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/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/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index 0208a7a128..66dc6f64dc 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, 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/PublicAccessEntryFactory.cs b/src/Umbraco.Core/Persistence/Factories/PublicAccessEntryFactory.cs new file mode 100644 index 0000000000..797914bce8 --- /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.AccessDeniedNodeId, + dto.Rules.Select(x => new PublicAccessRule(x.Id, x.AccessId) + { + Claim = x.Claim, + ClaimType = x.ClaimType, + 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, + AccessDeniedNodeId = 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, + Claim = x.Claim, + ClaimType = x.ClaimType, + 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 056d554d17..0494a25cfb 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/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs index e2a5318051..0ab2e683b8 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs @@ -1,9 +1,10 @@ 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)] + [Migration("7.3.0", 6, GlobalSettings.UmbracoMigrationName)] public class AddPublicAccessTables : MigrationBase { public override void Up() @@ -13,20 +14,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("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("id").AsGuid().NotNullable().PrimaryKey("PK_umbracoAccess") + .WithColumn("nodeId").AsGuid().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id", "umbracoNode", "uniqueID") + .WithColumn("loginNodeId").AsGuid().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id1", "umbracoNode", "uniqueID") + .WithColumn("noAccessNodeId").AsGuid().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id2", "umbracoNode", "uniqueID") + .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("id").AsGuid().NotNullable().PrimaryKey("PK_umbracoAccessRule") + .WithColumn("accessId").AsGuid().NotNullable().ForeignKey("FK_umbracoAccessRule_umbracoAccess_id", "umbracoAccess", "id") .WithColumn("claim").AsString().NotNullable() - .WithColumn("claimType").AsString().NotNullable(); + .WithColumn("claimType").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 + claim + claim type + Create.Index("IX_umbracoAccessRule").OnTable("umbracoAccessRule") + .OnColumn("accessId").Ascending() + .OnColumn("claim").Ascending() + .OnColumn("claimType").Ascending() + .WithOptions() + .Unique(); } public override void Down() 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..92876bbb4d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs @@ -0,0 +1,22 @@ +using Umbraco.Core.Configuration; + +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() + { + //must be non-nullable + Alter.Column("uniqueID").OnTable("umbracoNode").AsGuid().NotNullable(); + //must be a uniqe index + Delete.Index("IX_umbracoNodeUniqueID").OnTable("umbracoNode"); + 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/Relators/AccessRulesRelator.cs b/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs new file mode 100644 index 0000000000..424c68a863 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs @@ -0,0 +1,43 @@ +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 + 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(); + 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 55ec8b3b25..3ac4f5f942 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -305,6 +305,24 @@ 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 + var subQuery = new Sql() + .Select("umbracoAccessRule.accessId") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.AccessId, right => right.Id) + .Where(dto => dto.NodeId == entity.Key); + Database.Execute(SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); + //Now delete everything from umbracoAccess, we are doing this manually because we have joined on GUIDs instead + // of integers since we'll be moving to GUID for v8 for everything + Database.Execute("DELETE FROM umbracoAccess WHERE nodeId = @Key", new {Key = entity.Key}); + + //now let the normal delete clauses take care of everything else + base.PersistDeletedItem(entity); + } + protected override void PersistNewItem(IContent entity) { ((Content)entity).AddingEntity(); 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..9ce00cc13a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPublicAccessRepository.cs @@ -0,0 +1,10 @@ +using System; +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/Interfaces/ITaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITaskRepository.cs index d2b4770082..60f17c6de5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITaskRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITaskRepository.cs @@ -4,11 +4,6 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface ITaskTypeRepository : IRepositoryQueryable - { - - } - public interface ITaskRepository : IRepositoryQueryable { IEnumerable GetTasks(int? itemId = null, int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false); diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITaskTypeRepository.cs new file mode 100644 index 0000000000..ed35543ef6 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITaskTypeRepository.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface ITaskTypeRepository : IRepositoryQueryable + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs index 872b7f1d14..fe363fea16 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; @@ -72,8 +73,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..e2c02523d9 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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) + { + } + + 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) + .InnerJoin(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(); } + } + + 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); + + 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) + { + Database.Update(dto.Rules.Single(x => x.Id == rule.Key)); + } + else + { + Database.Insert(new AccessRuleDto + { + AccessId = dto.NodeId, + Claim = rule.Claim, + ClaimType = rule.ClaimType, + CreateDate = rule.CreateDate, + UpdateDate = rule.UpdateDate + }); + } + } + 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/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b759c51634..ab9d578fbf 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -320,22 +320,30 @@ + + + + + + + + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 2a65297864..f943f05de6 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -44,10 +44,10 @@ namespace Umbraco.Tests.Persistence.Repositories private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) { - var templateRepository = new TemplateRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax); - contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, templateRepository); - var repository = new ContentRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository); + 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; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs new file mode 100644 index 0000000000..e42a21a115 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -0,0 +1,259 @@ +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 + { + Claim = "test", + ClaimType = "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 + { + Claim = "test", + ClaimType = "RoleName" + }, + }); + repo.AddOrUpdate(entry); + unitOfWork.Commit(); + + var found = repo.GetAll().ToArray(); + + Assert.AreEqual(1, found.Count()); + Assert.AreEqual(content[0].Key, found[0].ProtectedNodeId); + Assert.AreEqual(content[1].Key, found[0].LoginNodeId); + Assert.AreEqual(content[2].Key, 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().Claim); + Assert.AreEqual("RoleName", found[0].Rules.First().ClaimType); + 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 + { + Claim = "test", + ClaimType = "RoleName" + }, + }); + repo.AddOrUpdate(entry); + unitOfWork.Commit(); + + //re-get + entry = repo.Get(entry.Key); + + entry.Rules.First().Claim = "blah"; + entry.Rules.First().ClaimType = "asdf"; + repo.AddOrUpdate(entry); + + unitOfWork.Commit(); + + //re-get + entry = repo.Get(entry.Key); + + Assert.AreEqual("blah", entry.Rules.First().Claim); + Assert.AreEqual("asdf", entry.Rules.First().ClaimType); + } + } + + [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 + { + Claim = "test", + ClaimType = "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 + { + Claim = "test", + ClaimType = "RoleName" + }, + }); + repo.AddOrUpdate(entry1); + + var entry2 = new PublicAccessEntry(content[1], content[0], content[2], new[] + { + new PublicAccessRule + { + Claim = "test", + ClaimType = "RoleName" + }, + }); + repo.AddOrUpdate(entry2); + + unitOfWork.Commit(); + + var found = repo.GetAll().ToArray(); + Assert.AreEqual(2, found.Count()); + } + } + + + [Test] + public void Get_All_With_Node_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 + { + Claim = "test", + ClaimType = "RoleName" + }, + }); + repo.AddOrUpdate(entry1); + + var entry2 = new PublicAccessEntry(content[1], content[0], content[2], new[] + { + new PublicAccessRule + { + Claim = "test", + ClaimType = "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 1477f62e9f..4a755de581 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs @@ -96,6 +96,10 @@ namespace Umbraco.Tests.Persistence.Repositories task.Comment = "blah"; task.Closed = true; + 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/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 7d4fe26670..0600e0106d 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -176,6 +176,7 @@ + diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index 32b39b9d50..006dea76b0 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - + - + ", 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 073afcb0b9..0429cf83ae 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1,15 +1,9 @@ using System; -using System.Collections; using System.ComponentModel; -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; @@ -19,48 +13,59 @@ 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; -using AttributeCollection = System.Web.UI.AttributeCollection; 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 { @@ -73,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 @@ -155,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 @@ -180,7 +257,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias) { - return RenderMacro(alias, new { }); + return _componentRenderer.RenderMacro(alias, new { }); } /// @@ -191,7 +268,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias, object parameters) { - return RenderMacro(alias, parameters.ToDictionary()); + return _componentRenderer.RenderMacro(alias, parameters.ToDictionary()); } /// @@ -202,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 @@ -337,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); } /// @@ -373,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 /// @@ -520,7 +407,7 @@ namespace Umbraco.Web { if (IsProtected(path)) { - return _membershipHelper.IsLoggedIn() + return MembershipHelper.IsLoggedIn() && UmbracoContext.Application.Services.PublicAccessService.HasAccess(path, GetCurrentMember(), Roles.Provider); } return true; @@ -545,7 +432,7 @@ namespace Umbraco.Web /// True is the current user is logged in public bool MemberIsLoggedOn() { - return _membershipHelper.IsLoggedIn(); + return MembershipHelper.IsLoggedIn(); } #endregion @@ -571,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); } /// @@ -582,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); } /// @@ -602,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 @@ -612,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 @@ -1021,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); } /// @@ -1047,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) @@ -1156,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); } @@ -1325,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.MacroEngines/RazorDynamicNode/RazorLibraryCore.cs b/src/umbraco.MacroEngines/RazorDynamicNode/RazorLibraryCore.cs index d9d93a458e..9ef42a798c 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/RazorLibraryCore.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/RazorLibraryCore.cs @@ -13,6 +13,7 @@ namespace umbraco.MacroEngines.Library { private readonly INode _node; private readonly UmbracoHelper _umbracoHelper; + private readonly HtmlStringUtilities _stringUtilities = new HtmlStringUtilities(); /// /// An empty HtmlHelper with a blank ViewContext, used only to access some htmlHelper extension methods @@ -193,16 +194,16 @@ namespace umbraco.MacroEngines.Library public string Coalesce(params object[] args) { - return _umbracoHelper.Coalesce(args); + return _stringUtilities.Coalesce(args); } public string Concatenate(params object[] args) { - return _umbracoHelper.Concatenate(args); + return _stringUtilities.Concatenate(args); } public string Join(string seperator, params object[] args) { - return _umbracoHelper.Join(seperator, args); + return _stringUtilities.Join(seperator, args); } public HtmlString If(bool test, string valueIfTrue, string valueIfFalse) From ca20fb799d9fd90414c467645151bd989a97dd79 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Feb 2015 17:28:21 +0100 Subject: [PATCH 37/38] publicizes RouteDefinition --- src/Umbraco.Web/Mvc/RouteDefinition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Mvc/RouteDefinition.cs b/src/Umbraco.Web/Mvc/RouteDefinition.cs index f0334202dd..fa9443c19d 100644 --- a/src/Umbraco.Web/Mvc/RouteDefinition.cs +++ b/src/Umbraco.Web/Mvc/RouteDefinition.cs @@ -7,7 +7,7 @@ namespace Umbraco.Web.Mvc /// /// Represents the data required to route to a specific controller/action during an Umbraco request /// - internal class RouteDefinition + public class RouteDefinition { public string ControllerName { get; set; } public string ActionName { get; set; } From f63e8d7aed5f7c0d52761b23fc0c616e017943d5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Feb 2015 18:05:37 +0100 Subject: [PATCH 38/38] Adds unit test to show how to configure a test to mock the CurrentPage of a SurfaceController with public APIs --- .../Mvc/SurfaceControllerTests.cs | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs index 317ad418f1..566b90b157 100644 --- a/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs @@ -1,7 +1,9 @@ +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; @@ -35,7 +37,7 @@ namespace Umbraco.Tests.Mvc var umbCtx = UmbracoContext.EnsureContext( new Mock().Object, appCtx, - new Mock(null, null).Object, + new Mock(null, null).Object, Mock.Of(), Enumerable.Empty(), true); @@ -74,7 +76,7 @@ namespace Umbraco.Tests.Mvc MockHelper.GetMockedServiceContext(), CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of())); - + var umbCtx = UmbracoContext.EnsureContext( new Mock().Object, appCtx, @@ -104,7 +106,7 @@ namespace Umbraco.Tests.Mvc var helper = new UmbracoHelper( umbCtx, Mock.Of(), - Mock.Of(query => query.TypedContent(It.IsAny()) == + Mock.Of(query => query.TypedContent(It.IsAny()) == //return mock of IPublishedContent for any call to GetById Mock.Of(content => content.Id == 2)), Mock.Of(), @@ -114,12 +116,54 @@ namespace Umbraco.Tests.Mvc Mock.Of(), Mock.Of(), new MembershipHelper(umbCtx, Mock.Of(), Mock.Of())); - + var ctrl = new TestSurfaceController(umbCtx, helper); var result = ctrl.GetContent(2) as PublishedContentResult; Assert.IsNotNull(result); - Assert.AreEqual(2, result.Content.Id); + Assert.AreEqual(2, result.Content.Id); + } + + [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()) + { + PublishedContent = content + }; + + 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 @@ -156,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